Python TCP客户端报ConnectionResetError:远程主机强制关闭连接排查
Python TCP客户端/服务器JSON通信报错:ConnectionResetError
问题现象
服务器运行正常,能接收客户端请求:
λ python server.py Server running on localhost:9091 Handle request from ('127.0.0.1', 50877) Received command: {'command': 'hello'} Sent reply: {'ok': True}
但客户端发送请求后报错连接被强制关闭:
λ python client.py Connected to ('localhost', 9091) Sent message {'command': 'hello'} to ('localhost', 9091) Traceback (most recent call last): File "C:\Users\Admin\Sample\client.py", line 25, in <module> connection.send_request(req) File "C:\Users\Admin\Sample\client.py", line 19, in send_request reply = json.load(fp=socket_input) File "C:\Users\Admin\AppData\Local\Programs\Python\Python39\lib\json\__init__.py", line 293, in load return loads(fp.read(), File "C:\Users\Admin\AppData\Local\Programs\Python\Python39\lib\socket.py", line 704, in readinto return self._sock.recv_into(b) ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
客户端代码
import json import socket class ServerConnection: def __init__(self, host: str, port: int): self._host = host self._port = port def send_request(self, request: any) -> str: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((self._host, self._port)) print(f"Connected to {(self._host, self._port)}") sock.sendall(bytes(json.dumps(request), "utf-8")) sock.shutdown(socket.SHUT_WR) print(f"Sent message {request} to {(self._host, self._port)}") with sock.makefile(mode="r", encoding="utf-8") as socket_input: reply = json.load(fp=socket_input) print(f"Received: {reply}") req = {"command": "hello"} connection = ServerConnection("localhost", 9091) connection.send_request(req)
服务器代码
import io import json import socketserver host: str = "localhost" port: int = 9091 class RequestHandler(socketserver.StreamRequestHandler): def send_reply(self, reply: any): json.dump( obj=reply, fp=io.TextIOWrapper(self.wfile, encoding="utf-8"), sort_keys=True, ) print(f"Sent reply: {reply}") def handle(self) -> None: print(f"Handle request from {self.client_address}") command = json.load( fp=io.TextIOWrapper(self.rfile, encoding="utf-8"), ) print(f"Received command: {command}") reply = {"ok": True} self.send_reply(reply) class Server(socketserver.TCPServer): def __init__(self, server_address): super().__init__( server_address=server_address, RequestHandlerClass=RequestHandler, bind_and_activate=True, ) self._active = True def shutdown(self) -> None: self._active = False def continue_handling(self) -> bool: return self._active with Server( server_address=(host, port), ) as server: print(f"Server running on {host}:{port}") while server.continue_handling(): server.handle_request() print(f"Server shut down on {host}:{port}")
错误原因分析
核心问题出在服务器的send_reply方法:
- 使用
io.TextIOWrapper包装socket的输出流self.wfile时,该包装器默认带有缓冲区,json.dump完成后数据并未立即写入到socket,而是留在缓冲区中。 - 当
handle方法执行完毕后,socketserver会自动关闭当前连接,此时缓冲区中的回复数据还没来得及发送给客户端,客户端在等待回复时收到连接被关闭的信号,触发ConnectionResetError。
你排查的系统资源耗尽、请求量过大、防火墙问题均不成立,本次问题完全由代码逻辑导致。
修复方案
方案1:直接使用字节流发送(推荐)
跳过TextIOWrapper,直接将JSON序列化为字节后写入self.wfile,避免缓冲问题:
def send_reply(self, reply: any): # 将JSON对象序列化为字符串后编码为字节 reply_data = json.dumps(reply, sort_keys=True).encode("utf-8") self.wfile.write(reply_data) self.wfile.flush() # 确保数据立即发送 print(f"Sent reply: {reply}")
方案2:手动刷新TextIOWrapper缓冲区
如果坚持使用TextIOWrapper,需要手动刷新缓冲区确保数据发送:
def send_reply(self, reply: any): wrapper = io.TextIOWrapper(self.wfile, encoding="utf-8") json.dump(obj=reply, fp=wrapper, sort_keys=True) wrapper.flush() # 强制将缓冲区数据写入socket print(f"Sent reply: {reply}")
另外,服务器读取客户端数据的代码可以简化,StreamRequestHandler的rfile本身就是可读取的文件对象,无需额外包装:
command = json.load(self.rfile)
内容的提问来源于stack exchange,提问作者tellmemore
相关产品推荐
相关产品推荐

