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
相关产品推荐
相关产品推荐

