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

Python Socket客户端终止后仍持续接收空消息的解决方法

解决Socket进程间通信中客户端异常退出导致服务器循环广播空消息的问题

这个问题我之前也碰到过,本质是TCP连接断开后的终止信号没被服务器正确处理,加上客户端异常退出时没有主动清理连接,导致服务器陷入空消息循环广播的死循环。咱们一步步拆解修复方案:

问题根源分析

当客户端通过Ctrl+D(或者进程意外崩溃)终止时,TCP连接会发送一个FIN包给服务器,此时服务器调用client.recv(BUFFER_SIZE)会返回空字节串b''——这是TCP协议明确规定的「连接已关闭」的信号。但你当前的服务器代码没有处理这个情况,会把空消息广播出去,并且因为recv立刻返回空,导致handle_client的while循环无限执行,反复广播空内容。

另外客户端的close方法虽然发送了{clientClose}标记,但异常退出时(比如Ctrl+D)这个方法根本没被调用,服务器收不到关闭通知,也无法主动移除失效的客户端连接。


修复方案:服务器端+客户端双向优化

1. 服务器端代码修改

主要要做这几件事:

  • 检测recv返回的空字节串,识别客户端断开事件
  • 从clients列表中移除失效连接,避免广播给已断开的客户端
  • 给clients列表加锁,避免多线程并发修改导致的异常
  • 处理recv可能抛出的连接异常(比如ConnectionResetError)

修改后的服务器代码:

from socket import AF_INET, gethostname, socket, SOCK_STREAM
import _thread as thread
import threading

# 给clients列表加锁,避免多线程并发修改
clients_lock = threading.Lock()
clients = []
HOST = gethostname()
PORT = 33000
BUFFER_SIZE = 1024
ADDR = (HOST, PORT)

s = socket(AF_INET, SOCK_STREAM)
s.bind(ADDR)

def accept_client():
    while True:
        client, _ = s.accept()
        with clients_lock:
            clients.append(client)
        print("Client accepted.")
        thread.start_new_thread(handle_client, (client, ))

def handle_client(client):
    try:
        while True:
            message = client.recv(BUFFER_SIZE)
            # 检测空字节串,说明客户端已断开
            if not message:
                print("Client disconnected.")
                break
            print("Incoming message: {}".format(message.decode('utf-8')))
            broadcast(message)
    except ConnectionResetError:
        print("Client connection reset.")
    finally:
        # 无论正常断开还是异常,都从clients移除并关闭连接
        with clients_lock:
            if client in clients:
                clients.remove(client)
        client.close()
        print("Client connection closed.")

def broadcast(message):
    print("Broadcasting...")
    # 遍历clients的副本,避免遍历过程中列表被修改
    with clients_lock:
        clients_copy = clients.copy()
    for c in clients_copy:
        try:
            print("Broadcasting to {}".format(c))
            c.send(message)
        except:
            # 发送失败说明客户端已断开,直接移除
            with clients_lock:
                if c in clients:
                    clients.remove(c)
            c.close()

if __name__ == '__main__':
    s.listen()
    print("Listening...")
    thread.start_new_thread(accept_client, ())
    try:
        while True:
            pass
    except KeyboardInterrupt:
        print("Exited.")
        # 关闭所有客户端连接
        with clients_lock:
            for c in clients:
                c.close()
        s.close()
        exit(0)

2. 客户端代码修改

需要确保客户端在异常退出时(比如Ctrl+C、Ctrl+D)能主动调用close方法,正确关闭连接:

  • 添加信号处理,捕获退出信号
  • 在close方法中先关闭socket的写入端,确保服务器能收到FIN信号
  • 优化异常处理逻辑

修改后的客户端代码:

from socket import AF_INET, gethostname, socket, SOCK_STREAM, SHUT_WR
import _thread as thread
import signal
import sys

class SocketClient:
    buffer_size = 1024
    def __init__(self, host=gethostname(), port=33000):
        self.host = host
        self.port = port
        self.buffer_size = 1024
        self.s = socket(AF_INET, SOCK_STREAM)
        self.s.connect((self.host, self.port))
        # 注册信号处理,捕获Ctrl+C和终止信号
        signal.signal(signal.SIGINT, self._handle_exit)
        signal.signal(signal.SIGTERM, self._handle_exit)
    
    def _handle_exit(self, signum, frame):
        """处理退出信号,主动关闭连接"""
        print("\nClosing client connection...")
        self.close()
        sys.exit(0)
    
    def bind_callback(self, callback, args=()):
        thread.start_new_thread(self.receive, (callback, args))
    
    def receive(self, callback, args):
        while True:
            try:
                msg = self.s.recv(self.buffer_size).decode('utf-8')
                if not msg:
                    # 服务器断开连接
                    print("Server disconnected.")
                    self.close()
                    break
                callback(msg, *args)
            except OSError as err:
                self.close()
                raise err
    
    def send(self, msg):
        try:
            self.s.send(msg.encode('utf-8'))
        except:
            self.close()
    
    def close(self):
        try:
            # 先发送关闭标记,再关闭写入端,通知服务器连接即将关闭
            self.send('{clientClose}')
            self.s.shutdown(SHUT_WR)
        except:
            pass
        finally:
            self.s.close()

关键修改点说明

  1. 服务器端:

    • 用threading.Lock保护clients列表,避免多线程并发添加/移除时出现异常
    • 在handle_client中检测message是否为空,一旦为空就跳出循环,清理连接
    • 广播时遍历clients的副本,避免遍历过程中列表被修改导致的错误
    • 捕获ConnectionResetError,处理客户端强制断开的情况
  2. 客户端:

    • 注册SIGINT和SIGTERM信号处理,确保Ctrl+C/系统终止时能主动关闭连接
    • 使用shutdown(SHUT_WR)关闭socket的写入端,让服务器能收到FIN信号,触发recv返回空
    • 在receive中检测空消息,处理服务器主动断开的情况

这样修改后,无论是客户端主动退出还是异常终止,服务器都能正确识别连接断开事件,移除失效客户端,不会再出现循环广播空消息的问题。

内容的提问来源于stack exchange,提问作者srakrn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:01:15