多线程UDP监听函数

3 投票
1 回答
4521 浏览
提问于 2025-04-17 13:21

我刚接触Python,所以如果我的问题听起来很初级,我先说声抱歉。

我想写一个简单的程序,让用户能够看到正在广播的UDP消息。为了让程序不卡住,我当然需要创建一个单独的线程来监听这些广播。

在我尝试使用线程之前,程序运行得很好。它只是等待UDP消息,并在收到消息时把它们显示在TextView里。当然,这样会导致主线程卡住。

这是我的代码:(是的,我知道我的代码有几个不相关的问题,但请忽略它们。例如,我从来没有关闭socket。我会在后面处理这些问题。)

import socket
import select
import sys
import threading

try:
    import pygtk
    pygtk.require("2.0")
except:
    pass
try:
    import gtk
except:
    print("GTK not available!")
    sys.exit(1)


class GUI:
    def __init__(self):
        self.builder = gtk.Builder()
        self.builder.add_from_file("UDPListener.ui")
        dic = {"on_mainWindow_destroy" : self.quit,
             "on_listenButton_clicked" : self.startThread,
         "on_stopListenButton_clicked" : self.stopListening}
        self.builder.connect_signals(dic)

        self.listenerThread = threading.Thread(target = self.listen)

    def startThread(self, widget):
        self.listenerThread.start()

    def listen(self):
        bufferSize = 1024                                                          
        IPAddress = "0.0.0.0"
        IPAddress: 0.0.0.0
        portNumber = 50000
        udpClient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        textView = self.builder.get_object("messagesReceivedEntry").get_buffer()
        textView.text = textView.get_text(textView.get_start_iter(),textView.get_end_iter())

        textView.set_text("Listening...")

        try:
            udpClient.bind((IPAddress, portNumber))
            udpClient.setblocking(0)
        except:
            print "Cannot connect."

        try:
            while True:
                result = select.select([udpClient],[],[])
                message = result[0][0].recv(bufferSize)
                print message
        except:
            print "Cannot receive message"

    def quit(self, widget):
        sys.exit(0)

    def stopListening(self, widget):
        pass

gui = GUI()
gtk.main() 

代码可以运行,一切正常,但它从来没有打印出任何消息(我自己广播的消息)。


更新:

感谢J.F. Sebastian的回答,我修改了我的代码,加入了以下内容:

import socket
import select
import sys
import threading
import gobject

try:
    import pygtk
    pygtk.require("2.0")
except:
    pass
try:
    import gtk
except:
    print("GTK not available!")
    sys.exit(1)

gobject.threads_init()

class GUI:
    def __init__(self, UDPClient):
        self.udpClient = UDPClient

        self.builder = gtk.Builder()
        self.builder.add_from_file("TestUDPListenerREORG.ui")
        dic = {"on_mainWindow_destroy" : self.quit,
             "on_listenButton_clicked" : self.startThread,
         "on_stopListenButton_clicked" : self.stopListening}
        self.builder.connect_signals(dic)

        self.textView = self.builder.get_object("messagesReceivedEntry").get_buffer()
        self.textViewText = self.textView.get_text(self.textView.get_start_iter(),self.textView.get_end_iter())

        self.listening = False
        self.listenerThread = threading.Thread(target = self.listen)

    def startThread(self, widget):
        self.listenerThread.start()
        pass

    def listen(self):
        try:
            self.udpClient.connect()
        except:
            print "Cannot connect."
        try:
            while True:
                result = select.select([self.udpClient.client],[],[])
                message = result[0][0].recv(1024)
                print message
                gobject.idle_add(self.updateGUI, message)
        except:
            print "Cannot receive message"

    def updateGUI(self, message):
        print "updating..."
        self.textView.set_text(self.textViewText + "\n" + message)
        print message

    def quit(self, widget):
        self.udpClient.close()
        sys.exit(0)

    def stopListening(self, widget):
        pass



class UDPClient:
    def __init__(self, IPAddress, portNumber):
        self.IPAddress = IPAddress
        self.portNumber = portNumber
        self.bufferSize = 1024

        self.client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def connect(self):
        try:
            self.client.bind((self.IPAddress, self.portNumber))
            self.client.setblocking(0)
        except: print "Cannot connect."

    def close(self):
        try:
            self.client.close()
        except:
            pass

udpClient = UDPClient("0.0.0.0", 50000)
gui = GUI(udpClient)
gtk.main() 

到目前为止,一切运行得非常顺利。

1 个回答

1

我发现对于多线程的gtk应用程序,最有效的做法是把所有的界面代码都放在主线程里。唯一一个可以在后台线程中运行的gtk方法是 gobject.idle_add(),它用来安排在主线程中执行的界面回调。

在你运行任何其他gtk代码之前,记得在模块的顶部放上 gobject.threads_init()

这里有一个Gtk 3的例子,但Gtk 2的原理是一样的。还有另一个例子

另外一种选择是使用 gobject.io_add_watch(),这样就可以避免使用多个线程。

撰写回答