用Python和GUI实现Socket多线程通信方案

2024-05-14 09:48:31 浏览数 (1)

下面是一个使用 Python 和 Tkinter GUI 库实现 Socket 多线程通信的简单示例。在这个示例中,我是创建了一个简单的聊天应用,其中服务器和客户端可以通过 Socket 进行通信。

1、问题背景

这个问题与在 Python 应用中使用 pyGTK、线程和套接字相关。开发者遇到了一个奇怪的错误,但由于涉及多个模块,他无法确定错误的具体位置。通过使用一些打印语句进行调试,开发者认为错误可能出现在以下代码片段中:

代码语言:javascript复制
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect(("localhost", 5005))
self.collectingThread = threading.Thread(target=self.callCollect)
self.collectingThread.daemon = True
self.collectingThread.start()

开发者想要做的是设置一个套接字,连接到一个本地运行的服务器脚本,并创建一个单独的线程来收集来自服务器脚本的所有传入数据。此线程被设置为每 500 毫秒运行一次 collectData 方法。在 collectData 方法中插入打印语句后,开发者在运行程序时发现以下现象:

  • 一开始 GUI 完全正常运行。
  • 然后在终端中打印以下内容:
代码语言:javascript复制
hello
**all data received from server script and printed here**
return
hello
  • 在终端中打印文本后,GUI 变为完全不正常状态(无法按下按钮等),并且必须强制退出才能关闭应用程序。 开发者的分析是,线程先打印“hello”,然后打印来自服务器的数据,最后打印“return”。500 毫秒后,它再次运行 collectData 方法,打印“hello”,然后尝试从服务器打印数据。但是,由于没有数据了,它引发了一个异常,但出于某种未知原因,它没有执行异常块中的代码,一切都从那里挂起。

2、解决方案

问题的核心在于使用了 timeout_add 将操作安排在主线程上,导致接收阻塞主线程,因此 GUI 也被阻塞,除非设置了超时或将套接字设置为非阻塞。

为了获得所需的效果,我们需要将接收委托给线程而不是相反,比如让线程等待一个事件对象,然后每 500 毫秒由安排的操作对事件发送信号。

修改后的代码示例:

代码语言:javascript复制
import socket
import threading
import gobject
​
class MyClass:
    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect(("localhost", 5005))
​
        self.collectingThread = threading.Thread(target=self.callCollect)
        self.collectingThread.daemon = True
        self.collectingThread.start()
​
        self.event = gobject.Event()
​
    def callCollect(self):
        while True:
            self.event.wait()
            self.collectData()
​
    def collectData(self):
        try:
            data = self.sock.recv(1024)
            if not data:
                return
            print("Received data:", data)
        except Exception as e:
            print("Error receiving data:", e)
        finally:
            self.event.set()
            gobject.timeout_add(500, self.wakeUp)
​
    def wakeUp(self):
        self.event.wakeUp()
        return True
​
if __name__ == "__main__":
    MyClass()
    gobject.MainLoop().run()

在上面的例子中,我们创建了一个 Event 对象 self.event,并使用 timeout_add 每 500 毫秒调用 wakeUp 方法。在 wakeUp 方法中,我们使用 self.event.wakeUp() 唤醒 self.event,从而导致 callCollect 方法中的线程从 self.event.wait() 返回,然后调用 collectData 方法来接收数据。

这两个代码示例分别实现了服务器端和客户端。服务器端监听本地 9999 端口,并等待客户端连接。每当有客户端连接时,服务器端会创建一个新的线程来处理该客户端的通信。客户端通过输入文本框来发送消息,同时接收来自服务器端和其他客户端的消息。

0 人点赞