Python Socket聊天程序客户端仅能接收一条消息的问题排查
问题描述
我用Python Socket开发了一款聊天程序,聊天窗口(window3)打开后,双方通过ms!{target_id}|{message}格式发送消息,服务器会以sm!{message}格式转发给目标客户端。但现在出现异常:其中一方只能接收对方发送的一条消息,服务器能接收所有消息并尝试转发,但客户端接收异常。
代码片段
import os import threading import PySimpleGUI as gui from rsa_controller import decryptwithPrivatekey, loadPublicKey, loadPrivateKey id = 0 target_id = 0 prikey = None window2_active = False # Indicates whether the second window is open window = None window2 = None window3 = None exit_flag = False # Indicates whether the program should exit pubkey = None prikey = None target_pubkey = None restart_thread = False def popout(title): gui.popup(title) def read_keys(): global pubkey, prikey print("Opening key files" + os.getcwd() + "\\keys\\") pubkey = loadPublicKey(os.getcwd() + "\\keys\\public.pem") prikey = loadPrivateKey(os.getcwd() + "\\keys\\private.pem") def recv_msg(): global target_id, window2_active, exit_flag global target_pubkey from main import s while not exit_flag: try: print("BEAT") data = s.recv(1024) if not data: break decoded_data = data.decode('utf-8') print("Received message:", decoded_data) if decoded_data == 'target_connected_Success': print("Received message:", decoded_data) elif decoded_data.startswith("!+@"): target_id = decoded_data[3:] if not window2_active: window.write_event_value('open_window2', None) elif decoded_data == 'target_connect_denied': gui.popup('Connection request denied') exit_flag = True elif decoded_data.startswith("pu!"): parts = decoded_data.split("|") target_id = parts[0][3:] target_pubkey = parts[1] print("Received the public key of the other party:", target_pubkey) window.write_event_value('open_window3', None) elif decoded_data.startswith("sm!"): message = decoded_data[3:] window3['text_output'].print("{}:{}".format(target_id, message)) continue else: msg_to_recv = decryptwithPrivateKey(decoded_data, prikey) print("Received message:", msg_to_recv) except Exception as e: print("Exception occurred while receiving message:", e) def window2_event_handler(event): global window2_active, exit_flag global pubkey from main import s if event == 'denied': print("Connection denied") s.send('!x!{}'.format(target_id).encode('utf-8')) window2_active = False exit_flag = True elif event == 'accept': print("Confirm sharing my public key and establish connection") # Handle the logic for confirming the connection s.send("mp!{}|{}".format(target_id, pubkey).encode()) window2_active = False exit_flag = True def window2(): from main import s global target_id, window2_active, exit_flag layout2 = [ [gui.Text('Connecting with'), gui.Text(str(target_id), key='target_id'), gui.Text("...")], [gui.Button('Confirm sharing my public key and establish connection', key='accept', enable_events=True), gui.Button('Deny connection request', key='denied', enable_events=True)] ] window2_active = True window2 = gui.Window("Connection Request", layout2, finalize=True, no_titlebar=True) while True: event2, values2 = window2.read() if event2 == gui.WINDOW_CLOSED: break window2_event_handler(event2) if exit_flag: break window2.close() def window3(): from main import s layout3 = [ [gui.Multiline(size=(None, 10), key='text_output', disabled=True, autoscroll=True)], [gui.Input(size=(None, 1), key='text_input'), gui.Button('Send', key='send')] ] global window3 window3 = gui.Window("Chat Window-{}".format(target_id), layout3, finalize=True) exit_flag_window3 = False # Exit flag for window3 while True: event3, values3 = window3.read() if event3 == gui.WINDOW_CLOSED: exit_flag_window3 = True # Set the exit flag for window3 to True break # Exit the event loop if event3 == 'send': message = values3['text_input'] # Handle the sending logic window3['text_output'].print(f'You: {message}') s.send(f"ms!{target_id}|{message}".encode('utf-8')) window3['text_input'].update('') # Clear the input field window3.close() # When window3 is closed, set the exit flag to True and wait for the event loop in the thread to exit exit_flag = True while threading.active_count() > 1: pass def start_GUI_progress(id): from main import s global restart_thread read_keys() layout = [ [gui.Text('Your ID'), gui.Text(id)], [gui.Text('Note: Please enter the target ID in the input box below and click the Connect button')], [gui.Input(key='target_id'), gui.Button('Connect', key='connect')] ] global window window = gui.Window("RSA Encrypted Instant Messaging", layout, finalize=True) host = "localhost" port = 23333 s.connect((host, port)) print(s.recv(1024)) t_recv = threading.Thread(target=recv_msg) t_recv.start() # Send the ID s.send(b"__!" + str(id).encode('utf-8')) while True: event, values = window.read() if event == gui.WINDOW_CLOSED: break if not t_recv.is_alive(): print("Thread DEAD!!") if event == 'connect': print("Client starts to attempt connection: {}".format(values['target_id'])) # Construct the message to send message = "_!?{}".format(values['target_id']) # Send the message to the server s.send(message.encode('utf-8')) message = "mp!{}|{}".format(values['target_id'], pubkey) s.send(message.encode('utf-8')) elif event == 'open_window2': window2() elif event == 'open_window3': window3() elif event == gui.WINDOW_CLOSE_ATTEMPTED_EVENT: break window.close()
问题分析与修复方案
核心问题
- 非主线程直接操作GUI控件:PySimpleGUI是单线程GUI框架,
recv_msg线程直接调用window3['text_output'].print()会触发线程安全异常,导致接收线程卡住或崩溃,无法继续接收后续消息。 exit_flag作用域错误:window3函数中设置exit_flag = True时未声明global,实际修改的是局部变量,无法终止recv_msg线程的循环。- 已关闭窗口的访问问题:
window3关闭后,recv_msg线程仍尝试访问该窗口对象,抛出异常导致线程终止。
修复步骤
1. 用事件通知主线程更新UI
修改recv_msg中处理sm!消息的逻辑,通过事件把消息传递给GUI主线程处理:
elif decoded_data.startswith("sm!"): message = decoded_data[3:] # 发送自定义事件给主线程,避免跨线程操作GUI window.write_event_value('recv_chat_msg', (target_id, message)) continue
在start_GUI_progress的事件循环中添加该事件的处理:
elif event == 'recv_chat_msg': target_id, msg = values[event] # 检查window3是否存在且未关闭 if window3 and not window3.was_closed(): window3['text_output'].print(f"{target_id}: {msg}")
2. 修正exit_flag的作用域
在window3函数中修改exit_flag时,必须声明global:
# When window3 is closed, set the exit flag to True and wait for the event loop in the thread to exit global exit_flag exit_flag = True
3. 避免访问已关闭的窗口
在recv_msg中操作窗口前,先检查窗口状态:
elif decoded_data.startswith("pu!"): parts = decoded_data.split("|") target_id = parts[0][3:] target_pubkey = parts[1] print("Received the public key of the other party:", target_pubkey) # 确保主窗口未关闭再发送事件 if window and not window.was_closed(): window.write_event_value('open_window3', None)
4. 可选:处理Socket粘包问题
Socket的recv(1024)可能一次性接收多条消息或拆分消息,导致解析错误。可以给每条消息添加换行符作为分隔符,修改接收逻辑:
def recv_msg(): global target_id, window2_active, exit_flag global target_pubkey from main import s buffer = "" # 缓存未完整接收的消息 while not exit_flag: try: data = s.recv(1024) if not data: break buffer += data.decode('utf-8') # 按换行分割完整消息 while '\n' in buffer: decoded_data, buffer = buffer.split('\n', 1) print("Received message:", decoded_data) # 原有的消息处理逻辑... except Exception as e: print("Exception occurred while receiving message:", e)
同时服务器转发消息时,在末尾添加\n:
# 服务器转发逻辑示例 s.send(f"sm!{message}\n".encode('utf-8'))
内容的提问来源于stack exchange,提问作者ddhello
相关产品推荐
相关产品推荐

