Windows和Linux上socket的终止问题(time_wait)
在编写远控工具DanceShell的shell连接部分,功能需求是当本次连接终止时,自动启动socket监听,依然是在此前的IP和端口进行监听,并且在监听前关闭socket连接:
1 | clientSocket.shutdown(2) |
此时在Windows上运行并断开重复监听时是成功的:
但是同样的项目代码,在Linux就会报错:
抛出来这样的错误:
1 | Traceback (most recent call last): |
报错显示地址已经被使用,无法再次进行socket bind()绑定。
起初以为是仅仅终止了clientSocket而没有结束(关闭)serverSocket,于是在关闭clientSocket后又加入一行关闭serverSocket的代码:
1 | clientSocket.shutdown(2) |
Windows上同样可以,但是Linux上运行,关闭再次监听依然会报错。
当我以为仅仅是Windows和Linux不同操作系统上的socket存在差异时,查询资料得知,Windows和Linux上第一次关闭socket连接并不会立即释放地址和端口,也就是不会立即释放该socket(ip:port),而是处于time_wait的一个状态
关于该状态的含义,书籍《TCP/IP详解卷》中给出的解释是,由于TCP连接存在一个最大段生存期MSL(Maximum Segment Lifetime),它代表任何报文段在被丢弃前在网络中被允许存在的最大时长,而当连接断开时,为了防止可能存在于网络中的的数据包,在重新建立新连接后被当作新连接的数据报文,所以会设定一个time_wait,也称之为2MSL,即时长是MSL的2倍。所以在这2MSL的time_wait时间内,该地址端口不允许再次绑定使用。
Windows上time_wait的时间是30-300s,不同的版本有不同的默认值,所以不能够立即再次绑定此前的端口(socket),当遇到以下两种情况时,才会完全释放:
- 1、程序结束
- 2、time_wait结束
但是,由于在实际应用中,服务端同一服务所监听的端口通常是固定的,比如80、443等,如果服务端终止了已经建立连接的服务器进程,此时需要立刻恢复监听,而TCP处于time_wait状态,无法立刻再次监听相同端口,需要等待2MSL,这会大大降低服务器的效率,影响正常的服务。所以针对此问题,之后又提出了socket地址复用(SO_REUSEADDR
)。
SO_REUSEADDR
地址复用就是为了绕过time_wait这个2MSL等待时间而设定的选项,所以它的作用范围仅仅是出于time_wait状态的socket。在bind()之前使用setsockopt()函数来设定socket参数,Python示例如下:
1 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
在服务器端加入该代码,代码块部分:
1 | def connect(host, port): |
此时再次在Linux上进行测试,成功监听
除了SO_REUSEADDR,还有SO_REUSEPORT,具体的区别可以参考
https://www.jianshu.com/p/a23b7e8a4c6a
1 | socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
参考: