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

Python Socket游戏联机无数据接收问题排查求助

问题:Python游戏联机地图传输失败,客户端无法接收数据

我正在开发Python游戏的联机功能,使用pickle序列化Tile地图对象后发送给客户端,但客户端始终无法接收到数据,尽管服务器已尝试发送。相关代码如下:

服务器代码

# Handles the sending of information to clients such as the map, other unit's sprites, damage, etc
import socket
from _thread import *
import pickle
import random
from Tile import Tile
# Map gen  0 = Plain  1 = Forest  2 = Hill  3 = river   4 = Bridge
def genMap():
    map = [[Tile(0)] * 100 for _ in range(100)]
    for column in range(99): # Generate forest
        for row in range(99):
            if (random.randint(0, 100) < 2): # Centre forest tile roll
                map[column][row] = Tile(1)
                if (random.randint(0, 100) < 65 and column != 98):
                    map[column+1][row] = Tile(1)
                    map[column+2][row] = Tile(1)
                    if (random.randint(0, 100) < 45 and column != 97):
                        map[column+3][row] = Tile(1)
                if (random.randint(0, 100) < 65 and row != 98):
                    map[column][row+1] = Tile(1)
                    map[column][row+2] = Tile(1)
                    if (random.randint(0, 100) < 65 and column != 98):
                        map[column+1][row+1] = Tile(1)
                        map[column+2][row+1] = Tile(1)
                        map[column+1][row+2] = Tile(1)
                        map[column+2][row+2] = Tile(1)
                        if (random.randint(0, 100) < 45 and column != 97):
                            map[column+3][row+1] = Tile(1)
                            map[column+3][row+2] = Tile(1)
                    if (random.randint(0, 100) < 45 and row != 97):
                        map[column][row+3] = Tile(1)
                        if (random.randint(0, 100) < 65 and column != 98):
                            map[column + 1][row + 3] = Tile(1)
                            map[column + 2][row + 3] = Tile(1)
                            if (random.randint(0, 100) < 45 and column != 97):
                                map[column+3][row + 3] = Tile(1)

    for column in range(99): # Generate Hill
        for row in range(99):
            if (random.randint(0, 100) <= 0): # Centre hill tile roll
                map[column][row] = Tile(2)
                if (random.randint(0, 100) < 65 and column != 98):
                    map[column+1][row] = Tile(2)
                    map[column+2][row] = Tile(2)
                    if (random.randint(0, 100) < 45 and column != 97):
                        map[column+3][row] = Tile(2)
                if (random.randint(0, 100) < 65 and row != 98):
                    map[column][row+1] = Tile(2)
                    map[column][row+2] = Tile(2)
                    if (random.randint(0, 100) < 65 and column != 98):
                        map[column+1][row+1] = Tile(2)
                        map[column+2][row+1] = Tile(2)
                        map[column+1][row+2] = Tile(2)
                        map[column+2][row+2] = Tile(2)
                        if (random.randint(0, 100) < 45 and column != 97):
                            map[column+3][row+1] = Tile(2)
                            map[column+3][row+2] = Tile(2)
                    if (random.randint(0, 100) < 45 and row != 97):
                        map[column][row+3] = Tile(2)
                        if (random.randint(0, 100) < 65 and column != 98):
                            map[column + 1][row + 3] = Tile(2)
                            map[column + 2][row + 3] = Tile(2)
                            if (random.randint(0, 100) < 45 and column != 97):
                                map[column+3][row + 3] = Tile(2)

    for column in range(100): # Generate middle river with bridge
        if column == 20 or column == 49 or column == 50 or column == 51 or column == 80: #Bridge
            map[column][51] = Tile(4)
            map[column][50] = Tile(4)
            map[column][49] = Tile(4)
        else: #River
            map[column][51] = Tile(3)
            map[column][50] = Tile(3)
            map[column][49] = Tile(3)
    return map


server = "localhost"
port = 5555
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.bind((server, port))
except socket.error as e:
    str(e)
s.listen(2)
print("Server started, waiting for connections... ")
map = genMap()
def threaded_client(conn, player, map):
    reply = ""
    while True:
        try:
            s.sendall(pickle.dumps(map))
        except:
            break

    print("Lost connection")
    conn.close()

currentPlayer = 0
while True:
    conn, addr = s.accept()
    print("Connected to:", addr)
    start_new_thread(threaded_client, (conn, currentPlayer, map))
    currentPlayer += 1

Network客户端代码

# Handles methods used for networking
import socket
import pickle
class Network:
    def __init__(self):
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server = "localhost"
        self.port = 5555
        self.addr = (self.server, self.port)
        self.p = self.connect()
    def connect(self):
        attempts = 0
        while (attempts < 100):
            try:
                self.client.connect(self.addr)
                return pickle.loads(self.client.recv(2048))
            except:
                attempts += 1

Main客户端代码

n = Network()
map = n.p

预期客户端的map会接收服务器发送的地图,但实际为空,请求排查问题原因。


问题原因与修复方案

核心错误点

  • 服务器发送套接字错误:线程函数threaded_client中误用了监听套接字s发送数据,应该使用与客户端建立连接的conn套接字。
  • 数据接收长度限制:客户端recv(2048)仅接收2048字节,而100x100的Tile地图序列化后远大于该值,会导致数据截断,pickle.loads解析失败。
  • 无限循环发送逻辑冗余:服务器线程无限循环发送地图,既浪费资源,也可能引发粘包问题。

修正后的代码

服务器关键代码修改

def threaded_client(conn, player, map):
    try:
        # 序列化地图数据
        map_data = pickle.dumps(map)
        # 先发送数据长度(4字节固定长度),避免粘包和截断
        conn.sendall(len(map_data).to_bytes(4, byteorder='big'))
        # 发送完整地图数据
        conn.sendall(map_data)
    except Exception as e:
        print(f"发送错误: {e}")
    finally:
        print("Lost connection")
        conn.close()

客户端Network关键代码修改

def connect(self):
    attempts = 0
    while attempts < 100:
        try:
            self.client.connect(self.addr)
            # 先接收数据长度(4字节)
            length_bytes = self.client.recv(4)
            if not length_bytes:
                continue
            data_length = int.from_bytes(length_bytes, byteorder='big')
            # 循环接收所有数据,直到接收完指定长度
            received_data = b''
            while len(received_data) < data_length:
                # 每次接收最大4096字节,避免缓冲区溢出
                chunk = self.client.recv(min(4096, data_length - len(received_data)))
                if not chunk:
                    raise ConnectionError("连接中断")
                received_data += chunk
            return pickle.loads(received_data)
        except Exception as e:
            print(f"连接/接收错误: {e}")
            attempts += 1
    return None

额外注意事项

  • Tile类一致性:客户端和服务器的Tile类定义必须完全一致(包括类名、属性、导入路径),否则pickle反序列化会失败。
  • 异常处理完善:添加具体的异常打印信息,便于快速定位问题,避免静默失败。
  • 发送逻辑优化:如果仅需发送一次地图,移除服务器线程的while True循环;若需持续同步地图状态,再考虑循环发送并加入心跳机制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 08:25:19