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

如何在不使用线程的情况下将select.select与Pygame结合实现客户端消息发送?

我来帮你搞定这个问题!核心痛点就是select.select()默认会一直阻塞到有IO事件触发,直接卡死了Pygame的事件循环和窗口渲染。不用线程的话,我们只需要给select加个超时时间,让它每隔一小段时间就“醒过来”一次,这样就能同时兼顾Pygame的交互和网络操作了。

先给你拆解下关键思路:

  1. 把socket设为非阻塞模式,避免send/recv操作本身卡住程序
  2. 给select.select()设置一个极短的超时(比如0.01秒),让它不会一直阻塞
  3. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:06:44