You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 21:24:54