Python多连接服务器场景下客户端Socket完成读写事件后无法关闭的问题
我帮你梳理下问题的核心原因,然后给出针对性的修复方案:
问题根源
你的客户端无法自动关闭主要来自两个关键问题:
1. 连接关闭的触发逻辑缺失
你修改后的客户端只有在服务器主动关闭连接(即recv_data为空)时才会执行关闭逻辑,但你的服务器在发送完所有消息后并不会主动断开——服务器的代码里,只有当客户端先关闭连接时,才会触发自身的关闭流程。这就形成了“互相等待”的死锁:客户端等服务器关,服务器等客户端关,两边都不会主动触发关闭。
另外,你在客户端的写事件里加了if data.outb and data.inb的判断,这会导致初始发送第一条消息时因为data.inb为空而发不出去,后续的消息收发逻辑也会混乱,这是完全没必要的限制。
2. 循环终止条件无法触发
因为客户端的连接一直没有被从选择器中注销(sel.unregister),所以sel.get_map()始终不为空,外层的break也就永远不会执行,程序会一直卡在循环里。
修复方案
我们需要调整客户端的逻辑,让它在发送完自身所有消息,并且接收完服务器的所有消息后主动关闭连接,同时修复发送逻辑的错误限制。
方案1:客户端主动判断收发完成(推荐)
这个方案不需要修改服务器代码,只调整客户端:
步骤1:在客户端初始化时记录服务器消息总长度
首先让客户端知道服务器要发送的消息总长度,这样才能判断是否收完所有内容:
def start_connections(host, port, num_conns): server_addr = (host, port) # 和你的server.py中的messages对应,计算服务器消息总长度 server_msg_total = sum(len(m) for m in [b"Message 1 from server.", b"Message 2 from server."]) for i in range(0, num_conns): connid = i + 1 print("starting connection", connid, "to", server_addr) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setblocking(False) sock.connect_ex(server_addr) events = selectors.EVENT_READ | selectors.EVENT_WRITE data = types.SimpleNamespace( connid=connid, recv_total=0, server_msg_total=server_msg_total, # 新增:记录服务器消息总长度 messages=list(messages), outb=b"", inb=b"", ) sel.register(sock, events, data=data)
步骤2:修复客户端的消息收发与关闭逻辑
移除写事件里的data.inb限制,同时添加“收发完成”的判断条件:
def service_connection(key, mask): sock = key.fileobj data = key.data if mask & selectors.EVENT_READ: recv_data = sock.recv(1024) print("received", repr(recv_data), "from connection", data.connid) if recv_data: data.recv_total += len(recv_data) data.inb += recv_data # 判断:收完服务器所有消息,且自身消息已发送完毕 if data.recv_total == data.server_msg_total and not data.messages and not data.outb: print("closing connection", data.connid) sel.unregister(sock) sock.close() else: # 处理服务器主动关闭的情况 print("closing connection", data.connid) sel.unregister(sock) sock.close() if mask & selectors.EVENT_WRITE: # 移除不必要的data.inb限制,恢复正常发送逻辑 if not data.outb and data.messages: data.outb = data.messages.pop(0) if data.outb: print("sending", repr(data.outb), "to connection", data.connid) sent = sock.send(data.outb) data.outb = data.outb[sent:] # 发送完所有消息后,如果已经收完服务器的消息,直接关闭 if not data.messages and not data.outb and data.recv_total == data.server_msg_total: print("closing connection", data.connid) sel.unregister(sock) sock.close()
步骤3:验证循环终止
当所有连接都被注销后,sel.get_map()会变为空,此时外层循环的if not sel.get_map(): break会触发,客户端程序会正常退出。
方案2:让服务器发送完消息后主动关闭(可选)
如果你不想在客户端硬编码服务器消息长度,可以修改服务器代码,让它在发送完所有消息后主动断开连接:
def service_connection(key, mask): sock = key.fileobj data = key.data if mask & selectors.EVENT_READ: recv_data = sock.recv(1024) if recv_data: data.inb += recv_data print("received", recv_data, "from", data.addr) else: print("closing connection to", data.addr) sel.unregister(sock) sock.close() if mask & selectors.EVENT_WRITE: if not data.outb and data.messages: data.outb = data.messages.pop(0) if data.outb: print("echoing", repr(data.outb), "to", data.addr) sent = sock.send(data.outb) data.outb = data.outb[sent:] # 新增:当所有消息发送完毕,主动关闭连接 elif not data.messages and not data.outb: print("all messages sent, closing connection to", data.addr) sel.unregister(sock) sock.close()
这样服务器发送完所有消息后会主动断开,客户端收到recv_data为空的信号后就会关闭自身连接,同样能解决问题。
内容的提问来源于stack exchange,提问作者Gary

