Chinaunix

标题: 关于c/s socket中超时问题的总结 [打印本页]

作者: nuclearweapon    时间: 2006-08-25 10:05
标题: 关于c/s socket中超时问题的总结
本人看了此贴
     http://bbs.chinaunix.net/viewthr ... &extra=page%3D1中思一克斑竹,wyezl 等的讨论,从而有所Think,得出下文,欢迎指正,共同进步!
         
         基于socket tcp的c/s模型中,如果server端只有当client给出request的时候才能作出response的时候
         server端不得不面临这样的设计问题:
         如果client的是异常掉线,比如pc 掉电,拔掉网线等的类似情况,导致server一直占用一些系统资源(
         主要指由于socket引起的)直到大约2小时以后(tcp socket的SO_KEEPALIVE option)server端才会释
         放这些资源。tcp只能保证这些的。 但是,一般不会轻易修改tcp_keepalive_time = 7200(秒)(sysctl)。
         除非为了做一些试验。那么,只能在应用层建立适当的time out机制。
                 
         我总结了如下4种情况,提出了我的处理方案。抛砖引玉。希望大家指正!
         (欢迎拍砖)   
         
         
            
            客户端 可以分为:  A、可控制                        B、不可控制
            服务端 可以分为:  C、select类非多process/thread  D、多process/thread   
           
           所谓客户端可以控制,就是指我们可以修改客户端的源码。
           这里指非process/thread是表示,不是一个client对应一个process/thread
           把select类函数检测的文件描述符集合叫做fdset。
         
           
          A         B
         C  1         3
         D  2         4
         
                           
     一、先说一下第3类:
         客户端为B类型,可能是我们不能修改client的代码,或者我们不愿修改client的代码。
         server端的类型为C的,如果为每一个connection都做一个定时器,不太现实,开销比较大。(尤其是连接数量确实比较大的话)
         
         
         我想到的一个策略是:
         设:
         1.服务端对每个connection,需要做的任务是func(fd);
         2.在服务端维护两个fdset,S(0)和S(1);
         3.在服务端有两个线程work和help且
            a。work线程以周期T(T<2小时)唤醒线程help;(注:T可以用接入server的client个数N替换)
            b。help线程运行的时间为t且t<<T
            
         当周期T到达的时候,work线程的select在检测S(i) i=0或者1;
         这个时候work线程首先唤醒help线程,然后让select检测集合S(1-i),新accpet的fd加入到S(1-i)中;
         对于被唤醒的help线程来说,用select检测S(i),并且在select上设置一个超时时间t;
         
         伪代码如下:
            
         对于worker来说:
         i=0;
         while(true){
                         val=select(S(i));//这个select不设超时
            if (如果是listenfd){
                            fd=accpet(listenfd);
                            S(i)<---fd;
            }else{
           对于每一个可以读的fd:
                    func(fd);           
            }
            if (周期T到达){
               1.从S(i)中去掉listenfd;
                     2.i=1-i;
                     3.listenfd加入到S(i)中;
                     4.唤醒help线程
                     
            }
         }
            
         对于helper线程:         
         while(true){
            rev=select(S(i),t);
            if (rev==超时){
                    1。释放所有资源(清空S(i),如果有必要的话)
                    2。释放cpu,继续等待被worker唤醒
            }else{
                     对于每一个可以读的fd:
                  func(fd);
        //这里需要注意的是对于linux的select,t会被修改为剩余的时间。
         //对于没有修改的api为了精确起见,我们也可以自己写函数修改成类似select的效果。
                                                          
            }
         };
         
         
         我想如果是SMP的系统的话效果会不错。
         
     二、对于第2种类型来说比较简单:
         因为client和server都是可以控制,而且一个client对应一个thread。
         那么client和server可以协商一个协议, 例如client以周期T向server发送一个信号==。
         当然,也可以只在server端用select加一个超时,如果你不需要检测client端的状态的话。
         
         
     三、对于第1种类型来说:
         我个人倾向使用和类型3一样的方法。
         
     四、对于类型4来说:
         目前我只想到只在server端用select加一个超时。
作者: 思一克    时间: 2006-08-25 10:08
很好的,继续研究和完善该帖子
作者: connet    时间: 2006-08-25 17:50
UNIX网络编程(W.Richard Stevens)的书讲的很清楚.
只有写才能检测到中断.
如果一端能确定某一段时间内必须有数据到达, 可由此判断中断.
楼主的  select 能检测到中断吗?

如果没有收到 FIN, select 不可能检测到什么.
作者: 苦中作乐    时间: 2006-08-25 18:48
可以考虑用启动独立的探测线程发送OOB心跳~!
作者: nuclearweapon    时间: 2006-08-25 21:30
原帖由 connet 于 2006-8-25 17:50 发表
如果一端能确定某一段时间内必须有数据到达, 可由此判断中断.
楼主的  select 能检测到中断吗?

在现实中,即便是所谓的“某一段时间内必须有数据到达”,我们也不能简简单单的说:“时间到了,数据没到。现在网络就是断的”。具体的原因可以参考tcp的SO_KEEPALIVE 是怎么判断的。
再说如果有“某一段时间内必须有数据到达”这个条件,某种程度上说你可以掌握(或者说影响)client端的设计,这样的话作为server端的设计者,也就有可能和client之间建立一种协议。就如我文章中说的第2种情况。要注意的是,在第2种情况中,我确实提到了可以使用select,但是那是有条件:“如果你不需要检测client端的状态的话。”

确实,“写”是能够检测状态,但是如果客户端不允许额外的数据怎么办呢?我目前能想到的在tcp的基础上的,只是超时机制。我这片文章主要讨论的是,在server端建立超时机制来尽可能合理的保证server端资源的正确使用!




非常感谢大家的讨论。

[ 本帖最后由 nuclearweapon 于 2006-8-26 09:24 编辑 ]
作者: nuclearweapon    时间: 2006-08-25 21:32
原帖由 苦中作乐 于 2006-8-25 18:48 发表
可以考虑用启动独立的探测线程发送OOB心跳~!


能不能把您设计的伪代码也写出来,一起完善这片文档呢?
谢谢!
作者: benlan    时间: 2006-08-25 23:42
1)服务器对每个连接定时去读或写操作,即使是做个空操作
2)心跳数据非常有用,能解决大部分问题
作者: 苦中作乐    时间: 2006-08-26 00:48
具体例子UNP上介绍的比较清楚,前一段时间我也在考虑这个事情,基本方法也就是LZ列的几种
(1)使用KEEPALIVE
(2)使用自定义探测数据包保持连接
(3)使用OOB探测
其中最好的我觉得就是OOB技术,因为OOB可以设置不占用普通数据缓冲区,并且OOB信息不会排队,但是有两个问题
(1)OOB是通过信号SIGURG来通知应用进程的,而SIGURG信号是不安全信号,可能丢失,这样可能导致认为丢失信号的连接有问题。
(2)用多线程的话....也有问题
上面是一些简单的认识,有问题的话还忘各位指点~
作者: yuanyawei    时间: 2006-10-16 17:03
原帖由 nuclearweapon 于 2006-8-25 10:05 发表
本人看了此贴
     客户端 可以分为:  A、可控制                        B、不可控制
            服务端 可以分为:  C、select类非多process/thread  D、多process/thread  


是不是首先应该是长连接的应用?

我觉得数据流向也很重要:

数据流分为:E: S->C  、F:C->S  、G:S<->C

这个问题确实我也恼火很久了,也是根据不同的应用不同的对待,就是没有通用的方法。
作者: 醉卧水云间    时间: 2006-10-16 17:11
从Multiget摘点代码
连接超時

  1. //with timeout connect
  2. #ifdef WIN32
  3. int CMgSocket::tconnect(SOCKET sock, const struct sockaddr *addr, unsigned int len)
  4. #else
  5. int CMgSocket::tconnect(int sock, const struct sockaddr *addr, unsigned int len)
  6. #endif
  7. {
  8.        
  9.         int ret, val;
  10.         struct timeval tv;
  11.     fd_set sset;

  12. #ifdef WIN32

  13.         int lon;
  14.         u_long iMode=1;
  15.         ioctlsocket(sock,FIONBIO,&iMode);
  16. #else

  17.         int flags,old_flags;

  18.     socklen_t lon;
  19.     flags = old_flags = fcntl(sock, F_GETFL, 0);
  20.         flags |=O_NONBLOCK;
  21.     if (fcntl(sock, F_SETFL, flags) == -1) {
  22.         return -1;
  23.     }               
  24. #endif       
  25.        
  26.         ret= connect(sock, addr, len);

  27.     if(ret < 0)
  28.         {

  29. #ifdef WIN32

  30.         if (WSAEWOULDBLOCK == WSAGetLastError())

  31. #else

  32.         if (errno == EINPROGRESS)

  33. #endif
  34.                 {
  35.             tv.tv_sec = m_nConnectTimeOut;
  36.             tv.tv_usec = 0;
  37.             FD_ZERO(&sset);
  38.             FD_SET(sock, &sset);
  39.             if(select(sock+1, NULL, &sset, NULL, &tv) > 0)
  40.                         {
  41.                 lon = sizeof(int);

  42. #ifdef WIN32
  43.                 getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)(&val), &lon);

  44. #else

  45.                 getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&val), &lon);

  46. #endif


  47.                 if(val != 0)
  48.                                 {
  49.                     ret= -1;
  50.                 }
  51.                                 else
  52.                                 {
  53.                                         ret=0;
  54.                                 }
  55.             }
  56.             else
  57.                         {
  58.                 ret= -1;
  59.             }
  60.         }
  61.         else
  62.                 {

  63.             ret = -1;
  64.         }
  65.     }       
  66. #ifdef WIN32
  67.         iMode=0;
  68.         ioctlsocket(sock,FIONBIO,&iMode);
  69. #else       
  70.         fcntl(sock, F_SETFL, old_flags);
  71. #endif
  72.         return ret;

  73. }
复制代码

作者: 醉卧水云间    时间: 2006-10-16 17:14
读写超時:

  1. //set timeout here
  2.         //set the timeout value to socket
  3. #ifdef WIN32       
  4.                 setsockopt( m_Sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&m_nTimeOut, sizeof( int ) );
  5.                 setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );       
  6. #else
  7.                
  8.                 struct timeval tv;
  9.                 tv.tv_sec=m_nTimeOut;
  10.                 tv.tv_usec=0;
  11.                
  12.                 //A returned value of 0 indicates the system will not time out. The maximum value is 32 767 seconds.
  13.                 //Return code 0 indicates that the function was successful.A return code equal to -1 indicates an error
  14.                 setsockopt( m_Sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof( tv ) );
  15.                 setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof( tv ) );       

  16. #endif       
复制代码

作者: yuanyawei    时间: 2006-10-16 17:27
标题: 回复 11楼 醉卧水云间 的帖子
setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );  

如果到时间了确实没数据来怎么办?认为是连接断开了吗?应该不可以,那怎么办?
你试试拔网线看看?
其实重点也不在客户端,在于服务端如何处理。
作者: 醉卧水云间    时间: 2006-10-16 20:14
原帖由 yuanyawei 于 2006-10-16 17:27 发表
setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );  

如果到时间了确实没数据来怎么办?认为是连接断开了吗?应该不可以,那怎么办?
你试试拔网线看看?
其实重点也 ...


超時会错,你可以认为连接无效了。
客户端和服务端连接建立后没什么区别。
作者: langue    时间: 2006-10-16 20:21
原帖由 醉卧水云间 于 2006-10-16 20:14 发表


超時会错,你可以认为连接无效了。
客户端和服务端连接建立后没什么区别。


同意第二句话。服务器和客户端,角色有时候可以互换。木马控制端就是实际的客户。
作者: 醉卧水云间    时间: 2006-10-16 21:01
原帖由 langue 于 2006-10-16 20:21 发表


同意第二句话。服务器和客户端,角色有时候可以互换。木马控制端就是实际的客户。


第一句话呢,超時会有超時错,应该正确吧。

昨天有个美国华人说要帮我把MultiGet移植到Mac上,不过忘记问他那个介词应该用in还是用under了,下回问问再告诉你他的看法,我个人还很迷惑于这个问题。
作者: yuanyawei    时间: 2006-10-17 10:01
原帖由 nuclearweapon 于 2006-8-25 10:05 发表
基于socket tcp的c/s模型中,如果server端只有当client给出request的时候才能作出response的时候
         server端不得不面临这样的设计问题:
         如果client的是异常掉线,比如pc 掉电,拔掉网线等的类似情况,导致server一直占用一些系统资源(
         主要指由于socket引起的)直到大约2小时以后(tcp socket的SO_KEEPALIVE option)server端才会释
         放这些资源。tcp只能保证这些的。 但是,一般不会轻易修改tcp_keepalive_time = 7200(秒)(sysctl)。
         除非为了做一些试验。那么,只能在应用层建立适当的time out机制。


愿意是讨论服务端如何判断客户端拔了网线后的处理,在负荷大的服务端,这样会浪费很多服务端资源,而且这种情况在有些业务上是需要控制的,而不是简单的判断超时的作法。
作者: zsniper    时间: 2007-08-03 14:58
OOB在kqueue或者epoll里面根本不合适

因为kqueue或者epoll只关心发生变化的连接,而那种超时的连接压根没法判断,

我感觉楼主的想法不错的。。我试试。。。。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2