如何让Python TCP/IP Socket服务器支持多客户端连接?
问题
我需要编写一个小型Python程序,实现TCP/IP客户端-服务器连接并运行二十问游戏:
- 服务器生成1到100之间的随机数
- 允许多个客户端连接,客户端轮流进行猜测
- 服务器向客户端返回反馈:客户端发送结构体(比如
('<', 50)表示猜测目标数字小于50),服务器验证后返回字符('I'正确,'N'错误,'Y'获胜,'V'失败(已有人赢),'K'失败且还有其他客户端在游戏) - 客户端基于二分法猜测,暂时不处理目标数字在猜测范围的情况,这不是当前重点
当前服务器在一个客户端连接后会完成整个游戏流程,无法等待其他客户端连接,请问如何让服务器处理多个客户端?
原server.py代码
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR import select import struct import random server_addr = ('', 10000) unpacker = struct.Struct('c I') correct_num = random.randint(1, 100) answer = '' print('Correct number:', correct_num) with socket(AF_INET, SOCK_STREAM) as server: server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) server.bind(server_addr) server.listen(5) sockets = [server] while True: readables, writables, exceptionals = select.select(sockets, sockets, sockets, 1) if not (readables or writables or exceptionals): continue for s in readables: if s is server: # new client connect client_conn, client_addr = s.accept() client_conn.setblocking(0) print('Connected:', client_addr) sockets.append(client_conn) else: # handle client data = s.recv(unpacker.size) if not data: sockets.remove(s) s.close() print('Exited') else: print('Received:', data) unp_data = unpacker.unpack(data) print('Unpack:', unp_data) if answer == 'Y': answer = 'V' else: if unp_data[0].decode() == '=': if int(unp_data[1]) == correct_num: answer = 'Y' else: answer = 'K' elif unp_data[0].decode() == '>': if correct_num > int(unp_data[1]): answer = 'I' else: answer = 'N' elif unp_data[0].decode() == '<': if correct_num < int(unp_data[1]): answer = 'I' else: answer = 'N' print('Evaluated and sent back', answer) s.sendall(str(answer).encode())
原client.py代码
from socket import socket, AF_INET, SOCK_STREAM import struct import random server_addr = ('localhost', 10000) packer = struct.Struct('c I') operators = ['<', '>', '='] last_num = last_n_num = last_i_num = 0 last_op = last_n_op = last_i_op = '' with socket(AF_INET, SOCK_STREAM) as client: client.connect(server_addr) lowest_num = 1 highest_num = 100 mid = lowest_num + (highest_num - lowest_num) // 2 sent_op = operators[mid % (len(operators) - 1)] range_nums = [mid] packed_data = packer.pack(sent_op.encode(), int(mid)) print('Client make starting guess') client.sendall(packed_data) while True: data = client.recv(1).decode() if data == 'V': print('Someone else won\nClient exits') client.close() exit(0) elif data == 'Y': print('Client won') client.close() exit(0) elif data == 'K': print('Client lose') client.close() exit(0) else: if data == 'I': if sent_op == '<': highest_num = mid - 1 elif sent_op == '>': lowest_num = mid + 1 if data == 'N': if sent_op == '<': lowest_num = mid + 1 elif sent_op == '>': highest_num = mid - 1 mid = lowest_num + (highest_num - lowest_num) // 2 if mid in range_nums: sent_op = '=' else: range_nums.append(mid) if lowest_num >= highest_num: sent_op = '=' else: sent_op = operators[mid % (len(operators) - 1)] print('Client make another guess') packed_data = packer.pack(sent_op.encode(), int(mid)) client.sendall(packed_data)
解决方案
你的核心问题是没有维护游戏轮次顺序和全局状态隔离,导致单个客户端会连续占用服务器处理资源。以下是修改后的实现:
修改思路
- 用队列维护客户端连接,确保轮流处理请求
- 用独立变量标记游戏是否结束、谁是获胜者,避免全局
answer的状态污染 - 只处理当前轮到的客户端请求,其他客户端的请求暂时挂起直到轮到它们
修改后的server.py代码
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR import select import struct import random from collections import deque server_addr = ('', 10000) unpacker = struct.Struct('c I') # 游戏全局状态 correct_num = random.randint(1, 100) game_over = False winner = None # 客户端队列,用于控制轮流猜测顺序 client_queue = deque() print('Correct number:', correct_num) with socket(AF_INET, SOCK_STREAM) as server: server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) server.bind(server_addr) server.listen(5) sockets = [server] while True: readables, writables, exceptionals = select.select(sockets, sockets, sockets, 1) if not (readables or writables or exceptionals): continue for s in readables: if s is server: # 处理新客户端连接 client_conn, client_addr = s.accept() client_conn.setblocking(0) print('Connected:', client_addr) sockets.append(client_conn) client_queue.append(client_conn) else: # 处理客户端请求 # 游戏未结束时,只处理队列头部的客户端请求(当前轮到的玩家) if not game_over and s != client_queue[0]: continue data = s.recv(unpacker.size) if not data: # 客户端断开连接,清理资源 sockets.remove(s) if s in client_queue: client_queue.remove(s) s.close() print('Exited:', s.getpeername()) continue if game_over: # 游戏已结束,所有后续请求返回'V' s.sendall(b'V') continue # 解析客户端请求 print('Received from', s.getpeername(), ':', data) unp_data = unpacker.unpack(data) op = unp_data[0].decode() num = int(unp_data[1]) response = '' # 验证猜测并生成响应 if op == '=': if num == correct_num: response = 'Y' game_over = True winner = s else: response = 'K' elif op == '>': response = 'I' if correct_num > num else 'N' elif op == '<': response = 'I' if correct_num < num else 'N' print('Send to', s.getpeername(), ':', response) s.sendall(response.encode()) # 游戏未结束时,切换到下一个客户端(猜错的玩家继续轮次可自行调整逻辑) if not game_over and response != 'K': client_queue.rotate(-1)
修改说明
- 客户端队列:用
deque实现高效的轮次切换,每次处理完当前客户端的有效请求后,将队列旋转,轮到下一个客户端 - 状态隔离:用
game_over和winner明确标记游戏状态,避免全局变量的混乱 - 请求过滤:非当前轮次的客户端请求会被暂时忽略,确保严格轮流猜测
- 断开处理:客户端断开时从队列和socket列表中移除,避免无效连接占用资源
客户端代码无需修改,可直接配合修改后的服务器运行。
内容的提问来源于stack exchange,提问作者Zoltán Orosz
相关产品推荐
相关产品推荐

