我们在测试代码时,由于需要经常重启服务,经常会发现服务端口被占用。
一般kill掉后台进程就ok了,但是如果服务有启动一些常驻的后台程序,可能也会导致端口不能释放。
在类UNIX系统中,一切被打开的文件、端口被抽象为文件描述符(file descriptor)
从python3.4开始,文件描述符默认是non-inheritable,也就是子进程不会共享文件描述符。
问题
一般为了实现多进程、多线程的webserver,服务端口fd必须设置为继承(set_inheritable),这样才能多进程监听一个端口(配合SO_REUSEPORT)
典型的是使用flask的测试服务器的场景,这里我们写一段代码模拟。
1 2 3 4 5 6
| import socket, os server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 22222)) server.set_inheritable(True)
os.system("python -c 'import time;time.sleep(1000)' ")
|
复制
我们通过lsof -p {pid}
可以看到这两个进程的所有文件描述符
server进程, 可以看到服务端口的fd是4
1 2 3 4 5 6 7 8 9 10 11
| COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME ptpython 6214 cwd DIR 253,0 4096 872946898 / ... ptpython 6214 0u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 1u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 2u CHR 136,13 0t0 16 /dev/pts/13 ptpython 6214 3r CHR 1,9 0t0 2057 /dev/urandom ptpython 6214 4u sock 0,7 0t0 58345077 protocol: TCP ptpython 6214 5u a_inode 0,10 0 8627 [eventpoll] ptpython 6214 6u unix 0x0000000000000000 0t0 58368029 socket ptpython 6214 7u unix 0x0000000000000000 0t0 58368030 socket
|
复制
sleep子进程,也拥有fd=4的文件描述符
1 2 3 4 5 6 7
| COMMAND PID FD TYPE DEVICE SIZE/OFF NODE NAME python 18022 cwd DIR 253,0 4096 872946898 / ... python 18022 0u CHR 136,13 0t0 16 /dev/pts/13 python 18022 1u CHR 136,13 0t0 16 /dev/pts/13 python 18022 2u CHR 136,13 0t0 16 /dev/pts/13 python 18022 4u sock 0,7 0t0 58345077 protocol: TCP
|
复制
如果server进程退出时,sleep进程没有退出,fd=4对应的端口就被占用了,服务也就不能正常启动了。
解决方法
手动清理
1 2 3 4 5 6 7 8
| import os import time
os.system(f'lsof -p {os.getpid()}') os.closerange(3, 100) time.sleep(5) os.system(f'lsof -p {os.getpid()}')
|
复制
使用close_fds
使用subprocess库而不是os来启动子程序, 通过close_fds参数关闭多余的文件描述符
1 2
| import subprocess subprocess.call("python -c 'import time;time.sleep(1000)'", shell=True, close_fds=True)
|
复制
参考
Gitalking ...