Python多线程Socket服务端匹配客户端处理函数触发KeyError异常
异常触发原因
你遇到的KeyError本质是字典初始化时机和逻辑设计错误导致的:
function_dictionary在服务启动阶段就完成了初始化,此时还没有任何客户端接入,clients是空列表,和functions压缩后生成的是空字典,内部没有任何socket对象作为键。- 后续新客户端接入时,你仅把新的socket对象追加到了
clients列表中,从未同步更新function_dictionary的键值对,所以访问function_dictionary[client]时必然找不到对应键,触发KeyError。 - 额外逻辑缺陷:你原本的设计是按客户端接入顺序绑定处理函数,完全没有识别客户端类型的步骤,就算字典能正常工作,也会出现把Iconet客户端分配给Robot处理函数的错配问题,不符合需求。
修正实现方案
核心调整逻辑:
- 不需要动态维护「socket对象->处理函数」的映射,只需要维护固定的「客户端类型字符串->处理函数」映射即可
- 客户端建立连接后,先读取客户端首包上报的身份类型,校验类型合法后,直接从固定映射中取对应的处理函数启动工作线程
- 增加异常兜底逻辑,对上报非法类型、连接异常断开的客户端做资源清理,避免socket泄漏
修正后的可运行代码如下:
import threading import socket PORT = 1026 SERVER = socket.gethostbyname(socket.gethostname()) ADDR = (SERVER,PORT) FORMAT = "utf-8" HEADER = 1024 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(ADDR) clients = [] def broadcast(values_iconet): for client in clients: try: client.send(values_iconet) except: # 发送失败说明客户端已断开,清理无效连接 clients.remove(client) def handle_robot(client,addr): print(f"[ROBOT CONNECTION] {addr} connected.") client.send('hello robot'.encode(FORMAT)) connected = True while connected: try: msg = client.recv(HEADER) if not msg: break # 在此处编写Robot客户端专属业务处理逻辑 print(f"[ROBOT {addr}] {msg.decode(FORMAT)}") except: break # 连接断开后清理资源 client.close() if client in clients: clients.remove(client) print(f"[ROBOT DISCONNECT] {addr} disconnected.") def handle_iconet(client,addr): print(f"[ICONET CONNECTION] {addr} connected.") client.send('hello iconet'.encode(FORMAT)) connected = True while connected: try: msg = client.recv(HEADER) if not msg: break # 在此处编写Iconet客户端专属业务处理逻辑 print(f"[ICONET {addr}] {msg.decode(FORMAT)}") except: break # 连接断开后清理资源 client.close() if client in clients: clients.remove(client) print(f"[ICONET DISCONNECT] {addr} disconnected.") # 固定维护 客户端类型->处理函数 的映射,不需要动态生成 handler_map = { "Robot": handle_robot, "Iconet": handle_iconet } def start(): server.listen() print(f"[LISTENING] Server is listening on {SERVER}") while True: client, addr = server.accept() clients.append(client) print(f"[NEW CONNECTION] {addr} connected.") client.send('Welcome, please send your client type'.encode(FORMAT)) try: # 读取客户端首包上报的类型,解码后去掉首尾空白字符 client_type = client.recv(HEADER).decode(FORMAT).strip() # 校验客户端类型合法性 if client_type not in handler_map: print(f"[INVALID CLIENT] {addr} sent unknown type {client_type}, disconnect.") client.send("Invalid client type".encode(FORMAT)) client.close() clients.remove(client) continue # 从固定映射取对应处理函数启动工作线程,设置为守护线程随主进程退出 thread = threading.Thread(target=handler_map[client_type], args=(client,addr), daemon=True) thread.start() print(f"[ACTIVE CONNECTIONS] {threading.active_count() -1}") except Exception as e: print(f"[CONNECTION ERROR] {addr} error: {str(e)}") client.close() if client in clients: clients.remove(client) print ('[STARTING] server is starting') start()
实现说明
- 客户端需要在连接成功、收到服务端欢迎消息后,第一时间发送自身类型字符串(
Robot或Iconet),服务端识别类型后才会进入对应业务处理流程 - 工作线程设置为daemon守护线程,服务端退出时会自动回收所有工作线程,避免进程僵死
- 所有消息收发位置增加了异常捕获,客户端异常断开时会自动从clients列表清理无效socket,避免广播操作时报错
内容的提问来源于stack exchange,提问作者Sho
相关产品推荐
相关产品推荐

