以前对socket的了解仅仅局限于listen/connect/epoll/select/close等这些API的表面使用。其具体语义以及一些状态都没有深究。总觉得这样写代码会出问题,今天咬咬牙把《tcp协议卷1》中的Tcp部分又看了一遍。发现由于对协议和API语义的了解不足,在程序中还是犯了不少错误。
TIME_WAIT状态是tcp网络编程中最不容易理解的地方。主动关闭的一方会经历TIME_WAIT状态, 这个状态会经历2MSL的时间。其目的就是为了可靠的实现TCP全双工连接的终止和允许老的重复分组消逝在网络中(具体可以参看UNP P37)。
TIME_WAIT的持续时间一般是1分钟到4分钟,如果在这段时间内涌入大量连接,然后服务器将其断开,就会导致在服务器上残留大量处理TIME_WAIT的连接,理论上这会严重拖慢系统的性能。
因此,只要条件允许,应该尽量让客户端来主动断开连接。
由于服务器绑定的是固定端口,当重启服务器时,只要还存在有通过这个固定端口接入进来的TIME_WAIT状态的连接,就会导致bind失败。因此服务器一定要为bind的socket设置SO_REUSEADDR属性。
close的默认行为首先对描述符引用计数减一,如果引用计数为为0则执行close流程。即把该socket标记为关闭, 然后立即返回到调用进程,该socket不能再被调用进程使用。然而tcp将尝试发送已经排队等待发送到对端的任何数据,发送完闭后才会执行正常的tcp连接终止序列(即四次挥手). 这个行为可以通过SO_LINGER更改。
相比close, shutdown的行为则更为粗爆一些。
不管有多少进程在持有这个socket,一旦这个socket被shutdown, 那么所有的进程均不能再对此socket进行已经shutdown过的操作。shutdown可以分别关闭读和写。
shutdown写时会将当前发送缓冲区中的数据都发送掉才会进行连接终止序列。
shutdown读时会将当前接收缓冲区中的数据都请空。
在silly的实现中,我对于close的实现仅仅是粗爆的将所有发送缓冲区清空,然后关闭socket。对比系统的close和shutdown函数可以发现, 这样做是不对的,因为在应用层调用close时,有可能数据并没有发完, 这样就会有可能导致客户端接收到的信息不完成,而造成其他bug.