TCP连接关闭总结

由于涉及面太广,只作简单整理,有兴趣的可参考《UNIX Networking Programming》volum 1, Section 5.7, 5.12, 5.14, 5.15, 6.6 以及7.5 SO_LINGER选项。

以一个简单的echo服务器为例,客户端从标准输入读入字符,发送给服务器,服务器收到后再原样返回,客户端收到后打印到标准输出。

那么,关于套接字的关闭有以下几种情形:

1,客户端关闭连接:

1.1,客户端调用close()

1.2,客户端进程关闭

1.3,客户端调用shutdown()

1.4,客户端调用close()+SO_LINGER选项

1.5,客户端崩溃

 

2,服务器关闭连接:

2.1,服务器调用close()

2.2,服务器进程关闭

2.3,服务器崩溃

2.4,服务器崩溃+SO_KEEPALIVE选项

========================================分割线=========================================

1.1与1.2等价,就算客户端进程关闭,系统内核也会自动close(socket),且注意,当socket引用为0时才会真正调用close(),close()总是立即返回的,然后由系统尝试发送完内核缓冲区内的所有数据,接着才发送FIN。

说道这里,不得不谈谈TCP连接关闭的四次握手。可以看成是2组FIN, ACK。主动关闭的一方先发送FIN,收到ACK后,进入FIN_WAIT2状态,此时也叫做“半关闭”状态,特别须要注意的是,此时客户端套接字依然可以接收数据包,但是不能发送数据包。 被动关闭的一方,此时收到FIN了,一般情况下都是由于read(socket)返回0,然后得知对方关闭,close(socket)后,另外一组FIN,ACK随之产生,此时主动方进入TIME_WAIT状态。即四次握手完成。

以上即是正常情况下连接关闭的情形。

再看看1.3,shutdown()与close()主要有3点区别:

shutdown()不理会引用计数与内核缓冲区内剩余待发数据包,直接发送FIN;

shutdown()可以只关闭套接字某个方向的连接,例如关闭发送,关闭接收,或者2者都关闭;

实际上shutdown(write)后,就是上面说的半关闭情形,依然可以完成四次握手。

再看看1.4,为什么要设置SO_LINGER呢

SO_LINGER的目的就是改变close()的默认行为,可以决定close()在哪个状态返回,或者让套接字立即发送RST,从而没有FIN的发送。接收方返回ECONNRESET错误,连接直接关闭。

再来总结下1.1-1.4,这么多关闭连接的方式,那么什么方式才是最好的呢?

择优选择的方式当然是考虑最恶劣的情况,对方主机崩溃或网络故障导致数据包传输停滞。

RST不用考虑了,直接TIME_WAIT状态都没,如果有网络故障,可能下次创建的套接字还会接收到已经被销毁的套接字的数据报。

close()不能保证对方一定收到FIN。

close()+SO_LINGER虽然能控制close()在收到ACK后返回,依然不能保证四次握手完成。

shutdown()先进入半关闭状态,再调用read(),返回0(收到对方FIN)则说明四次握手正常进行,此为最优方式。

其实仔细想想,一般情况也不用这么麻烦,拿网游服务器来说,客户端close()后,就算服务器不知道,那么这种情况归为1.5讨论;如果是服务端close()而客户端不知道,那么归为2.3讨论。总之都有解决办法。。

现在再讨论1.5,很简单,服务端加入链路异常检测机制即可,这也是所有大型TCP服务器必备的机制,定时发送小数据包检测客户端是否有异常退出。

========================================分割线=========================================

服务器关闭连接方面:

2.1,2.2等价,一般情况下也与1.1,1.2等价,只是主动关闭方是服务器了。

但是,在我们讨论的例子里,客户端要从标准输入读字符,这是阻塞方式,服务端关闭连接后,客户端无法知道,因为它阻塞在标准输入了,当我们再次输入字符,并发送,收到FIN或RST,此时客户端才关闭。总之,客户端由于某种原因,不能及时调用read(),所以无法得知服务器关闭了连接。

2.3,服务器崩溃,客户端由于一直收不到ACK,会一直尝试发送数据,标准socket大概是9分钟后才会返回错误。

2.3,服务器崩溃,客户端又长时间与服务器没有数据交互,此时设置SO_KEEPALIVE选项可得知。

========================================分割线=========================================

后记:网络是门复杂的学问,由此TCP连接的关闭可见一斑。普通程序员通常不会考虑这么细致,但是我相信这些问题一直困扰着他们。

 

补充说明:经试验,在Windows平台,1.2  2.2情况等同于close()+SO_LINGER选项直接发送RST,可能由于系统必须及时清理资源吧,这点与linux是不同的,有兴趣的可以试试。。

你可能感兴趣的:(Network)