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

Python Socket服务端仅能接收客户端首条消息异常排查

问题排查与修复方案

核心问题原因

你的代码存在两个典型的TCP Socket编程疏漏,直接导致后续消息无法被正常接收:

  1. 未处理recv()的空返回场景:TCP是面向流的协议,当对端正常关闭连接、或者中间网络设备(防火墙、NAT网关)静默断开空闲连接时,recv()要么返回空字节b'',要么长期阻塞挂起。你的代码直接对返回值做解码,既不判断空连接,也没有超时检测机制,发完第一条消息后的空闲间隙里,连接很容易被中间网络设备丢弃会话表项,后续发送的数据包会被直接丢弃,服务端的recv()会一直阻塞,表现为收不到任何消息。
  2. 不规范的类属性定义:你在ClientThread类层面直接定义conn = None、addr = None,属于所有线程实例共享的类属性,多客户端并发场景下会出现连接串扰的隐患。
  3. 客户端无接收逻辑:原客户端只发不收,服务端返回的任何数据(包括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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:18:25