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
相关产品推荐
相关产品推荐

