如何使用wxreactor和twisted Perspective Broker[PB]编写聊天客户端

2024-04-28 21:51:54 发布

您现在位置:Python中文网/ 问答频道 /正文

我在学习wxPython和twisted的透视经纪人。我被指派一起使用它们来生成一个聊天客户端(我已经编写了服务器和一个基于控制台的客户端)

这就是困扰我的地方:PB有自己的“流”和回调等,这与wxpython的事件驱动流不太吻合。我应该用什么样的程序来配合呢?在

我尝试过使用twisted pb client部分从服务器获取并存储本地方法中的信息,wxpythongui可以调用这些方法来响应某些事件,并在开始时使用它来设置在线用户和组的列表。我想我遇到了这个序列的问题——在wx代码调用它们之前没有存储必要的变量,因为两者都是同时启动的。也许为帧创建插入一个时间延迟之类的方法会有所帮助,但这感觉是一个笨拙的解决方案,如果真的是一个解决方案的话。在

另一种方法是将服务器引用直接传递到wxPython框架(和子面板/笔记本)。在这里,我遇到了一些问题,因为回调需要一个不同的类,而wx需要同一个类中的信息……也许有一种方法可以迫使它们进入同一个模子中,但同样,这感觉非常笨拙(加上我还没有设法使它起作用。在

有没有解决这个问题的资源?标准方法?在

如果这些可能会说明我的方法有问题。。。在

这是我的服务器代码:http://pastebin.com/84fmhsRV GUI客户端代码:http://pastebin.com/UimXe4RY

谢谢你的帮助。在


Tags: 方法代码服务器com信息http客户端wxpython
2条回答

我在这里的聚会真的迟到了,但我可以为将来的读者提供一些有用的建议。在

这很难实现的原因是您试图让两个事件循环协同工作。你有扭曲的反应器和wxWidgets循环。有两种方法可以网格化循环

  1. 在Twisted中使用一个特殊情况下的reactor,它被设计成将Twisted和wx事件合并成一个循环。Twisted设计时就考虑到了这一点,因此,为达到这个目的,酿造一个定制的反应器并不难。在
  2. 在不同的线程中运行Twisted reactor和wx事件循环。在这种情况下,您依赖于操作系统将执行时间委托给每个事件循环。在

实际上,我今天刚刚完成了Twisted和PyQt这两种策略的工作。Qt和wxWidgets并没有什么不同,所以我认为您可以用最少的努力来调整我的解决方案。注意,这里我没有使用透视代理。一旦您理解了我是如何做到这一点的,添加透视代理层将非常容易。在

首先,我用方法1描述了我的解决方案,它依赖于PYQT4反应器。这是完整的工作代码(您需要pyqt4reactor,它可以在interwebz上的各种非正式地方找到)

使用特殊反应器的聊天客户端

import sys

import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
import PyQt4.uic as uic

import twisted.internet.defer as defer
import twisted.internet.protocol as protocol
import qt4reactor

import constants as C

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.ui = uic.loadUi('ui.ui')

        self.ui.sendButton.clicked.connect(self.sendMessage)
        self.ui.inputBox.returnPressed.connect(self.sendMessage)
        self.ui.connectButton.clicked.connect(self.getNetworkConnection)

        self.ui.show()

    def getNetworkConnection(self):
        #This line should probably not be inside getNetworkConnection
        factory = protocol.ClientCreator(reactor, ChatProtocol)
        d = factory.connectTCP(C.HOST, C.PORT)
        def onConnected(p):
            self.cxn = p
            p.emitter.signal.connect(self.onNewData)
            self.ui.connectButton.setEnabled(False)
        d.addCallback(onConnected)

    def onNewData(self, data):
        self.ui.outputBox.append(data)

    def sendMessage(self):
        message = str(self.ui.inputBox.text())
        self.ui.inputBox.clear()
        self.cxn.send(message)

class Emitter(QtCore.QObject):

    signal = QtCore.pyqtSignal(str)

    def __init__(self):
        QtCore.QObject.__init__(self)

class ChatProtocol(protocol.Protocol):

    def __init__(self):
        self.emitter = Emitter()

    def dataReceived(self, data):
        self.emitter.signal.emit(data)

    def send(self, data):
        self.transport.write(data)

class ChatFactory(protocol.ClientFactory):
    protocol = ChatProtocol

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    qt4reactor.install()
    from twisted.internet import reactor
    mainWindow = MainWindow()
    reactor.run()

让我们检查一下ChatProtocol及其助手类Emitter

^{pr2}$

协议本身非常简单。当您调用.send时,它通过传输写入数据。在

数据接收稍微复杂一些。为了让Twisted代码通知Qt事件循环传入的聊天,我们赋予协议一个发射器,它是一个可以发出单个信号的qo对象。在主Qt窗口中,我们将这个信号连接起来,以便它将数据发送到聊天窗口。这种连接发生在我们建立连接时。让我们来看看:

class MainWindow(QtGui.QMainWindow):

    <snip>

    def getNetworkConnection(self):
        #This line should probably not be inside getNetworkConnection
        factory = protocol.ClientCreator(reactor, ChatProtocol)
        d = factory.connectTCP(C.HOST, C.PORT)
        def onConnected(p):
            self.cxn = p
            p.emitter.signal.connect(self.onNewData)
            self.ui.connectButton.setEnabled(False)
        d.addCallback(onConnected)

我们告诉客户机工厂进行TCP连接。这给出了一个deferred,它将以产生的协议作为参数来调用。我们的回调函数onConnected负责将该协议的发射器信号连接到onNewData。这意味着每当协议的发射器发射时,即调用dataReceived时,数据将传播到Qt信号/时隙系统并显示在outputBox中。其余的函数应该或多或少有意义。在

还和我在一起?如果你是,我现在将演示如何使用线程来实现这一点。这是完整的工作代码

使用线程聊天客户端

import sys

import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
import PyQt4.uic as uic

import twisted.internet.reactor as reactor
import twisted.internet.defer as defer
import twisted.internet.protocol as protocol

import constants as C

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.ui = uic.loadUi('ui.ui')

        self.ui.sendButton.clicked.connect(self.sendMessage)
        self.ui.inputBox.returnPressed.connect(self.sendMessage)
        self.ui.connectButton.clicked.connect(self.getNetworkConnection)

        self.ui.show()

        self.networkThread = NetworkThread()
        self.networkThread.start()
        self.connect(self.networkThread,
                     self.networkThread.sigConnected,
                     self.onConnected)

    def getNetworkConnection(self):
        #This line should probably not be inside getNetworkConnection
        factory = protocol.ClientCreator(reactor, ChatProtocol)
        self.networkThread.callFromMain(factory.connectTCP,
                                        self.networkThread.sigConnected,
                                        C.HOST, C.PORT)

    def onConnected(self, p):
        self.cxn = p
        p.emitter.signal.connect(self.onNewData)
        self.ui.connectButton.setEnabled(False)

    def onNewData(self, data):
        self.ui.outputBox.append(data)

    def sendMessage(self):
        message = str(self.ui.inputBox.text())
        self.networkThread.callFromMain(self.cxn.send, None, message)
        self.ui.inputBox.clear()

class NetworkThread(QtCore.QThread):
    """Run the twisted reactor in its own thread"""
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.sigConnected = QtCore.SIGNAL("sigConnected")

    def run(self):
        reactor.run(installSignalHandlers=0)

    def callFromMain(self, func, successSignal, *args):
        """Call an async I/O function with a Qt signal as it's callback"""

        def succeed(result):
            self.emit(successSignal, result)

        def wrapped():
            d = defer.maybeDeferred(func, *args)
            if successSignal is not None:
                d.addCallback(succeed)

        reactor.callFromThread(wrapped)

class Emitter(QtCore.QObject):
    #Not sure why I specified a name here...
    signal = QtCore.pyqtSignal(str, name='newData')

class ChatProtocol(protocol.Protocol):
    def __init__(self):
        self.emitter = Emitter()

    def dataReceived(self, data):
        self.emitter.signal.emit(data)

    def send(self, data):
        self.transport.write(data)

class ChatFactory(protocol.ClientFactory):
    protocol = ChatProtocol

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())

除了在QThread中运行reactor之外,这里有趣的区别在于我们在代码的扭曲部分中连接回调的方式。我们特别使用了一个辅助函数callFromMain

    def callFromMain(self, func, successSignal, *args):
        """Call an async I/O function with a Qt signal as it's callback"""

        def succeed(result):
            self.emit(successSignal, result)

        def wrapped():
            d = defer.maybeDeferred(func, *args)
            if successSignal is not None:
                d.addCallback(succeed)

        reactor.callFromThread(wrapped)

我们提供了一个我们希望在Twisted线程中调用的函数、当函数的结果可用时我们希望发出的Qt信号,以及函数的额外参数。reactor调用我们的函数,并将回调附加到结果deferred,它将发出我们提供的信号。在

我希望这对某人有帮助:)

如果有人看到简化,请告诉我。在

你可能想看看Twisted和wxPython上的这两个页面:

我还发现了一个关于这个主题的recipe。wiki链接已经完成了一个简单的聊天程序。在

相关问题 更多 >