一个常见的初学者问题:
kill一个进程后,再次启用该程序总是受到"Address already in use"的问题困扰。
这里通常有一个前提条件,即该进程上至少有一个连接仍然存活
问题复现
使用以下程序结合telnet可以简洁复现这个问题。
from socket import *
from struct import pack
PORT = 9000
LISTENQ = 5
# creat a socket
sock = socket(AF_INET, SOCK_STREAM)
sock.bind((inet_ntop(AF_INET, pack('i', INADDR_ANY)), PORT))
sock.listen(LISTENQ)
# blocking...
sock.accept()
# blocking...
sock.accept()
在进程第一次被阻塞时,通过telnet localhost 9000
建立一个连接。
在进程第二次被阻塞时,kill一下该程序相应的进程
然后,再次执行该程序。烦人的警告"Address already in use"再次出现.
为什么会出现这个问题?
-
我们做了什么?
向维持一个TCP连接通信的进程发送终止信号(kill), 进程执行缺省动作,被内核终结(terminate) ——— 相当于服务端主动关闭tcp连接。 -
主动关闭tcp连接会发生什么?
主动关闭方的套接字将转换为TIME_WAIT
状态, 以确保安全处理处于终结阶段tcp连接的"后事" -
验证
根据UNP1所述,TIME_WAIT
状态一般持续2MSL
的时间长度。
因此,趁着机会,瞥一眼这个连接的套接字状态, 向shell输入netstat -an | grep 9000
不出所料, 将会显示以下结果:
tcp 0 0 127.0.0.1:9000 127.0.0.1:48428 TIME_WAIT
-
后话
既然是主动关闭连接就能复现该问题,何必使用kill呢? 连接一旦建立直接exit不就好了。将上述代码段略作修改。from socket import * from struct import pack PORT = 9000 LISTENQ = 5 # creat a socket sock = socket(AF_INET, SOCK_STREAM) sock.bind((inet_ntop(AF_INET, pack('i', INADDR_ANY)), PORT)) sock.listen(LISTENQ) # blocking... sock.accept() # blocking... # sock.accept()
执行该程序,随后使用telnet建立一个连接。
再次执行此程序时,出现同样的问题。
可以使用netstat
作进一步验证。
怎么解决这个问题?
更确切地说,怎么优雅地结束这个进程,使得再次启动时不再需要等待一段时间。
UNP上给了我们答复,使用套接字中的SO_ADDRREUSE
选项。