- 服务器关闭与客户端关闭
这个问题涉及到2个TCP连接状态,CLOSE_WAIT与TIME_WAIT。我想描述清楚这2个状态,将会对服务器关闭的生命周期了解的更为清晰。
(1)定义
假设有服务器A正在运行,接受从客户端C发送过来的连接。
在某个时间点上,C正常关闭了连接(一般正常关闭是使用Close函数),此时服务器在Recv函数上会接收到一个长度为0的消息,表示对端(C客户端)已经关闭了发送连接,意味着C不会再向A发送有意义的数据了,此时可能A还有部分数据没有发送给C(对于构建自己的网络库的时候,可能在应用层上有各种自定义的网络缓冲,其中在这里需要判断所有的发送缓冲是否都发送完毕,或者网络库定义的discard字段开启),所以A仍旧可以继续向C发送剩余的数据,在所有数据发送完毕后,再调用Close函数,关闭socket连接,那么从接收到0长度的对端关闭信息到调用Close关闭Socket的这段时间,TCP状态称为CLOSE_WAIT。同时,再调用Close函数解除CLOSE_WAIT状态后,进入到LAST_ACK状态,需要等待对端C回答这个关闭。对端C接收到了一个Close()信号上带的syn,需要返回一个ack,回答刚才说的LAST_ACK的等待。请在这里停一下。
此时大家可以想一下,首先A关闭通知C,进入到LAST_ACK状态,他在等待什么?等待C告诉他,C收到通知将会关闭所有与A的连接。同时,C收到A的Close之后,开发送这个回答,那么谁又来告诉他这个回答已经到C了呢?他是否也需要再等待C的回答,回答告诉他他的回答已经收到,于是就没完没了了,因为永远无法最后让他们同时确定对方已经收到这个信息(有点类似拜占庭将军问题)。于是我们回过头来,在理顺思路,在A调用Close,到达LAST_ACK状态之后,C会收到这个通知,他必须在关闭自己的所有与A的连接的之后,再次通知A他的关闭结束了,此时他发送了一个ACK,进入到了我们所说的TIME_WAIT状态,他期待这个ACK会到达A,让A从LAST_ACK状态到达真正的CLOSED状态。但是他不再期待A会没完没了的继续回答这个ACK了。于是他根据一个基础,从C到A的所有IP包,生命周期是MSL,比如30秒,那么从他这儿进入到TIME_WAIT状态到最后C的LAST-ACK状态说他没收到你的ACK,最多只可能有2个MSL的时间,也就是1分钟。
(2)影响
通过以上的分析,我们最后一定要总结出一句话来:“主动关闭的一方进入到TIME_WAIT状态!”。如果记住这一句话,基本上就可以分清楚TCP状态的终极奥义了。大家一定想知道,TCP这些状态,我们到底应该如何把握,让服务器先关闭还是后关闭比较好呢?下面我们再仔细分析,先后关闭服务器的优劣。
【原因1】首先,TIME_WAIT是不可避免的,这一句话,相信通过小结(1)的分析,已经确定了,根据UNP的描述,这个时间大致是1到4分钟。并且他是根据IP+PORT的对儿来确定的,假设服务器A先关闭,那么C对A的连接,再不设置别的参数的情形下,C会使用另外一个端口与服务器A进行连接,我们使用 netstat -anp | grep 服务器端口号 可以看到C连接的端口号+1,而之前的连接端口号状态为 TIME_WAIT。在CENTOS上大概1分钟就可以消失。如果大量连接到来,端口号将不够使用,同时TIME_WAIT还会占用些许资源。所以,最好的情形是让服务器不要主动关闭连接,避免进入到TIME_WAIT状态。
【原因2】TCP断开要经过4次握手,比如上述例子,如果A先关闭调用Close,则会发送一个FIN到C,C的协议栈自动返回一个ACK,然后进入CLOSE_WAIT,等待C应用层调用CLOSE关闭连接。试想,一个千锤百炼的服务端程序和多种多样的客户端程序,谁的程序更可靠?并且在当前为了可依赖的Server尽量减少Server逻辑,并且大剂量测试的情形下,我更倾向于信任Server,那么不可靠的Client,比如这里的C,由于程序员的粗心忘记调用Close了,或者某个异常逻辑跳过了Close。灾难了,服务器将会保存许许多多的CLOSE_WAIT这个本来可以不必长久存在的状态。所以更应该将服务器端保持被动关闭,因为他会更好的处理4次握手的中间那个容易被遗忘的Close函数。
(3)解决
重用time_wait echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
回收time_wait echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 在centos上,会导致客户端机器无法连接服务器,任何端口!!!