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

Python多客户端Socket连接中客户端切换网络引发conn.recv无限阻塞问题求助

看起来你遇到了TCP半开连接导致的无限阻塞问题,我来帮你拆解核心原因,再给出针对性的解决方案:

问题根源

当客户端切换同一网络的2GHz/5GHz频段时,原来的TCP连接会进入半开状态——双方都没有主动发送关闭连接的FIN包,但实际的网络链路已经失效了。此时你的服务端向旧连接发送空格后,TCP协议会一直尝试重传数据,但永远收不到客户端的ACK;而你之前设置的self.socket.settimeout(2)完全无效,因为这个超时是给**服务器的监听socket(用来接收新连接的那个)**设置的,对已经建立的客户端连接conn没有任何作用,这就是conn.recv()无限阻塞的关键原因。

解决方案

1. 给每个客户端连接单独设置超时

修改你的check_clients函数,把超时设置在每个客户端的连接socket上,这样recv()在指定时间内收不到数据就会抛出异常,不会一直阻塞:

def check_clients(self, UUID):
    invalid_conn = []
    print(f"(UUID: {UUID}) Checking for dead clients")
    if UUID not in self.all_connections:
        return False
    
    # 遍历每个客户端连接,单独配置超时
    for i, conn in enumerate(self.all_connections[UUID]):
        try:
            # 给当前客户端连接设置2秒的recv超时
            conn.settimeout(2)
            print('sending blank cmd')
            conn.send(str.encode(' '))
            cwd = str(conn.recv(20480))  # 超时后会触发异常
            print('got cwd:',cwd)
            # 恢复默认的无超时状态(如果后续业务需要正常阻塞接收)
            conn.settimeout(None)
        except Exception as e:
            print(f'Client connection error: {str(e)} - marking as invalid')
            invalid_conn.append(conn)
            continue
    
    # 清理无效连接,记得先关闭socket再删除
    for conn in invalid_conn:
        client_id = self.all_connections[UUID].index(conn)
        print(f'(UUID: {UUID}) Could not reach client, removing {self.all_addresses[UUID][client_id]}')
        try:
            conn.close()
        except:
            pass
        del self.all_connections[UUID][client_id]
        del self.all_addresses[UUID][client_id]
    
    print(f"(UUID: {UUID}) Dead clients check finished")
    return

2. 客户端新增连接异常处理与重连逻辑

客户端切换网络后,旧的socket连接已经失效,但当前代码没有处理recv()返回空字节(表示连接已关闭)或抛出异常的情况。修改receive_commands函数,加入异常捕获和自动重连:

# Receive commands from server
def receive_commands(self):
    while True:
        print("Listening for commands")
        output_str = None
        try:
            data = self.sock.recv(1024)
            print('data:',data)
            # 如果recv返回空字节,说明服务端已断开连接
            if not data:
                print("Connection closed by server, retrying connection...")
                self.reconnect()
                continue
            if len(data) > 0:
                try:
                    cmd = data[:].decode("cp850")
                    # 业务逻辑处理(你的原有代码)
                    output_str = str(f'received: {cmd} from server', "cp850")
                except Exception as e:
                    output_str = f"Command failed to execute: {str(e)}"
            if output_str is None:
                raise ConnectionError("No response from server")
            else:
                self.print_output(output_str)
                print(f"Input command: {data[:].decode('cp850')}")
                print(f"Output -> {output_str}\n")
        except Exception as e:
            print(f"Connection error occurred: {str(e)}, reconnecting...")
            self.reconnect()
            continue
    self.sock.close()
    return

# 新增自动重连函数
def reconnect(self):
    # 先关闭旧的无效socket
    try:
        self.sock.close()
    except:
        pass
    # 循环尝试重连,直到成功
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    while True:
        try:
            self.sock.connect((self.server_ip, self.server_port))
            print("Successfully reconnected to server!")
            break
        except:
            print("Reconnection failed, trying again in 3 seconds...")
            time.sleep(3)

3. 可选:优化为标准心跳机制

如果想让检测更可靠,避免空格干扰业务逻辑,可以把当前的检测改成标准心跳协议:

  • 服务端发送特定心跳标识(比如"HEARTBEAT")
  • 客户端收到后回复固定响应(比如"HEARTBEAT_ACK")
  • 这样可以清晰区分心跳检测和正常业务命令,降低误判概率

关键提醒

  • 服务器的监听socket和客户端连接socket是完全独立的,不要给监听socket设置超时来处理客户端连接,它只影响accept()操作。
  • TCP本身不会自动检测半开连接,默认的TCP Keepalive超时非常长(通常2小时),所以必须通过应用层心跳主动检测。
  • 客户端切换网络后,旧连接无法复用,必须主动关闭并重新连接,否则会一直卡在无效连接上。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:07:30