如何用Python Socket处理断开连接问题(ConnectionResetError)
解决Python Socket服务器处理客户端断开的问题
嘿,我刚看了你的代码,这个问题其实很常见——当客户端意外断开(比如直接关掉程序),服务器在调用recv或者send的时候就会抛出ConnectionResetError,直接把当前客户端线程搞挂,甚至影响整个服务器运行。别担心,我们一步步来修复它,顺便优化下代码里的小问题!
核心问题分析
你遇到的ConnectionResetError是因为客户端强制关闭了连接,但服务器还在尝试从这个连接读取或发送数据。另外你的代码还有两个小隐患:
- 没有处理客户端正常关闭连接的情况(此时
recv()会返回空字节b'') - 多个线程同时操作
connections列表,会有线程安全问题(比如一个线程在遍历列表,另一个线程在删除元素)
具体修复方案
我们可以通过以下几步来解决:
1. 捕获IO异常,处理客户端断开
在recvmsg函数的循环里添加try-except块,捕获ConnectionResetError和其他IO相关异常,当异常发生时,从连接列表中移除该客户端,然后退出线程。
2. 处理正常断开的情况
当客户端正常关闭连接时,clientsocket.recv(1024)会返回空字节b'',这时候我们也要主动断开连接,从列表中移除。
3. 给连接列表加线程锁
因为acceptconnection线程在添加连接,而每个客户端线程可能在删除连接,多个线程同时操作列表会引发错误,所以用threading.Lock来保证操作的原子性。
4. 优化消息转发逻辑
原来的代码里把消息解码再重新编码,其实直接转发原始字节数据就可以,没必要重复解码编码,节省资源。
修改后的完整代码
import socket import threading s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 1234)) s.listen(5) connections = [] # 添加线程锁,保证connections列表的线程安全 conn_lock = threading.Lock() def accptconnection(): while True: clientsocket, address = s.accept() print(f"Connection from {address} has been established.") # 加锁后再修改连接列表 with conn_lock: connections.append(clientsocket) threading.Thread(target=recvmsg, args=(clientsocket, address,)).start() def recvmsg(clientsocket, address): try: while True: msg = clientsocket.recv(1024) # 处理正常断开:recv返回空字节 if not msg: print(f"Client {address} disconnected normally.") break print(f"Received from {address}: {msg.decode('utf-8')}") # 加锁后遍历并转发消息 with conn_lock: # 遍历列表的副本,避免遍历过程中列表被修改 for connection in connections.copy(): try: # 直接转发原始字节,不用重复解码编码 connection.send(msg) except Exception as e: # 如果转发时遇到异常,说明该连接也断开了,移除它 print(f"Failed to send to a client: {e}") connections.remove(connection) except ConnectionResetError: print(f"Client {address} forcibly closed the connection.") except Exception as e: print(f"Unexpected error with client {address}: {e}") finally: # 无论哪种断开情况,最后都要从列表中移除该连接 with conn_lock: if clientsocket in connections: connections.remove(clientsocket) # 关闭客户端套接字 clientsocket.close() print(f"Connection with {address} closed.") accptconnection()
关键修改点说明
- 把连接建立的提示移到了
acceptconnection里,避免原来每次接收消息都重复打印连接信息的小bug - 用
with conn_lock:包裹所有对connections列表的修改操作,保证线程安全 - 在转发消息时遍历
connections.copy(),这样即使遍历过程中列表被修改,也不会引发错误 - 用
finally块确保无论是否发生异常,都会关闭套接字并移除连接 - 捕获了通用的
Exception处理其他意外情况,同时保留特定的ConnectionResetError区分不同断开原因
这样修改后,你的服务器就能优雅地处理客户端断开的情况,不会因为单个客户端的断开而崩溃啦!
内容的提问来源于stack exchange,提问作者HoneyPoop
相关产品推荐
相关产品推荐

