有趣的TCP关闭close_wait状态和SO_REUSEADDR选项

有趣的TCP关闭close_wait状态和SO_REUSEADDR选项,第1张

最近在看TCP相关的知识,学到了很多,但是也有一些疑惑,主要就是关于TCP关闭状态中的close_wait,这个状态比较有趣,这个状态之所有存在的两个原因我这里就不再赘述了,但是这里想讲一下如果一个套接字处于close_wait状态,会有什么有趣的事情发生.

当一个套接字处于close_wait状态,在默认情况下,我们是无法再使用这个套接字对应的网卡(网卡可以是实际的也可以是虚拟的)的对应的端口

上面的话可能听起来不那么容易理解,下面我会给出一些例子.

这个例子使用的是TCP的默认设置,没有设置SO_REUSEADDR选项,服务器监听在192.168.1.100:9999,我们使用在同一局域网下的另外一台电脑连接上服务端,同时发送数据"hello server",从服务端的输出可以看到服务端确实已经和客户端建立了连接,同时收到了来自客户端的消息,这个时候我们直接杀死服务器,让服务端主动关闭连接,因为time_wait状态只有在主动关闭tcp连接的一方才会出现,然后我们使用

来查看端口情况,然后再尝试重启服务端

结果图如下:

这里可以看到,地址为127.0.0.1:9999是处于time_wait状态的,这个时候我们企图再次重启服务端,报错是bind error: Address already in use,这个倒是在我们的意料之中,因为9999端口是处于time_wait状态的,端口无法被复用属于正常.但是我们这里尝试换个ip来看看是否能够重启服务端.结果图如下:

这里的结果就比较有意思了,当我们试图绑定127.0.0.1这个ip地址的时候,我们启动服务器是成功的,而当我们试图使用localhost为ip地址来启动服务端的时候,我们得到了bind: Address already in use的错误,这个是什么原因呢.

这里我简单从个人的角度来解释一下,首先第一个问题,为什么以127.0.0.1为ip地址来启动服务端是能够成功的,我的个人理解以及查阅资料得到的结论是,端口是相对于网口而言的(或者说是相对ip地址而言的),因此127.0.0.1和192.168.1.100对应的不同的网卡,也就是说端口是互不影响的,所以我们之前启动的是192.168.1.100网卡上的9999端口,对127.0.0.1这块网卡上的9999端口毫无影响,所以我们启动服务端成功了.

第二个问题在明白了第一个问题之后就变得简单了,这里稍微比较唬人的是localhost,实际上localhost对应的地址是0.0.0.0,而0.0.0.0这个ip地址实际上是通配地址,也就是说和*是等同的,我们希望绑定服务端绑定的ip地址是0.0.0.0,也就意味着是绑定的通配地址,也就意味着对于一台计算机的所有ip地址都需要绑定,而192.168.1.100对应的网卡的9999端口处于time_wait状态,所以显然会绑定失败,导致绑定localhost这个操作失败.还是比较好理解的.

下面我们继续讲下SO_REUSEADDR选项,这个选项允许复用处于time_wait状态的地址,增加了这个选项,让我们再来试试.

很明显可以看到虽然192.168.1.100:9999是处于time_wait状态,但是我们仍然可以启动绑定各个ip的服务端,顺利启动,没有遇到任何问题.

最后让我们来测试一个有趣的问题,当一个套接字处于time_wait状态的时候,这个套接字是否已经被回收了,这个问题我们就不能直接杀死服务端了,而是需要服务端主动断开连接.

首先我们启动服务端,然后通过ps命令查看服务端进程的pid,然后查看这个进程打开的所有文件描述符,首先我们看到,前三个是标准输入,输出和标准错误输出,3对应的是监听套接字,4对应的epoll文件描述符,这个时候我们启动一个客户端去连接服务器,

这里我在客户端向服务端发送数据之后,服务端sleep5秒钟就直接关掉这个描述符,所以是服务端主动断开连接,看图可以确定在建立连接的过程中确实多占用了一个文件描述符,也就是5对应的文件描述符(因为这个时候服务端处于sleep状态,这个tcp连接仍然存在),但是当服务端sleep结束,服务端就会主动关闭这个连接,有趣的是之前连接虽然还处于time_wait状态但是套接字却已经被回收了,也就是说虽然一个套接字处于time_wait状态,但是实际上这个套接字是已经被回收的.(更严谨的证明是再使用一个新的客户端来连接服务端,保证之前那个套接字仍然处于time_wait的情况下,查看新的客户端连接对应的文件描述符,如果确实和之前那个连接的文件描述符相同,说明这个描述符的确能被复用,我在我自己电脑上测试了确实是这样的,这里就不再贴图了)

总结:个人总结,对于一个tcp服务端,在启动的时候是一定要增加SO_REUSEADDR选项的,另外time_wait状态实际上对服务端的影响并不是很大,除非是使用不当导致的问题,否则因为实际上处于time_wait的套接字是已经被回收了,所以起码并不会占用进程的文件描述符数量而只是占用少量的资源,除非是特别高并发的情况,但是这种情况下与其不断优化不如考虑增加机器(笑).

1.

客户端发送完数据

发送一个fin

告诉服务器发送完了,等待关闭

2.

服务器收到

fin后知道客户端已经发送完数据

应答ack

3.

服务器数据发送完毕要关闭链接

发送fin

4.

客户端收到fin

知道服务器发送完毕

回复ack

客户端关闭

5.

服务器收到ack

服务器关闭

如果没有收到ack命令或者其他数据

计时器超时会自动中断连接

客户端发送完数据 发送一个Fin 告诉服务器发送完了,等待关闭

服务器收到 Fin后知道客户端已经发送完数据 应答ACK

服务器数据发送完毕要关闭链接 发送Fin

客户端收到Fin 知道服务器发送完毕 回复ACK  客户端关闭

服务器收到ACK 服务器关闭

  如果没有收到ACK命令或者其他数据  计时器超时会自动中断连接


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/305802.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-27
下一篇2023-04-27

发表评论

登录后才能评论

评论列表(0条)

    保存