Python Socket服务端仅能接收客户端首条消息异常排查
问题排查与修复方案
核心问题原因
你的代码存在两个典型的TCP Socket编程疏漏,直接导致后续消息无法被正常接收:
- 未处理
recv()的空返回场景:TCP是面向流的协议,当对端正常关闭连接、或者中间网络设备(防火墙、NAT网关)静默断开空闲连接时,recv()要么返回空字节b'',要么长期阻塞挂起。你的代码直接对返回值做解码,既不判断空连接,也没有超时检测机制,发完第一条消息后的空闲间隙里,连接很容易被中间网络设备丢弃会话表项,后续发送的数据包会被直接丢弃,服务端的recv()会一直阻塞,表现为收不到任何消息。 - 不规范的类属性定义:你在
ClientThread类层面直接定义conn = None、addr = None,属于所有线程实例共享的类属性,多客户端并发场景下会出现连接串扰的隐患。 - 客户端无接收逻辑:原客户端只发不收,服务端返回的任何数据(包括TCP协议栈的控制包)都会堆积在内核缓冲区,占满TCP窗口后会直接阻塞后续发送流程。
修复后可运行代码
Server.py
import socket import threading # 基础配置 ip = '0.0.0.0' port = 25565 encoding = 'utf-8' client_list = [] client_lock = threading.Lock() is_server_running = True class ClientThread(threading.Thread): def __init__(self, conn, addr): super().__init__(daemon=True) self.conn = conn self.addr = addr self.conn.settimeout(120) # 2分钟超时,自动清理僵死连接 print(f"已为客户端 {self.addr[0]}:{self.addr[1]} 创建处理线程") def run(self): while True: try: data = self.conn.recv(1024) # 核心修复:空返回值代表连接已断开 if not data: print(f"客户端 {self.addr[0]}:{self.addr[1]} 主动断开连接") break msg = data.decode(encoding) print(f"收到客户端 {self.addr[0]}:{self.addr[1]} 消息: \"{msg}\"") except socket.timeout: # 超时发送心跳,维持NAT会话表项 try: self.conn.sendall(b"__ping__") except Exception: print(f"客户端 {self.addr[0]}:{self.addr[1]} 连接超时") break except Exception as e: print(f"处理客户端 {self.addr[0]}:{self.addr[1]} 出错: {str(e)}") break # 线程安全移除客户端实例 with client_lock: if self in client_list: client_list.remove(self) self.conn.close() if __name__ == "__main__": print("正在启动服务端...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许端口快速复用 s.bind((ip, port)) s.listen(5) print(f"服务端启动完成,监听端口 {port}") while is_server_running: try: conn, addr = s.accept() print(f"收到新连接来自 {addr[0]}:{addr[1]}") client_thread = ClientThread(conn, addr) client_thread.start() with client_lock: client_list.append(client_thread) except KeyboardInterrupt: print("\n收到停止信号,正在关闭服务端") is_server_running = False break except Exception as e: print(f"监听出错: {str(e)},服务端停止") is_server_running = False break # 清理所有连接 with client_lock: for client in client_list: try: client.conn.close() except: pass s.close()
Client.py
import socket import threading encoding = 'utf-8' server_addr = "127.0.0.1" # 替换为实际服务端IP port = 25565 is_client_running = True def recv_loop(sock): """独立线程处理接收,不阻塞控制台输入""" global is_client_running while is_client_running: try: data = sock.recv(1024) if not data: print("\n服务端已断开连接") is_client_running = False break msg = data.decode(encoding) if msg != "__ping__": # 过滤心跳包 print(f"\n收到服务端消息: {msg}\n>>> ", end="") except Exception as e: print(f"\n连接异常: {str(e)}") is_client_running = False break if __name__ == "__main__": s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((server_addr, port)) print("连接服务端成功") recv_thread = threading.Thread(target=recv_loop, args=(s,), daemon=True) recv_thread.start() s.sendall("Hello !".encode(encoding)) while is_client_running: message = input(">>> ") if not message: continue try: s.sendall(message.encode(encoding)) except Exception as e: print(f"发送失败: {str(e)}") is_client_running = False break except Exception as e: print(f"连接服务端失败: {str(e)}") finally: s.close()
测试注意事项
- 先在本机用
127.0.0.1测试逻辑,确认代码正常后再做跨机器、公网测试,排除防火墙、端口映射的网络问题。 - 公网测试时确认服务端系统防火墙、云服务器安全组开放25565端口的TCP协议。
- 后续完善功能时需要加消息长度前缀解决TCP粘包问题,避免出现消息截断、多条消息拼接的异常。
内容的提问来源于stack exchange,提问作者SquidyDev
相关产品推荐
相关产品推荐

