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

