setsockopt :SO_LINGER 选项设置


有三种组合方式:
from:http://blog.csdn.net/yyyzlf/article/details/5316926

TCP连接断开的时候调用closesocket函数,已经讨论过有优雅的断开和强制断开,那么如何设置断开连接的方式呢?是通过设置socket描述符一个linger结构体属性。

linger结构体数据结构如下:

struct linger

{


     int l_onoff;

     int l_linger;

};

有三种组合方式:

第一种

    l_onoff = 0;

    l_linger忽略

    这种方式下,就是在closesocket的时候立刻返回,底层会将未发送完的数据发送完成后再释放资源,也就

是优雅的退出。


第二种

    l_onoff非零

    l_linger = 0;

    这种方式下,在调用closesocket的时候同样会立刻返回,但不会发送未发送完成的数据,而是通过一个REST包强制的关闭socket描述符,也就是强制的退出。


第三种

    l_onoff非零

    l_linger > 0

    这种方式下,在调用closesocket的时候不会立刻返回,内核会延迟一段时间,这个时间就由l_linger得值来决定。如果超时时间到达之前,发送完未发送的数据(包括FIN包)并得到另一端的确认,closesocket会返回正确,socket描述符优雅性退出。否则,closesocket会直接返回错误值,未发送数据丢失,socket描述符被强制性退出。需要注意的时,如果socket描述符被设置为非堵塞型,则closesocket会直接返回值。



setsockopt 设置 SO_LINGER 选项

 from:http://blog.csdn.net/factor2000/article/details/3929816

   此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。

 

SO_LINGER选项用来改变此缺省设置。使用如下结构:

struct linger {

     int l_onoff; /* 0 = off, nozero = on */

     int l_linger; /* linger time */

};

 

有下列三种情况:

1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;

 

2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;

 

3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。

 

注释:l_linger的单位依赖于实现: 4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。

 

下面的代码是一个使用SO_LINGER选项的例子,使用30秒的超时时限:
#define TRUE     1
#define FALSE    0
int z; /* Status code
*/ int s;       /* Socket s */
struct linger so_linger;
...
so_linger.l_onoff = TRUE;
so_linger.l_linger = 30;
z = setsockopt(s,
    SOL_SOCKET,
    SO_LINGER,
    &so_linger,
    sizeof so_linger);
if ( z )
   perror("setsockopt(2)");

下面的例子显示了如何设置SO_LINGER的值来中止套接口s上的当前连接:
#define TRUE     1
#define FALSE    0
int z; /* Status code */
int s;       /* Socket s */
struct linger so_linger;
...
so_linger.l_onoff = TRUE;
so_linger.l_linger = 0;
z = setsockopt(s,
    SOL_SOCKET,
    SO_LINGER,
    &so_linger,
    sizeof so_linger);
if ( z )
    perror("setsockopt(2)");
    close(s); /* Abort connection */

在上面的这个例子中,当调用close函数时,套接口s会立即中止。中止的语义是通过将超时值设置为0来实现的。

 

 

 

 

/********** WINDOWS **********/

 

 

/* 当连接中断时,需要延迟关闭(linger)以保证所有数据都被传输,所以需要打开SO_LINGER这个选项;  
 * //注:大致意思就是说SO_LINGER选项用来设置当调用closesocket时是否马上关闭socket; 
 * linger的结构在/usr/include/linux/socket.h中定义://注:这个结构就是SetSocketOpt中的Data的数据结构 
 *  struct linger 
 *  { 
 *   int l_onoff;  /* Linger active */       //低字节,0和非0,用来表示是否延时关闭socket
 *   int l_linger; /* How long to linger */   //高字节,延时的时间数,单位为秒
 *  }; 
 *  如果l_onoff为0,则延迟关闭特性就被取消。

 *   如果非零,则允许套接口延迟关闭; l_linger字段则指明延迟关闭的时间 
 */


更具体的描述如下:
1、若设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零),并设置了零超时间隔,则closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为“强制”或“失效”关闭,因为套接口的虚电路立即被复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。

2、若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅”或“从容”关闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。

3、若在一个流类套接口上设置了SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零),则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。
 

SO_DONTLINGER 若为真,则SO_LINGER选项被禁止。
SO_LINGER延迟关闭连接 struct linger上面这两个选项影响close行为;


     选项          间隔    关闭方式  等待关闭与否
  SO_DONTLINGER   不关心     优雅         否
  SO_LINGER        零        强制         否
  SO_LINGER       非零       优雅         是


SOCKET CLOSE_WAIT状态的说明

from: http://blog.csdn.net/factor2000/article/details/3929197
CLOSE_WAIT出现的原因: 就是某一方在网络连接断开后,对等方没有检测到这个错误(对方断开)而没有调用 closesocket,导致了这个状态的出现;
 
断开连接的时候: 
      当发起主动关闭的左边这方发送一个FIN过去后,右边被动关闭的这方要回应一个ACK,这个ACK是TCP回应的(同时TCP向上层应用程序提交一个ERROR,导致上面的SOCKET的send或者recv返回SOCKET_ERROR),而不是应用程序发送的,此时,被动关闭的一方就处于CLOSE_WAIT状态了。如果此时被动关闭的这一方不再继续调用closesocket,那么他就不会发送接下来的FIN,导致自己老是处于CLOSE_WAIT。只有被动关闭的这一方调用了closesocket,才会发送一个FIN给主动关闭的这一方,同时也使得自己的状态变迁为LAST_ACK,待接收到主动关闭方发送的ACK后,才会将SOCKET置为CLOSED。
 
[cpp]  view plain copy
  1. int nRet = recv(sockConnected, szRecvBuffer,sizeof(szRecvBuffer),0);   
  2. ///   
  3. /// 当对方调用closesocket的时候,我的程序正在recv,  
  4. /// 这时候有可能对方发送的FIN包我没有收到,而是由TCP代回了一个ACK包,  
  5. /// 所以我这边程序进入CLOSE_WAIT状态。   
  6. /// 所以建议在这里判断是否已出错,是就主动closesocket。   
  7. /// 因为前面已经设置了recv超时时间为30秒,那么如果真的是超时了,   
  8. /// 这里收到的错误应该是WSAETIMEDOUT,这种情况下也可以关闭连接的   
  9. if (nRet == SOCKET_ERROR)   
  10. {   
  11.    TRACE_INFO(_T("=用recv接收发生Socket错误="));   
  12.    closesocket(sockConnected);   
  13.    return FALSE;  
  14. }   
   
检测到SOCKET_ORROR 则主动调用closesocket() 关闭套接字; 
***************************************************************
首先我们知道,如果我们的 Client 程序处于 CLOSE_WAIT 状态的话,说明套接字是被动关闭的!
因为如果是 Server 端主动断掉当前连接的话,那么双方关闭这个 TCP 连接共需要四个 packet
       Server ---> FIN ---> Client
       Server <--- ACK <--- Client
     时候 Server 端处于 FIN_WAIT_2 状态;而我们的程序处于 CLOSE_WAIT 状态。
       Server <--- FIN <--- Client
Client 发送 FIN Server Client 就置为 LAST_ACK 态。
         Server ---> ACK ---> Client
Server 回应了 ACK ,那么 Client 的套接字才会真正置为 CLOSED 状态。

我们的程序处于 CLOSE_WAIT 状态,而不是 LAST_ACK ,说明还没有发 FIN Server ,那么可能是在关闭连接之前还有许多数据要发送或者其他事要做,导致没有发这个 FIN packet
原因知道了,那么为什么不发 FIN 包呢,难道会在关闭己方连接前有那么多事情要做吗?
还有一个问题,为什么有数千个连接都处于这个状态呢?难道那段时间内,服务器端总是主动拆除我们的连接吗?
不管怎么样,我们必须防止类似情况再度发生!
首先,我们要防止不断开辟新的 端口 ,这可以通过设置 SO_REUSEADDR 套接字选项做到:
重用本地地址和端口
以前我总是一个端口不行,就换一个新的使用,所以导致让数千个端口进入 CLOSE_WAIT 状态。如果下次还发生这种尴尬状况,我希望加一个限定,只是当前这个端口处于 CLOSE_WAIT 状态!
在调用
sockConnected = socket(AF_INET, SOCK_STREAM, 0);
之后,我们要设置该套接字的选项来重用:
///  允许重用本地地址和端口:
///  这样的好处是,即使socket断了,调用前面的socket函数也不会占用另一个,而是始终就是一个端口
///  这样防止socket始终连接不上,那么按照原来的做法,会不断地换端口。
int  nREUSEADDR = 1;
setsockopt(sockConnected,
              SOL_SOCKET,
              SO_REUSEADDR,
              (const char*)&nREUSEADDR,
              sizeof(int));
教科书上是这么说的:这样,假如服务器关闭或者退出,造成本地地址和端口都处于 TIME_WAIT 状态,那么 SO_REUSEADDR 就显得非常有用。
也许我们无法避免被冻结在 CLOSE_WAIT 状态永远不出现,但起码可以保证不会占用新的端口。
其次,我们要设置 SO_LINGER 套接字选项:(相关介绍可参考:SO_LINGER 选项设置)
从容关闭还是强行关闭?
LINGER 是“拖延”的意思。
默认情况下 (Win2k) SO_DONTLINGER 套接字选项的是 1 SO_LINGER 选项是, linger {l_onoff 0 l_linger 0}
如果在发送数据的过程中 (send() 没有完成,还有数据没发送 ) 而调用了 closesocket() ,以前我们一般采取的措施是“从容关闭”:
因为在退出服务或者每次重新建立 socket 之前,我都会先调用
///  先将双向的通讯关闭
      shutdown(sockConnected, SD_BOTH);
     ///  安全起见,每次建立Socket连接前,先把这个旧连接关闭
closesocket(sockConnected);
我们这次要这么做:
设置 SO_LINGER 为零(亦即 linger 结构中的 l_onoff 域设为非零,但 l_linger 0 ,便不用担心 closesocket 调用进入“锁定”状态(等待完成),不论是否有排队数据未发送或未被确认。这种关闭方式称为“强行关闭”,因为套接字的虚电路立即被复位,尚未发出的所有数据都会丢失。在远端的 recv() 调用都会失败,并返回 WSAECONNRESET 错误。
connect 成功建立连接之后设置该选项:
linger m_sLinger;
m_sLinger.l_onoff = 1; // ( 在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
m_sLinger.l_linger = 0; // ( 容许逗留的时间为0秒)
setsockopt(sockConnected,
         SOL_SOCKET,
         SO_LINGER,
         (const char*)&m_sLinger,
         sizeof(linger));
总结
也许我们避免不了 CLOSE_WAIT 状态冻结的再次出现,但我们会使影响降到最小,希望那个重用套接字选项能够使得下一次重新建立连接时可以把 CLOSE_WAIT 状态踢掉。

你可能感兴趣的:(setsockopt :SO_LINGER 选项设置)