基于Socket的Python Fallout Terminal聊天室通信异常求助
Fallout Terminal聊天室发送消息后冻结问题排查与修复
问题描述
我正在开发一个功能完整的Fallout Terminal,集成了运行脚本用户间聊天的聊天室功能。临时搭建的小型聊天室起初运行正常,但来回发送约10条消息后,消息无法正常收发,最终程序陷入无响应冻结状态,寻求原因分析与修复方法。
注:return_menu()函数仅用于退出循环返回主页,不会对消息系统代码产生任何影响。
现有代码
定义代码
import socket import time def charp(userInput, speed): for char in userInput: print(char, end='') sys.stdout.flush() time.sleep(speed) e() def e(): print("")
服务器代码
def server_host(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("localhost", 9999)) server.listen() client, addr = server.accept() charp("| Save user's name as: ", 0.05) msgServer = input("") e() while True: msg = client.recv(1024).decode('utf-8') if msg == 'EXIT': client.send("EXIT".encode('utf-8')) break else: charp(msgServer + ": " + msg, 0.05) client.send(input("You: ").encode('utf-8')) client.close() server.close() return_menu()
客户端代码
def client_host(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("localhost", 9999)) charp("| Save user's name as: ", 0.05) msgClient = input("") e() while True: client.send(input("You: ").encode('utf-8')) msg = client.recv(1024).decode('utf-8') if msg == "EXIT": client.send("EXIT".encode('utf-8')) break else: charp(msgClient + ": " + msg, 0.05) client.close() return_menu()
问题原因分析
- TCP粘包/拆包问题:TCP是字节流协议,
recv(1024)无法保证每次都能拿到完整的一条消息。当消息长度超过1024字节,或多条消息被合并发送时,会导致某次recv只获取到部分消息,后续逻辑混乱,引发双方互相等待(一方等recv完整消息,另一方等input输入),最终死锁冻结。 - 缺少sys模块导入:
charp函数中调用了sys.stdout.flush(),但代码未导入sys模块,可能在运行时抛出异常,干扰消息流转。 - 同步收发逻辑缺陷:当前代码采用严格的"发-收-发"同步逻辑,一旦某次消息接收不完整,整个流程就会阻塞,无法继续处理后续消息。
修复方案与修改后代码
核心修复点
- 给每条消息添加边界标记(如换行符
\n),接收时循环读取直到获取完整的一条消息,解决粘包/拆包问题。 - 补全sys模块导入,避免函数执行异常。
- 使用try-finally确保socket资源正常关闭,避免资源泄漏。
- 可选:使用多线程分离收发逻辑,避免输入时阻塞消息接收。
修改后代码
定义代码(补全导入)
import socket import time import sys # 新增sys模块导入 def charp(userInput, speed): for char in userInput: print(char, end='') sys.stdout.flush() time.sleep(speed) e() def e(): print("")
服务器代码(修复收发逻辑)
def server_host(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("localhost", 9999)) server.listen() client, addr = server.accept() charp("| Save user's name as: ", 0.05) msgServer = input("") e() try: while True: # 循环接收直到获取完整的一行消息 msg_buffer = b"" while b"\n" not in msg_buffer: chunk = client.recv(1024) if not chunk: # 客户端断开连接 break msg_buffer += chunk msg = msg_buffer.decode('utf-8').strip() if msg == 'EXIT': client.send("EXIT\n".encode('utf-8')) break charp(f"{msgServer}: {msg}", 0.05) # 发送消息时添加换行标记 send_msg = input("You: ") client.send(f"{send_msg}\n".encode('utf-8')) finally: client.close() server.close() return_menu()
客户端代码(修复收发逻辑)
def client_host(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("localhost", 9999)) charp("| Save user's name as: ", 0.05) msgClient = input("") e() try: while True: send_msg = input("You: ") client.send(f"{send_msg}\n".encode('utf-8')) # 循环接收直到获取完整的一行消息 msg_buffer = b"" while b"\n" not in msg_buffer: chunk = client.recv(1024) if not chunk: # 服务器断开连接 break msg_buffer += chunk msg = msg_buffer.decode('utf-8').strip() if msg == "EXIT": client.send("EXIT\n".encode('utf-8')) break charp(f"{msgClient}: {msg}", 0.05) finally: client.close() return_menu()
可选优化:多线程分离收发
如果希望在输入消息时也能实时接收对方消息,避免阻塞,可以用多线程实现:
客户端多线程版本
import threading def receive_messages(client, msgClient): try: while True: msg_buffer = b"" while b"\n" not in msg_buffer: chunk = client.recv(1024) if not chunk: charp("对方已断开连接", 0.05) break msg_buffer += chunk msg = msg_buffer.decode('utf-8').strip() if msg == "EXIT": charp("对方已退出聊天", 0.05) break charp(f"{msgClient}: {msg}", 0.05) finally: client.close() return_menu() def client_host(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("localhost", 9999)) charp("| Save user's name as: ", 0.05) msgClient = input("") e() # 启动接收线程 recv_thread = threading.Thread(target=receive_messages, args=(client, msgClient)) recv_thread.daemon = True recv_thread.start() try: while True: send_msg = input("You: ") client.send(f"{send_msg}\n".encode('utf-8')) if send_msg == "EXIT": break finally: client.close()
服务器的多线程版本可以参照客户端逻辑实现,这里不再赘述。
内容的提问来源于stack exchange,提问作者TrickyTaco11
相关产品推荐
相关产品推荐

