You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python双人聊天程序需按回车才能接收消息:实时接收实现与代码问题排查求助

嘿,我来帮你搞定这个实时接收消息的问题!你的脚本里第二个用户必须按回车才能看到消息,核心原因是客户端的input()函数阻塞了整个程序流程——它会一直卡着等用户输入,直到按下回车才会去检查服务器有没有发新消息。咱们来修改客户端代码,让它能同时监听用户输入和服务器消息,实现真正的实时接收。

问题根源拆解

你当前的客户端主循环是这样的逻辑:

while True:
    # 这里会阻塞,直到用户按下回车
    message = input(f"{my_username} > ")
    # 发送消息...
    # 然后才去接收服务器消息
    try:
        while True:
            # 接收消息...
    except ...:
        ...

也就是说,在你按下回车之前,程序根本不会进入接收消息的代码块,自然看不到别人发的消息。

解决方案:用select同时监听输入和Socket

我们可以用Python的select模块,让程序同时等待两个事件:用户输入(标准输入)Socket的 incoming 消息。这样不管是用户要发消息,还是服务器有新消息过来,程序都能立刻响应。

下面是修改后的完整client.py代码:

import sys
import errno
import select
import socket
from pip._vendor.colorama import Fore

# 注意:你原来的变量名拼写错误,是HEADER_LENGTH不是HEADER_LENGHT
HEADER_LENGTH = 10
IP = "localhost"
PORT = 2021

print(Fore.WHITE+"|"+Fore.YELLOW+"chat"+Fore.WHITE+"|")
print()

my_username = input(Fore.WHITE+"|"+Fore.YELLOW+"Username"+Fore.WHITE+"|"+ Fore.WHITE+" > ")

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((IP, PORT))
client_socket.setblocking(False)

# 发送用户名到服务器
username = my_username.encode("utf-8")
username_header = f"{len(username): <{HEADER_LENGTH}}".encode("utf-8")
client_socket.send(username_header + username)

# 先打印一次输入提示符,让用户知道可以开始输入了
print(f"{my_username} > ", end="", flush=True)

while True:
    # 用select监听socket和标准输入,等待任意一个可读
    read_sockets, _, _ = select.select([client_socket, sys.stdin], [], [])
    
    for notified_socket in read_sockets:
        if notified_socket == client_socket:
            # 处理服务器发来的新消息
            try:
                # 接收用户名头和用户名
                username_header = client_socket.recv(HEADER_LENGTH)
                if not len(username_header):
                    print("\nConnection closed by the server")
                    sys.exit()
                username_length = int(username_header.decode("utf-8").strip())
                username = client_socket.recv(username_length).decode("utf-8")

                # 接收消息头和消息内容
                message_header = client_socket.recv(HEADER_LENGTH)
                message_length = int(message_header.decode("utf-8").strip())
                message = client_socket.recv(message_length).decode("utf-8")

                # 打印消息:先换行避免覆盖输入提示符,然后重新显示提示符
                print(f"\n{username} > {message}")
                print(f"{my_username} > ", end="", flush=True)
            except IOError as e:
                if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
                    print('\nReading error:', str(e))
                    sys.exit()
                continue
            except Exception as e:
                print('\nGeneral error:', str(e))
                sys.exit()
        else:
            # 处理用户输入的消息
            message = sys.stdin.readline().strip()
            if message:
                # 发送消息到服务器
                message_encoded = message.encode('utf-8')
                message_header = f"{len(message_encoded): <{HEADER_LENGTH}}".encode("utf-8")
                client_socket.send(message_header + message_encoded)
            # 重新显示输入提示符,让用户继续输入
            print(f"{my_username} > ", end="", flush=True)

关键修改说明

  1. select实现非阻塞监听
    • select.select([client_socket, sys.stdin], [], [])会让程序暂停,直到其中一个对象(socket或标准输入)有数据可读,不会再卡在input()上。
  2. 分开处理两种事件
    • 当socket可读时,立即接收并打印服务器消息;
    • 当标准输入可读时,读取用户输入并发送消息。
  3. 优化用户体验
    • 收到消息时先换行,避免和当前的输入提示符混在一起;
    • 打印完消息后重新显示输入提示符,让用户可以继续输入;
    • 使用flush=True确保提示符立即显示,不会被缓冲区延迟。
  4. 修复变量拼写错误:把HEADER_LENGHT改成HEADER_LENGTH,和服务器端保持一致,避免潜在的bug。

服务器端的小修正

别忘了把服务器端的HEADER_LENGHT也改成HEADER_LENGTH,确保两端的消息头长度一致。

现在启动服务器和两个客户端,你会发现不用按回车,就能实时收到对方发送的消息啦!

内容的提问来源于stack exchange,提问作者Bünyamin Efe

火山引擎 最新活动