首页 > 学技术 > 技术网文 > C/C++ > 正文

[保留] 关于c/s socket中超时问题的总结


来源 chinaunix.net kuqin整理

本人看了此贴
     http://bbs.chinaunix.net/viewthread.php?tid=813588&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:36

很好的,继续研究和完善该帖子


 connet 回复于:2006-08-25 17:50:42

UNIX网络编程(W.Richard Stevens)的书讲的很清楚.
只有写才能检测到中断.
如果一端能确定某一段时间内必须有数据到达, 可由此判断中断.
楼主的  select 能检测到中断吗?

如果没有收到 FIN, select 不可能检测到什么.


 苦中作乐 回复于:2006-08-25 18:48:56

可以考虑用启动独立的探测线程发送OOB心跳~!


 nuclearweapon 回复于:2006-08-25 21:30:50

引用:原帖由 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:09

引用:原帖由 苦中作乐 于 2006-8-25 18:48 发表
可以考虑用启动独立的探测线程发送OOB心跳~! 



能不能把您设计的伪代码也写出来,一起完善这片文档呢?
谢谢!


 benlan 回复于:2006-08-25 23:42:05

1)服务器对每个连接定时去读或写操作,即使是做个空操作
2)心跳数据非常有用,能解决大部分问题


 苦中作乐 回复于:2006-08-26 00:48:28

具体例子UNP上介绍的比较清楚,前一段时间我也在考虑这个事情,基本方法也就是LZ列的几种
(1)使用KEEPALIVE
(2)使用自定义探测数据包保持连接
(3)使用OOB探测
其中最好的我觉得就是OOB技术,因为OOB可以设置不占用普通数据缓冲区,并且OOB信息不会排队,但是有两个问题
(1)OOB是通过信号SIGURG来通知应用进程的,而SIGURG信号是不安全信号,可能丢失,这样可能导致认为丢失信号的连接有问题。
(2)用多线程的话....也有问题
上面是一些简单的认识,有问题的话还忘各位指点~


 yuanyawei 回复于:2006-10-16 17:03:54

引用:原帖由 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:13

从Multiget摘点代码
连接超時

//with timeout connect
#ifdef WIN32
int CMgSocket::tconnect(SOCKET sock, const struct sockaddr *addr, unsigned int len)
#else
int CMgSocket::tconnect(int sock, const struct sockaddr *addr, unsigned int len)
#endif
{

int ret, val;
struct timeval tv;
    fd_set sset; 

#ifdef WIN32

int lon;
u_long iMode=1;
ioctlsocket(sock,FIONBIO,&iMode);
#else

int flags,old_flags;

    socklen_t lon;
    flags = old_flags = fcntl(sock, F_GETFL, 0);
flags |=O_NONBLOCK;
    if (fcntl(sock, F_SETFL, flags) == -1) {
        return -1;
    }
#endif

ret= connect(sock, addr, len);

    if(ret < 0) 
{

#ifdef WIN32

        if (WSAEWOULDBLOCK == WSAGetLastError()) 

#else

        if (errno == EINPROGRESS) 

#endif

            tv.tv_sec = m_nConnectTimeOut;
            tv.tv_usec = 0;
            FD_ZERO(&sset);
            FD_SET(sock, &sset);
            if(select(sock+1, NULL, &sset, NULL, &tv) > 0) 
{
                lon = sizeof(int);

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

#else

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

#endif


                if(val != 0)
{
                    ret= -1;
                }
else
{
ret=0;
}
            }
            else
{
                ret= -1;
            }
        }
        else 
{

            ret = -1;
        }
    }
#ifdef WIN32
iMode=0;
ioctlsocket(sock,FIONBIO,&iMode);
#else
fcntl(sock, F_SETFL, old_flags);
#endif
return ret;

}



 醉卧水云间 回复于:2006-10-16 17:14:38

读写超時:

//set timeout here
//set the timeout value to socket
#ifdef WIN32
setsockopt( m_Sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&m_nTimeOut, sizeof( int ) );
setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );
#else

struct timeval tv;
tv.tv_sec=m_nTimeOut;
tv.tv_usec=0;

//A returned value of 0 indicates the system will not time out. The maximum value is 32 767 seconds.
//Return code 0 indicates that the function was successful.A return code equal to -1 indicates an error
setsockopt( m_Sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof( tv ) );
setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof( tv ) );

#endif



 yuanyawei 回复于:2006-10-16 17:27:56

setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );  

如果到时间了确实没数据来怎么办?认为是连接断开了吗?应该不可以,那怎么办?
你试试拔网线看看?
其实重点也不在客户端,在于服务端如何处理。


 醉卧水云间 回复于:2006-10-16 20:14:55

引用:原帖由 yuanyawei 于 2006-10-16 17:27 发表
setsockopt( m_Sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&m_nTimeOut, sizeof( int ) );  

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



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


 langue 回复于:2006-10-16 20:21:12

引用:原帖由 醉卧水云间 于 2006-10-16 20:14 发表


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



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


 醉卧水云间 回复于:2006-10-16 21:01:08

引用:原帖由 langue 于 2006-10-16 20:21 发表


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



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

昨天有个美国华人说要帮我把MultiGet移植到Mac上,不过忘记问他那个介词应该用in还是用under了,下回问问再告诉你他的看法,我个人还很迷惑于这个问题。


 yuanyawei 回复于:2006-10-17 10:01:07

引用:原帖由 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机制。 



愿意是讨论服务端如何判断客户端拔了网线后的处理,在负荷大的服务端,这样会浪费很多服务端资源,而且这种情况在有些业务上是需要控制的,而不是简单的判断超时的作法。




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=817533
转载请注明作者名及原文出处



收藏本页到: