多客户端聊天服务器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()
问题分析与修正
核心问题
msvcrt.getch()的阻塞特性:msvcrt.getch()是阻塞调用,会直接等待用户按键后才返回,导致代码在进入select()之前就被卡住,完全无法实现非阻塞逻辑。- select监听对象错误:
select()只能监听合法的文件描述符,msvcrt.getch()的返回值不是文件描述符,不能直接放入监听列表。 - 重复读取按键:在判断
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
相关产品推荐
相关产品推荐

