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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:36:29