Tcp 断开连接

痛定思痛。 2022-09-17 12:22 311阅读 0赞

TCP状态转移要点
TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不 会被释放。网络服务器程序要同时管理大量连接,所以很有必要保证无用连接完全断开,否则大量僵死的连接会浪费许多服务器资源。在众多TCP状态中,最值得 注意的状态有两个:CLOSE_WAIT和TIME_WAIT。

1、LISTENING状态
  FTP服务启动后首先处于侦听(LISTENING)状态。

2、ESTABLISHED状态
  ESTABLISHED的意思是建立连接。表示两台机器正在通信。

3、CLOSE_WAIT

  1. 对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE\_WAIT 此时我方要调用close()来使得连接正确关闭

4、TIME_WAIT

  1. 我方主动调用close()断开连接,收到对方确认后状态变为TIME\_WAITTCP协议规定TIME\_WAIT状态会一直持续2MSL(即两倍的分 段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME\_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情 况下,尽量不要主动断开连接,以减少TIME\_WAIT状态造成的资源浪费。
  2. 目前有一种避免TIME\_WAIT资源浪费的方法,就是关闭socketLINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作 可能会带来错误。

在连接撤销过程中,有如下四个过程:

o_TCPDisconnectState.PNG

  1. HOST1上的应用程序关闭己方的连接导致TCP发送一个FIN消息给HOST2。
  2. HOST2发送一个确认消息给HOST1,并且HOST2把FIN作为EOF递交给HOST2上的应用程序。
  3. 一段时间过后,HOST2上的应用程序关闭它那边的连接,引发一个FIN消息给HOST1。
  4. HOST1给HOST2发送一个确认消息,然后HOST2关闭连接并释放资源,然而,HOST1却没有关闭连接,而是进入了TIME_WAIT状态,并为 两个最大段生存时间(2MSL)保留在此状态.

为什么需要time_wait?

  1. 因为在第四步的时候,HOST1发送的ACK可能丢失并导致HOST2重新发送FIN消息,TIME_WAIT维护连接状态.
    如果执行主动关闭的一方HOST1 不进入到TIME_WAIT状态就关闭连接那会发生什么呢?当重传的FIN消息到达时,因为TCP已经不再有连接的信息了,所以就用RST(重新启动)消 息应答,导致HOST2进入错误的状态而不是有序终止状态,如果发送最后ACK消息的一方处于TIME_WAIT状态并仍然记录着连接的信息,它就可以正 确的响应对等方HOST2的FIN消息了.
  2. TIME_WAIT为连接中”离群的段”提供从网络中消失的时间.
    考虑一下,如果延迟或者重传段在连接关闭后到达时会发生什么呢?通常情况下,因为TCP仅仅丢弃该数据并响应RST消息,所以这不会造成任何问题。当 RST消息到达发出延时段的主机时,因为该主机也没有记录连接的任何信息,所以它也丢弃该段。然而,如果两个相同主机之间又建立了一个具有相同端口号的新 连接,那么离群的段就可能被看成是新连接的,如果离群的段中数据的任何序列号恰恰在新连接的当前接收窗口中,数据就会被重新接收,其结果就是破坏新连接。

一、TCP连接关闭的几种方式:

1、“正常”关闭:调用close()关闭socket、没close但进程正常结束(当然这是不应该的做法)、进程core掉、在shell 命令行中kill掉进程,都可抽象成“正常”关闭。因为即使core掉,内核也会马上帮应用程序回收(close)socket文件描述符。

  1. “正常”关闭,默认情况下(非默认即设置Linger下面会介绍),关闭端即客户端TCP层会发FIN包,对端即服务器TCP层收到后,回ACK,客户端 进入FIN\_WAIT2状态。此时,TCP终止连接的4个分组中服务器应该发的第3个分组FIN包,其TCP层是不会主动发的,只有服务器端socket “正常”关闭,才会发出这个FIN包。至此,客户端进入TIME\_WAIT状态。

2、“非”正常关闭:客户端崩溃了,此时肯定发不出FIN包了(当然啦,内核都没机会帮应用程序回收资源了)。这种情况,服务器端有如下两种情 况:

  1. A、服务器send数据,因为客户端已经崩溃,服务器收不到ACK自然会不停的重传。源自
  2. Berkeley的重传机制,重传8次,相对第一次传的15分钟后仍没收到ACK,则返回
  3. ETIMEDOUTEHOSTUNREAC错误。如果服务器不理会这个错误,再次调用send,则
  4. 立马返回Broken Pipe错误。
  5. 注:15分钟超时可以在 /proc/sys/net/ipv4/tcp\_retries2 中修改

B、 服务器不发任何数据了,那只有靠应用层心跳检测机制或Keepalive,来发觉TCP断连了。

二、SO_LINGER套接口选项

  1. Al\_onoff设置为0,这也是默认情况,函数close()是立即返回的,然后TCP连接双方是通过
  2. FINACK4分组来终止TCP连接的。当然,发送缓冲区还有数据的话,系统将试着将这些数据
  3. 发送到对方。
  4. Bl\_onoff0l\_linger设置0,函数close()立即返回,并发送RST终止连接,发送缓冲区的数据丢弃。
  5. Cl\_onoff0l\_linger0,函数close()不立即返回,而是在(a)发送缓冲区数据发送完并得到确认
  6. (b)l\_linger延迟时间到,l\_linger时间单位为微妙。两者之一成立时返回。如果在发送缓冲区数据发送
  7. 完并被确认前延迟时间到的话,close返回EWOULDBLOCK(或EAGAIN)错误。

三、客户端TCP连接“正常”关闭,服务器的几种情况:

  1. 情形 客户端l\_onoff设置为0

“正常”关闭 客户端l_onoff非0,l_linger设置0,“正常”关闭
服务器阻塞模式send,正阻塞在send函数未返回 客户端TCP发送FIN,服务器send函数返回成功(返回字节数是实际拷贝到发送缓冲区的字节数)。客户端发送RST。如果服务器再次调用send,将 返回errno[32]:Broken pipe 客户端TCP发送RST,服务器函数返回成功(返回字节数是实际拷贝到发送缓冲区的字节数)。若服务器再次调用send,则返回 -1,errno[104]:Connection reset by peer。若再次调用send,则返回-1,errno[32]:Broken pipe
服务器空闲 客户端TCP发送FIN,若服务器没理会而调用send,客户端发送RST,send返回-1,errno[32]:Broken pipe 客户端TCP发送RST,若服务器没理会而调用send,send返回-1,errno[104]:Connection reset by peer。若再次调用send,则返回-1,errno[32]:Broken pipe

总之,1、收到对端RST后,仍然调入send(),则返回Connection reset by peer,再次调用send(),则返回Broken pipe

  1. 2、收到对端FIN后,仍然调研哪个send(),直接返回Broken pipe

setsockopt 设置 SO_LINGER 选项

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

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

struct linger {

  1. int l\_onoff; /\* 0 = off, nozero = on \*/
  2. 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行为;

  1. 选项 间隔 关闭方式 等待关闭与否

SO_DONTLINGER 不关心 优雅 否
SO_LINGER 零 强制 否
SO_LINGER 非零 优雅 是

发表评论

表情:
评论列表 (有 0 条评论,311人围观)

还没有评论,来说两句吧...

相关阅读

    相关 tcp断开连接四次挥手

    tcp断开连接是经历了四次挥手的过程 1:客户端主动断开连接向服务端发送:①请求标志位(FIN),②连接序号(seq) 2:服务端收到请求后向客户端发送:①确认请求标志位(

    相关 Tcp 断开连接

    TCP状态转移要点      TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源