TCP半关闭的必要性

TCP所提供的是全双工服务(数据可在两个方向上同时传递),连接建立后,就如同开辟了两条通路。对连接的任一端来说,一条通路用于进数据,另一条用于出数据。 一般说关闭TCP连接,是指某一端调用了close(windows平台为closesocket)函数将自己的两条通路同时断开,其结果就是,它再也不可在socket上收发数据了(TCP会将那些已在发送缓冲中的数据发送出去)。此时TCP的状况是这个样子(假设A单方面调用close):

可能的问题有两个:

一、A在调用close的时候,其应用层的缓冲区中还有一些数据没有发送(指将数据拷贝到发送缓冲,而非发送给对端)出去,这些数据在close生效之后,将不再有机会被发送。

二、当A调用close欲退出本次通信的时候,其对端B未必就想结束通信。此时的B可能正在向A发送数据,或者就算现在没发数据,在未来的某个时刻还是有可能再向A发数据的。在close生效后,这些数据就没办法再让A收到了。

对于第一点,由于是应用进程自愿发起的close过程,我们可认为它在这么做之前就已对应用缓冲中的数据进行了妥善处理。即:根据应用层业务需求,将需要发出的数据全部放入发送缓冲区后再调用close函数。既然我们相信A是这么干的,那么这个问题就不存在了。

第二点,当A发现自己不再有向外发数据的需求时,就简单粗暴的关闭掉自己的两条通路。这就相当于一个不想说话的人,在闭嘴的同时捂上了自己的耳朵。当然,这个人可能就是不想再听了也说不定。不过,这种单方面的行为总是欠妥,结束会话这事还是双方协商着来做比较好。

使用shutdown接口可以发起所谓的“半关闭”过程。即:发起方可以只关闭其指定的某一条通路,关闭后,另一条通路还是通的。若是不想再说了,就只管闭嘴,但耳朵还是要开着,听听对方的想法。若在某一时刻得知对方也不想再说了,再捂上自己的耳朵。过程如下: 这里要注意:B既可用close又可用shutdown(只可用SHUT_WR,SHUT_RDWR和SHUT_RD不会产生FIN段)来触发TCP发送FIN报文段,但是,调用shutdown后,内核不会释放掉文件描述符资源,而调用close却可以完成资源的最终释放。因此,B在这个地方直接调用close更妥当,若非要使用shutdown,则要在后面再close一次才完整。

A也应该在某个时刻将相应的socket文件描述符close掉,在得知B端发来FIN的时候(recv到0字节)去做这个工作会比较自然一些(并非必须在此处)。

“半关闭”其实是TCP层面提供的一种方式,应用进程可以选择不使用它,代价就是要在应用层多做一些工作:基于一些消息协商以确保连接双方都完成了数据收发,最后进行close就行了。

不过,在要使用close的时候还是先考虑使用shutdown吧。