下面是一个使用 Python 和 Tkinter GUI 库实现 Socket 多线程通信的简单示例。在这个示例中,我是创建了一个简单的聊天应用,其中服务器和客户端可以通过 Socket 进行通信。
1、问题背景
这个问题与在 Python 应用中使用 pyGTK、线程和套接字相关。开发者遇到了一个奇怪的错误,但由于涉及多个模块,他无法确定错误的具体位置。通过使用一些打印语句进行调试,开发者认为错误可能出现在以下代码片段中:
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 完全正常运行。
- 然后在终端中打印以下内容:
hello
**all data received from server script and printed here**
return
hello
- 在终端中打印文本后,GUI 变为完全不正常状态(无法按下按钮等),并且必须强制退出才能关闭应用程序。
开发者的分析是,线程先打印“hello”,然后打印来自服务器的数据,最后打印“return”。500 毫秒后,它再次运行collectData
方法,打印“hello”,然后尝试从服务器打印数据。但是,由于没有数据了,它引发了一个异常,但出于某种未知原因,它没有执行异常块中的代码,一切都从那里挂起。
2、解决方案
问题的核心在于使用了 timeout_add
将操作安排在主线程上,导致接收阻塞主线程,因此 GUI 也被阻塞,除非设置了超时或将套接字设置为非阻塞。
为了获得所需的效果,我们需要将接收委托给线程而不是相反,比如让线程等待一个事件对象,然后每 500 毫秒由安排的操作对事件发送信号。
修改后的代码示例:
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 端口,并等待客户端连接。每当有客户端连接时,服务器端会创建一个新的线程来处理该客户端的通信。客户端通过输入文本框来发送消息,同时接收来自服务器端和其他客户端的消息。