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

多客户端聊天服务器select问题:客户端select调用阻塞调试求助

客户端select()停滞问题排查

我尝试在客户端使用非阻塞函数(select和msvcrt)实现聊天功能,但调试时执行到select()代码行便停滞,仿照服务器端写法编写了select监听列表的循环,仍无法实现非阻塞功能。


服务器代码

import socket
import select

SERVER_IP = '127.0.0.1'
SERVER_PORT = 33333
SERVER_MSG_LENGTH = 6

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((SERVER_IP, SERVER_PORT))
server_socket.listen(5)

open_client_socket = []
msg_to_send = []


def send_waiting_msg(wlist, sender_socket):
    for message in msg_to_send:
        (client_socket, data) = message
        length = str(len(data)).zfill(6)
        for client_socket in wlist:
            if client_socket != sender_socket:
                msg = length + data
                client_socket.send(msg.encode())
        msg_to_send.remove(message)


def main():
    flag = True
    print("server is running and listening...")
    while flag:
        rlist, wlist, xlist = select.select([server_socket] + open_client_socket, open_client_socket, [])
        for current_socket in rlist:
            if current_socket is server_socket:
                client_socket, address = server_socket.accept()
                open_client_socket.append(client_socket)
                print("connection with {} has been made".format(address))
            else:
                length = current_socket.recv(SERVER_MSG_LENGTH).decode()
                try:
                    data = current_socket.recv(int(length)).decode()
                    if data == "quit":
                        open_client_socket.remove(current_socket)
                        print("connection has been closed")
                    else:
                        print(data)
                        msg_to_send.append((current_socket, data))
                        send_waiting_msg(wlist, current_socket)
                except Exception as err:
                    print(err)
                    exit(-1)
            if len(open_client_socket) == 0:
                flag = False
    server_socket.close()

原客户端代码

import socket
import msvcrt
import select

SERVER_HOST = '127.0.0.1'
SERVER_PORT = 33333
SERVER_MSG_LENGTH = 6

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock.connect((SERVER_HOST, SERVER_PORT))

open_client_socket = []


def main():
    flag = True
    while flag:
        ready_to_read, _, _ = select.select([client_sock, msvcrt.getch()], [], [])
        for source in ready_to_read:
            if source == client_sock:
                try:
                    length = client_sock.recv(SERVER_MSG_LENGTH)
                    print("send")
                    data = client_sock.recv(int(length)).decode()
                    print(data)
                except Exception as err:
                    print(err)
                    exit(-1)

            elif source == msvcrt.getch():
                send = msvcrt.getch()
                if send == b'quit':
                    flag = False
                length = str(len(send)).zfill(SERVER_MSG_LENGTH)
                msg = length + send.decode()
                client_sock.send(msg.encode())
    client_sock.close()

问题分析与修正

核心问题

  1. msvcrt.getch()的阻塞特性:msvcrt.getch()是阻塞调用,会直接等待用户按键后才返回,导致代码在进入select()之前就被卡住,完全无法实现非阻塞逻辑。
  2. select监听对象错误:select()只能监听合法的文件描述符,msvcrt.getch()的返回值不是文件描述符,不能直接放入监听列表。
  3. 重复读取按键:在判断source时再次调用msvcrt.getch(),会导致按键被重复读取,出现逻辑混乱。

修正后的客户端代码

import socket
import select
import sys

SERVER_HOST = '127.0.0.1'
SERVER_PORT = 33333
SERVER_MSG_LENGTH = 6

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock.connect((SERVER_HOST, SERVER_PORT))

def main():
    flag = True
    # 监听socket和标准输入的文件描述符
    listen_fds = [client_sock.fileno(), sys.stdin.fileno()]
    while flag:
        ready_to_read, _, _ = select.select(listen_fds, [], [])
        for fd in ready_to_read:
            if fd == client_sock.fileno():
                # 处理服务器消息
                try:
                    length = client_sock.recv(SERVER_MSG_LENGTH).decode()
                    if not length:
                        print("服务器断开连接")
                        flag = False
                        break
                    data = client_sock.recv(int(length)).decode()
                    print(f"收到消息: {data}")
                except Exception as err:
                    print(f"接收错误: {err}")
                    flag = False
                    break
            elif fd == sys.stdin.fileno():
                # 处理用户输入
                send = sys.stdin.readline().strip()
                if send == "quit":
                    flag = False
                # 发送消息
                length = str(len(send)).zfill(SERVER_MSG_LENGTH)
                msg = length + send
                client_sock.send(msg.encode())
    client_sock.close()

if __name__ == "__main__":
    main()

关键修改说明

  • 改用sys.stdin.fileno()作为标准输入的文件描述符,配合select()实现非阻塞监听。
  • 用sys.stdin.readline()读取用户输入,避免msvcrt.getch()的阻塞问题,同时支持完整输入(原代码只能读取单个字符)。
  • 增加了异常处理逻辑,例如服务器断开时的判断,提升稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 16:05:21