You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多进程与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文件描述符:

  1. 主程序退出时,子进程可能未被正确终止,成为僵尸进程持续占用Socket资源
  2. 即使主进程关闭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()

验证方法

  1. 运行上述代码,测试数据接收正常后,按Ctrl+C终止程序
  2. 再次启动程序,检查是否仍出现端口占用错误
  3. 如果问题依旧,用lsof -i :<你的端口号>查看端口占用进程,用kill -9 <进程ID>手动清理残留进程

内容的提问来源于stack exchange,提问作者Benny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 10:10:56