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

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()
问题分析与修复方案

核心问题

  1. 非主线程直接操作GUI控件:PySimpleGUI是单线程GUI框架,recv_msg线程直接调用window3['text_output'].print()会触发线程安全异常,导致接收线程卡住或崩溃,无法继续接收后续消息。
  2. exit_flag作用域错误:window3函数中设置exit_flag = True时未声明global,实际修改的是局部变量,无法终止recv_msg线程的循环。
  3. 已关闭窗口的访问问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 07:17:05