先给出四次挥手过程中c/s的状态变化示意图。有了图理解起来就容易许多。
time_wait 表示主动关闭,close_wait 表示被动关闭。
close_wait状态的生成原因
首先我们知道,如果ag真人游戏的服务器程序apache处于close_wait状态的话,说明套接字是被动关闭的!
因为如果是client端主动断掉当前连接的话,那么双方关闭这个tcp连接共需要四个packet:
client ---> fin ---> server
client <--- ack <--- server
这时候client端处于fin_wait_2状态;而server 程序处于close_wait状态。
client <--- fin <--- server
这时server 发送fin给client,server 就置为last_ack状态。
client ---> ack ---> server
client回应了ack,那么server 的套接字才会真正置为closed状态。
server 程序处于close_wait状态,而不是last_ack状态,说明还没有发fin给client,那么可能是在关闭连接之前还有许多数据要发送或者其他事要做,导致没有发这个fin packet。
客户端主动关闭时,发出fin包,收到服务器的ack,客户端停留在fin_wait2状态。而服务端收到fin,发出ack后,停留在colse_wait状态。 这个close_wait状态非常讨厌,它持续的时间非常长,服务器端如果积攒大量的colse_wait状态的socket,有可能将服务器资源(套接字描述符耗尽)耗尽,进而无法提供服务。 那么,服务器上是怎么产生大量的失去控制的colse_wait状态的socket呢?
我们来追踪一下。 一个很浅显的原因是,服务器没有继续发fin包给客户端。
服务器为什么不发fin,可能是业务实现上的需要,现在不是发送fin的时机,因为服务器还有数据要发往客户端,发送完了自然就要通过系统调用发fin了,这个场景并不是上面我们提到的持续的colse_wait状态,这个在受控范围之内。
那么究竟是什么原因呢,咱们引入两个系统调用close(sockfd)和shutdown(sockfd,how)接着往下分析。
在这儿,需要明确的一个概念---- 一个进程打开一个socket,然后此进程再派生子进程的时候,此socket的sockfd会被继承。
socket是系统级的对象,现在的结果是,此socket被两个进程打开,此socket的引用计数会变成2。
继续说上述两个系统调用对socket的关闭情况。 调用close(sockfd)时,内核检查此fd对应的socket上的引用计数。如果引用计数大于1,那么将这个引用计数减1,然后返回。如果引用计数等于1,那么内核会真正通过发fin来关闭tcp连接。
调用shutdown(sockfd,shut_rdwr)时,内核不会检查此fd对应的socket上的引用计数,直接通过发fin来关闭tcp连接。
现在应该真相大白了,可能是服务器的实现有点问题,父进程打开了socket,然后用派生子进程来处理业务,父进程继续对网络请求进行监听,永远不会终止。客户端发fin过来的时候,处理业务的子进程的read返回0,子进程发现对端已经关闭了,直接调用close()对本端进行关闭。
实际上,仅仅使socket的引用计数减1,socket并没关闭。从而导致系统中又多了一个close_wait的socket。。。 如何避免这样的情况发生? 子进程的关闭处理应该是这样的: shutdown(sockfd, shut_rdwr); close(sockfd);
这样处理,服务器的fin会被发出,socket进入last_ack状态,等待最后的ack到来,就能进入初始状态closed 最后加上点干货(从百度百科上看到关于fin_wait_1和fin_wait_2的理解。)
其实fin_wait_1和fin_wait_2状态的真正含义都是表示等待对方的fin报文。
而这两种状态的区别是:fin_wait_1状态实际上是当socket在established状态时,它想主动关闭连接,向对方发送了fin报文,此时该socket即进入到fin_wait_1状态。
而当对方回应ack报文后,则进入到fin_wait_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ack报文,所以fin_wait_1状态一般是比较难见到的,而fin_wait_2状态还有时常常可以用netstat看到。