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

Python实现串口转多客户端TCP服务器:客户端断开时如何更新套接字列表?

串口转多客户端TCP桥接:处理客户端断开连接的问题

你已经实现了一个串口转TCP/IP的桥接程序,能正常运行,但需要解决TCP客户端断开时更新SerialToNet类中sockets列表的问题。下面是修复后的完整代码,以及关键修改说明:

修复后的代码

import sys
import socket
from threading import Thread, Lock
import serial
import serial.threaded


class SerialToNet(serial.threaded.Protocol):
    """serial->socket"""

    def __init__(self):
        self.sockets: list[socket.socket] = []
        self.socket_lock = Lock()  # 线程锁,保证多线程操作sockets列表安全

    def __call__(self):
        return self

    def data_received(self, data):
        """Forward data from Serial to IP client Sockets"""
        with self.socket_lock:
            # 遍历列表副本,避免遍历过程中列表被修改导致异常
            for socket in list(self.sockets):
                try:
                    socket.sendall(data)
                except (ConnectionResetError, BrokenPipeError):
                    # 发送失败说明客户端已断开,直接移除并关闭socket
                    self.sockets.remove(socket)
                    socket.close()


class NetToSerial(Thread):
    """socket->serial"""

    def __init__(self, client_socket, serial_worker, serial_to_net):
        Thread.__init__(self)
        self._socket = client_socket
        self.serial_worker = serial_worker
        self.serial_to_net = serial_to_net

    def run(self):
        try:
            while True:
                data = self._socket.recv(1024)
                if not data:  # recv返回空字节表示客户端主动断开连接
                    break
                self.serial_worker.write(data)
        except (ConnectionAbortedError, ConnectionResetError):
            print("NetToSerial client disconnection")
        finally:
            # 无论正常退出还是异常,都移除socket并关闭
            with self.serial_to_net.socket_lock:
                if self._socket in self.serial_to_net.sockets:
                    self.serial_to_net.sockets.remove(self._socket)
            self._socket.close()
            print(f"Client socket closed and removed from list")


if __name__ == "__main__":

    # Serial connection
    SERIAL_COM_PORT = 'COM9'
    try:
        ser = serial.Serial(SERIAL_COM_PORT, 115200, timeout=2)
    except serial.SerialException:
        sys.exit(f"Serial port {SERIAL_COM_PORT} is not available")

    serial_to_net = SerialToNet()
    serial_worker = serial.threaded.ReaderThread(ser, serial_to_net)
    serial_worker.start()

    # TCP Server
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind(('', 3490))
    mythreads = []

    try:
        # Wait new IP clients
        while True:
            server_socket.listen()
            print("Server: waiting TCP client connection")
            (client_socket, client_addr) = server_socket.accept()
            print(f"New client connected: {client_addr}")

            # New client
            net_to_serial_thread = NetToSerial(client_socket, serial_worker, serial_to_net)
            with serial_to_net.socket_lock:
                serial_to_net.sockets.append(client_socket)

            net_to_serial_thread.start()
            mythreads.append(net_to_serial_thread)
    except KeyboardInterrupt:
        print("Shutting down server...")

    # 程序退出时清理资源
    with serial_to_net.socket_lock:
        for sock in serial_to_net.sockets:
            sock.close()
    for t in mythreads:
        t.join()
    serial_worker.stop()
    ser.close()

关键修改说明

  1. 添加线程锁:在SerialToNet中加入socket_lock,避免多个线程同时修改sockets列表引发的并发异常。
  2. 客户端断开时主动移除socket:在NetToSerial的run方法中,通过finally块确保无论客户端是主动断开还是异常断开,都能从sockets列表中移除对应连接并关闭socket。
  3. 串口转发时的异常处理:在data_received中遍历sockets的副本,同时捕获发送失败的异常,直接移除失效连接,防止后续重复报错。
  4. 优化全局变量依赖:将serial_worker和serial_to_net作为参数传入NetToSerial,避免全局变量导致的线程安全问题。
  5. 完善退出清理逻辑:捕获KeyboardInterrupt后,主动关闭所有客户端连接、等待线程结束、停止串口线程并关闭串口,确保资源正常释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:55:25