由于涉及面太广,只作简单整理,有兴趣的可参考《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是不同的,有兴趣的可以试试。。