如何在不使用线程的情况下将select.select与Pygame结合实现客户端消息发送?
我来帮你搞定这个问题!核心痛点就是select.select()默认会一直阻塞到有IO事件触发,直接卡死了Pygame的事件循环和窗口渲染。不用线程的话,我们只需要给select加个超时时间,让它每隔一小段时间就“醒过来”一次,这样就能同时兼顾Pygame的交互和网络操作了。
先给你拆解下关键思路:
- 把socket设为非阻塞模式,避免send/recv操作本身卡住程序
- 给
select.select()设置一个极短的超时(比如0.01秒),让它不会一直阻塞 - 在Pygame的主循环里,先处理窗口事件,再处理网络IO,最后刷新窗口,保证窗口流畅
下面是修改后的完整可运行代码,我给你加了详细注释:
import pygame import socket import select # 初始化Pygame窗口 pygame.init() screen = pygame.display.set_mode((450, 350)) pygame.display.set_caption("Pygame 网络客户端") font = pygame.font.Font(None, 32) # 初始化网络连接(替换成你的服务器IP和端口) client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(("127.0.0.1", 12345)) client_socket.setblocking(False) # 关键:设置为非阻塞模式,避免IO操作卡主 messages_to_send = [] open_client_sockets = [client_socket] def send_waiting_messages(wlist): """发送等待队列里的消息,只处理当前可写的socket""" # 记录要删除的已发送消息索引(倒序删除避免索引混乱) messages_to_remove = [] for idx, (sock, data) in enumerate(messages_to_send): if sock in wlist: try: # 发送消息(非阻塞模式下如果暂时发不出去会抛异常,我们后面再重试) sent_bytes = sock.send(data.encode("utf-8")) if sent_bytes == len(data): # 消息完全发送成功,标记删除 messages_to_remove.append(idx) else: # 部分发送,更新剩余要发送的内容 messages_to_send[idx] = (sock, data[sent_bytes:]) except Exception: # 发送失败(比如连接断开),后续处理 messages_to_remove.append(idx) # 移除已发送/失效的消息 for idx in reversed(messages_to_remove): del messages_to_send[idx] # Pygame主循环 running = True input_text = "" # 用来存储用户输入的文本 while running: # 1. 先处理Pygame窗口事件(必须优先处理,不然窗口会假死) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: # 按下回车,把输入的文本加入发送队列 if input_text.strip(): messages_to_send.append((client_socket, input_text)) input_text = "" elif event.key == pygame.K_BACKSPACE: # 退格删除输入文本 input_text = input_text[:-1] else: # 普通按键,追加到输入文本 input_text += event.unicode # 2. 处理网络IO:用select监听socket,超时0.01秒避免阻塞 rlist, wlist, xlist = select.select(open_client_sockets, open_client_sockets, [], 0.01) # 处理可读的socket(接收服务器消息) for sock in rlist: try: data = sock.recv(1024) if not data: # 服务器断开连接 print("服务器已断开连接") sock.close() open_client_sockets.remove(sock) else: print(f"收到消息:{data.decode('utf-8')}") except Exception as e: print(f"接收消息出错:{e}") sock.close() open_client_sockets.remove(sock) # 发送等待队列里的消息 send_waiting_messages(wlist) # 3. 更新并渲染Pygame窗口 screen.fill((240, 240, 240)) # 渲染输入提示和当前输入文本 prompt_text = font.render("输入消息,按回车发送:", True, (30, 30, 30)) input_display = font.render(input_text, True, (0, 0, 200)) screen.blit(prompt_text, (20, 20)) screen.blit(input_display, (20, 60)) # 渲染等待发送的消息提示 if messages_to_send: pending_text = font.render(f"等待发送的消息:{len(messages_to_send)}条", True, (150, 0, 0)) screen.blit(pending_text, (20, 100)) # 更新窗口显示 pygame.display.flip() # 程序结束,清理资源 client_socket.close() pygame.quit()
几个关键细节说明:
- 非阻塞socket:
client_socket.setblocking(False)是必须的,这样即使socket暂时无法发送/接收数据,也不会卡住程序,而是抛出异常让我们可以后续处理。 - select超时设置:
select.select(..., 0.01)让select最多等待10毫秒就返回,这样Pygame的事件循环能及时响应,窗口不会出现卡顿或假死。 - 部分发送处理:如果消息比较大,一次send可能发不完,代码里处理了这种情况——只删除完全发送成功的消息,未发送完的部分会留在队列里下次继续发。
内容的提问来源于stack exchange,提问作者Lior shem
相关产品推荐
相关产品推荐

