多进程与Socket:OSError: [Errno 98] 地址已占用问题求助
解决多进程Socket端口占用(Errno 98)问题
问题背景
- 树莓派Thonny IDE中运行Python脚本,通过Socket接收Arduino编程的UWB ESP32设备数据
- 单进程面向对象代码中,添加
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)后,可正常解决“OSError: [Errno 98] Address already in use”错误 - 改用
multiprocessing.Process和Queue实现多进程调用后,首次运行正常,但停止程序再次启动时,会再次触发端口占用错误;且先运行多进程代码后,单进程代码也无法使用同一端口,只能通过重启树莓派恢复
问题根源
多进程场景下,子进程会继承父进程的Socket文件描述符:
- 主程序退出时,子进程可能未被正确终止,成为僵尸进程持续占用Socket资源
- 即使主进程关闭Socket,子进程继承的Socket可能仍处于TIME_WAIT状态,且
SO_REUSEADDR在继承场景下无法完全生效
解决步骤
1. 子进程独立创建Socket
不要在父进程中创建Socket后让子进程继承,而是在子进程的运行函数内部单独创建Socket并设置复用选项,确保每个进程的Socket资源独立。
2. 同时启用SO_REUSEPORT选项(Linux专属)
除了SO_REUSEADDR,添加SO_REUSEPORT选项,允许同一端口被多个合法进程绑定(树莓派系统内核版本通常满足要求):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
3. 强制终止并清理子进程
在程序退出时(比如捕获Ctrl+C的SIGINT信号),显式终止子进程并等待其结束,避免残留进程占用资源。
4. 规范Socket关闭流程
在Socket不再使用时,先调用shutdown(socket.SHUT_RDWR)关闭读写通道,再调用close()释放资源,每个创建Socket的进程都要执行该操作。
修正后的代码示例
import socket import multiprocessing from multiprocessing import Queue import signal import time class UWBProcess: def __init__(self, queue, host='0.0.0.0', port=12345): self.queue = queue self.host = host self.port = port self.running = False self.process = None def _run_uwb(self): # 子进程内部独立创建Socket,避免继承父进程描述符 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 启用地址和端口复用 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) try: sock.bind((self.host, self.port)) sock.listen(1) self.running = True print(f"UWB Socket监听中:{self.host}:{self.port}") conn, addr = sock.accept() with conn: print(f"已连接设备:{addr}") while self.running: data = conn.recv(1024) if not data: break self.queue.put(data.decode('utf-8')) except Exception as e: print(f"UWB进程错误:{e}") finally: # 规范关闭Socket if sock: sock.shutdown(socket.SHUT_RDWR) sock.close() self.running = False print("UWB Socket已关闭") def start(self): self.process = multiprocessing.Process(target=self._run_uwb) self.process.start() def stop(self): self.running = False if self.process and self.process.is_alive(): self.process.terminate() # 等待进程终止,超时则强制杀死 self.process.join(timeout=3) if self.process.is_alive(): self.process.kill() print("UWB进程已终止") class ROBOTCONTROLLER: def __init__(self): self.data_queue = Queue() self.uwb = UWBProcess(self.data_queue) # 捕获退出信号,确保子进程被清理 signal.signal(signal.SIGINT, self._handle_exit) signal.signal(signal.SIGTERM, self._handle_exit) def _handle_exit(self, signum, frame): print("\n正在停止机器人控制器...") self.uwb.stop() exit(0) def run(self): self.uwb.start() while True: if not self.data_queue.empty(): data = self.data_queue.get() print(f"收到UWB数据:{data}") time.sleep(0.1) if __name__ == "__main__": controller = ROBOTCONTROLLER() controller.run()
验证方法
- 运行上述代码,测试数据接收正常后,按
Ctrl+C终止程序 - 再次启动程序,检查是否仍出现端口占用错误
- 如果问题依旧,用
lsof -i :<你的端口号>查看端口占用进程,用kill -9 <进程ID>手动清理残留进程
内容的提问来源于stack exchange,提问作者Benny
相关产品推荐
相关产品推荐

