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

下面是一个使用 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 端口,并等待客户端连接。每当有客户端连接时,服务器端会创建一个新的线程来处理该客户端的通信。客户端通过输入文本框来发送消息,同时接收来自服务器端和其他客户端的消息。

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值