TCP连接的释放(8)

一 TCP连接的释放过程:


 

二 底层细节分析

1 so_linger的作用

        通常close方法是立即返回的,so_linger用来保证对方收到了close时发出的消息才返回close调用,即,至少需要对方通过发送ACK且到达本机,close调用才能返回,这常用于需要高可靠性关闭连接的应用,也就是说,★必须确保发出的消息、FIN都被对方收到.

        例如,有些响应发出后调用close关闭连接,接下来就会关闭进程。如果close时发出的消息其实丢失在网络中了,那么,进程突然退出时连接上发出的RST就可能被对方收到,而且,之前丢失的消息不会有重发来保障可靠性了

        注意,务必慎用so_linger,它会在不经意间降低你程序中代码的执行速度(close的阻塞).

        这里需要注意,so_linger不是确保连接被四次握手关闭再使close返回,而只是保证我方发出的消息都已被对方收到。例如,若对方程序写的有问题,当它收到FIN进入CLOSE_WAIT状态,却一直不调用close发出FIN,此时,对方仍然会通过ACK确认,我方收到了ACK进入FIN_WAIT2状态,但没收到对方的FIN,我方的close调用却不会再阻塞,close直接返回,控制权交还用户进程。

 

2 关闭监听句柄(对于监听socket执行关闭,和对处于ESTABLISH这种通讯的socket执行关闭,有何区别?)

        用于listen的监听句柄也是使用close关闭,关闭这样的句柄含义当然很不同,它本身并不对应着某个TCP连接,但是,附着在它之上的却可能有半成品连接。什么意思呢?TCP是双工的,它的打开需要三次握手,三次握手也就是3个步骤,其含义为:客户端打开接收、发送的功能;服务器端认可并也打开接收、发送的功能;客户端认可。当第1、2步骤完成、第3步步骤未完成时,就会在服务器上有许多半连接,close这个操作主要是清理这些连接。

        close首先会移除keepalive定时器。keepalive功能常用于服务器上,防止僵死、异常退出的客户端占用服务器连接资源。移除此定时器后,若ESTABLISH状态的TCP连接在tcp_keepalive_time时间(如服务器上常配置为2小时)内没有通讯,服务器就会主动关闭连接。

        接下来,关闭每一个半连接。如何关闭半连接?这时当然不能发FIN包,即正常的四次握手关闭连接,而是会发送RST复位标志去关闭请求。处理完所有半打开的连接close的任务就基本完成了。

 

3 关闭普通ESTABLISH状态的连接(未设置so_linger)

        首先检查是否有接收到却未处理的消息。

        如果close调用时存在收到远端的、没有处理的消息,这时根据close这一行为的意义,是要丢弃这些消息的。但丢弃消息后,意味着连接远端误以为发出的消息已经被本机收到处理了(因为ACK包确认过了),但实际上确是收到未处理,此时也不能使用正常的四次握手关闭,而是会向远端发送一个RST非正常复位关闭连接。这个做法的依据请参考draft-ietf-tcpimpl-prob-03.txt文档3.10节,Failure to RST on close with data pending。所以,这也要求我们程序员在关闭连接时,要确保已经接收、处理了连接上的消息。

        如果此时没有未处理的消息,那么进入发送FIN来关闭连接的阶段。

        这时,先看看是否有待发送的消息。前一篇已经说过,发消息时要计算滑动窗口、拥塞窗口、nagle算法等,这些因素可能导致消息会延迟发送的。如果有待发送的消息,那么要尽力保证这些消息都发出去的。所以,会在最后一个报文中加入FIN标志,同时,关闭用于减少网络中小报文的nagle算法,向连接对端发送消息。如果没有待发送的消息,则构造一个报文,仅含有FIN标志位,发送出去关闭连接。

 

4 最后做个总结。调用close时,可能导致发送RST复位关闭连接,例如有未读消息、打开so_linger但so_linger却为0、关闭监听句柄时半打开的连接。更多时会导致发FIN来四次握手关闭连接,但打开so_linger可能导致close阻塞住等待着对方的ACK表明收到了消息。

 

5 shutdown

1)shutdown可携带一个参数,取值有3个,分别意味着:只关闭读、只关闭写、同时关闭读写。

对于监听句柄,如果参数为关闭写,显然没有任何意义。但关闭读从某方面来说是有意义的,例如不再接受新的连接。看看最右边蓝色分支,针对监听句柄,若参数为关闭写,则不做任何事;若为关闭读,则把端口上的半打开连接使用RST关闭,与close如出一辙。

2)若shutdown的是半打开的连接,则发出RST来关闭连接。

3)若shutdown的是正常连接,那么关闭读其实与对端是没有关系的。只要本机把接收掉的消息丢掉,其实就等价于关闭读了,并不一定非要对端关闭写的。实际上,shutdown正是这么干的。若参数中的标志位含有关闭读,只是标识下,当我们调用read等方法时这个标识就起作用了,会使进程读不到任何数据。

4)若参数中有标志位为关闭写,那么下面做的事与close是一致的:发出FIN包,告诉对方,本机不会再发消息了。

 

 

三 Java API分析

1 关闭连接的API:

(1)Socket.close()将同时终止输入和输出数据流

(2)shutdownInput()和shutdownOutput()可以分别关闭相应流

(3)在Socket通信一端关闭输出流,可以达到通知另一端数据发送停止的效果,另一端的输入流上的读取尾部数据的操作将返回-1

 

2 关闭机制分析:

(1)应用程序通过调用连接套接字的close()方法或shutdownOutput()方法表明数据已经发送完毕。此刻,底层的TCP实现首先将留存在SendQ队列中的数据传输出去(还要依赖于另一端RecvQ队列的剩余空间),然后向另一端发送一个关闭TCP连接的握手消息。该关闭握手消息可以看作是流终止标志:它告诉接收端TCP不会再有新的数据传入RecvQ队列了。(注意,关闭握手消息本身并没有传递给接收端应用程序,而是通过read()方法返回-1来指示其在字节流中的位置。)

 

(2)正在关闭的TCP将等待其关闭握手消息的确认信息,该确认信息表明在连接上传输的所有数据已经安全地传输到了RecvQ中。只要收到了确认消息,该连接就变成"半关闭(Half closed)"状态。直到连接的另一个方向上收到了对称的握手消息后,连接才完全关闭--也就是说,连接的两端都表明它们再没有数据要发送了。

 

(3) 要注意的是,close()方法或shutdownOutput()方法是立即返回的,虽然Tcp底层还在为关闭执行相关协议操作

 

3 关闭TCP连接时的通知机制

    close方法会关闭输入流和输出流,关闭输出流会通知连接的另一端,关闭输入流应该是不通知另一端的,当发送方发送数据过来后,数据仍然会被确认,但是不再放入接收缓冲区,而是直接丢弃,没有任何提醒.

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值