Python单客户端Socket问答游戏转多客户端改造求助
多客户端问答游戏服务端问题修复
你的代码核心问题是混合使用多线程与非阻塞Selector模型,再加上逻辑漏洞,导致出现ResourceIO错误和连接限制异常。以下是具体问题分析和修复方案:
核心问题点
- 非阻塞套接字在多线程中调用
recv()/send()会直接抛出BlockingIOError(即你遇到的ResourceIO错误),因为非阻塞模式下无数据时不会等待,直接报错。 - 全局变量
CURR_CLIENTS仅在新连接时递增,客户端断开时未递减,导致服务端达到3个连接后,即便有客户端退出也不再接受新请求。 handle_client函数外层冗余的while True会重复发送欢迎消息;同时未处理客户端主动断开的场景(recv()返回空字节串)。- 冗余的Selector代码完全未实际使用,反而造成逻辑混乱。
修复后的完整代码
import sys import threading import socket MAX_CLIENTS = 3 CURR_CLIENTS = 0 # 用锁保证客户端计数的线程安全 client_lock = threading.Lock() class ClientData: def __init__(self, conn, addr): self.conn = conn self.addr = addr self.money = 0 self.phase = 0 self.life_saver = True self.question_num = 0 self.chaser_phase = 0 self.status = 1 def initialize_client_data(client_data): # 初始化玩家数据逻辑 pass def handle_client(client_data): global CURR_CLIENTS try: print(f"[NEW CONNECTION] - {client_data.addr}") # 发送初始欢迎消息 client_data.conn.send("Welcome to the game!\nDo you want to play? y/n \n>>".encode()) connected = True while connected: # 多线程下用阻塞接收更稳定 msg = client_data.conn.recv(1024).decode().strip() if not msg: # 客户端主动断开连接 print(f"[DISCONNECTED] - {client_data.addr}") connected = False break print(f"[{client_data.addr}] {msg}") if msg.lower() == 'n': connected = False client_data.conn.send("Bye Bye".encode()) print(f"[DISCONNECTED] - {client_data.addr}") else: initialize_client_data(client_data) phase_A(client_data) # 游戏主循环:检查玩家状态 while client_data.status == 1: # 游戏结束判定逻辑 pass except Exception as e: print(f"[ERROR] {client_data.addr}: {str(e)}") finally: # 确保连接关闭,更新客户端计数 client_data.conn.close() with client_lock: CURR_CLIENTS -= 1 print(f"[ACTIVE CONNECTIONS] {CURR_CLIENTS}") def send_question(client_data): # 向客户端发送问题逻辑 pass def chaser_answer(client_data, question_num): # 电脑随机回答逻辑 pass def client_answer(client_data, question_num): # 发送问题并接收玩家回答逻辑 pass def phase_A(client_data): # 游戏Phase A逻辑 pass def phase_B(client_data): # 游戏Phase B逻辑 pass def start(): global CURR_CLIENTS server.listen() print(f"Listening on {host}:{port}") while True: try: conn, addr = server.accept() with client_lock: if CURR_CLIENTS >= MAX_CLIENTS: # 达到最大连接数,拒绝新请求 conn.send("Server is full, try later.".encode()) conn.close() print(f"[REJECTED CONNECTION] - {addr}") continue CURR_CLIENTS += 1 print(f"[ACTIVE CONNECTIONS] {CURR_CLIENTS}") # 每个客户端启动独立线程处理 client_data = ClientData(conn, addr) thread = threading.Thread(target=handle_client, args=(client_data,), daemon=True) thread.start() except Exception as e: print(f"[SERVER ERROR] {str(e)}") if __name__ == "__main__": if len(sys.argv) != 3: print("Usage: python server.py <host> <port>") sys.exit(1) host, port = sys.argv[1], int(sys.argv[2]) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许端口快速复用,避免服务重启后端口占用 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((host, port)) start()
关键修改说明
- 移除冗余Selector逻辑:改用纯多线程模型,每个客户端对应独立线程,逻辑更简洁稳定。
- 线程安全计数:通过
threading.Lock保证客户端计数的原子操作,避免多线程下的计数错误。 - 阻塞套接字:去掉非阻塞设置,多线程环境下阻塞接收/发送更可靠,不会触发IO异常。
- 完善断开处理:检测客户端主动断开的场景,并在
finally块中确保连接关闭和计数更新。 - 超额连接拒绝:达到最大连接数时,直接拒绝新请求并返回提示。
- 守护线程设置:客户端线程设为守护线程,服务端退出时自动销毁线程,避免资源泄漏。
内容的提问来源于stack exchange,提问作者roni naor
相关产品推荐
相关产品推荐

