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

Python多线程Socket C/S通信多客户端发送异常排查

问题原因

多客户端接入后第一个客户端无法发消息的核心bug是服务端代码的变量名作用域覆盖,附带几处冗余代码和逻辑隐患:

  • 致命错误:服务端处理单客户端消息的recive函数,入参为当前连接的套接字client,但在广播消息的for循环中,你再次使用client作为循环遍历变量:
    # 原错误代码片段
    for client in clients:
        client.send(bytes(string, "utf-8"))
    
    循环执行完成后,函数作用域内原本指向当前线程绑定客户端的client变量,会被覆盖为clients列表中最后一个客户端的套接字。下一轮循环调用client.recv()时,实际读取的是最后接入的客户端的套接字数据,第一个客户端的线程完全无法读到自身连接上收到的消息,自然表现为无法发送消息。
  • 冗余代码:你在全局作用域和__main__入口块中重复编写了服务端初始化、绑定、监听逻辑,全局定义的server对象从未被实际使用,属于无效代码。
  • 逻辑隐患:客户端断开连接时,没有将对应套接字从clients列表中移除,后续广播消息时向已关闭的连接写入数据会持续触发异常;同时代码导入了大量未使用的模块,无实际作用还会增加加载开销。
  • 客户端逻辑缺陷:使用全局变量做消息内容比对,存在消息漏打印、内容串扰的风险。
修复后可运行代码

服务端

import socket
import _thread

# 服务端配置
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
PORT = 55555

# 存储所有在线连接和对应昵称
clients = []
nicknames = []

def broadcast(message):
    """向所有在线客户端广播消息"""
    # 遍历使用独立变量名conn,避免和单连接变量冲突
    for conn in clients:
        try:
            conn.send(message)
        except:
            # 发送失败说明连接已断开,清理资源
            idx = clients.index(conn)
            clients.remove(conn)
            conn.close()
            left_nick = nicknames[idx]
            nicknames.remove(left_nick)
            print(f"用户 {left_nick} 已断开连接")

def handle_client(conn):
    """处理单个客户端的连接逻辑"""
    try:
        # 接收昵称
        nickname = conn.recv(2048).decode("utf-8")
        nicknames.append(nickname)
        clients.append(conn)
        print(f"新用户加入:{nickname},当前在线人数{len(clients)}")
        broadcast(bytes(f"[系统消息] {nickname} 加入了聊天室", "utf-8"))
    except Exception as e:
        conn.close()
        return

    while True:
        try:
            message = conn.recv(2048)
            if not message:
                break
            msg_text = message.decode("utf-8")
            if msg_text == "!exit":
                break
            # 转发消息
            idx = clients.index(conn)
            sender_name = nicknames[idx]
            broadcast(bytes(f"{sender_name}: {msg_text}", "utf-8"))
            print(f"{sender_name}: {msg_text}")
        except:
            break
    
    # 连接断开后的资源清理
    idx = clients.index(conn)
    clients.remove(conn)
    conn.close()
    left_nick = nicknames[idx]
    nicknames.remove(left_nick)
    broadcast(bytes(f"[系统消息] {left_nick} 离开了聊天室", "utf-8"))
    print(f"用户 {left_nick} 已断开连接,当前在线人数{len(clients)}")

if __name__ == "__main__":
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((local_ip, PORT))
    server.listen()
    print(f"聊天服务已启动,监听地址 {local_ip}:{PORT}")
    while True:
        conn, addr = server.accept()
        print(f"新连接建立:{addr[0]}:{addr[1]}")
        _thread.start_new_thread(handle_client, (conn,))

客户端

import socket
import _thread

# 服务端配置
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
PORT = 55555

nickname = input("请输入你的昵称:")
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((local_ip, PORT))
# 连接建立后先发送昵称
client.send(bytes(nickname, "utf-8"))

def receive_msg():
    """持续接收服务端广播的消息"""
    while True:
        try:
            msg = client.recv(2048).decode("utf-8")
            print(msg)
        except:
            print("与服务端的连接已断开")
            client.close()
            break

def send_msg():
    """持续读取用户输入发送给服务端"""
    while True:
        try:
            content = input("")
            if content == "!exit":
                client.send(bytes(content, "utf-8"))
                client.close()
                break
            client.send(bytes(content, "utf-8"))
        except:
            break

# 启动接收线程,主线程跑发送逻辑
_thread.start_new_thread(receive_msg, ())
send_msg()
关键修改点
  • 彻底修复变量覆盖问题:广播循环使用独立的conn作为遍历变量,和线程持有的单连接对象完全隔离,不会出现套接字串用的问题。
  • 新增连接自动清理逻辑:客户端主动退出、网络异常断开时,会自动从在线列表移除,不会残留无效连接。
  • 删除所有未使用的导入、重复的初始化代码,精简全局变量,避免变量串扰。
  • 优化消息格式,增加系统提示,用户加入/退出时所有在线客户端都能收到通知。
  • 去掉了客户端原来的全局字符串比对逻辑,避免消息漏显示的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:03:27