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

如何让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)

解决方案

你的核心问题是没有维护游戏轮次顺序和全局状态隔离,导致单个客户端会连续占用服务器处理资源。以下是修改后的实现:

修改思路

  1. 用队列维护客户端连接,确保轮流处理请求
  2. 用独立变量标记游戏是否结束、谁是获胜者,避免全局answer的状态污染
  3. 只处理当前轮到的客户端请求,其他客户端的请求暂时挂起直到轮到它们

修改后的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 21:10:35