如何使用wxreactor与扭曲的Perspective Broker [PB]编写聊天客户端

时间:2012-07-05 13:44:34

标签: python wxpython twisted perspective broker

我正在学习wxPython和twisted的Perspective Broker。我被分配一起使用它们来制作聊天客户端(我已经编写了服务器和基于控制台的客户端。)

这就是让我感到难过的事情:PB拥有回调等自己的'流',并没有直观地与wxpython的事件驱动流相结合。我应该使用什么样的程序结构才能使两者合作?

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

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

是否有解决此问题的资源?标准方法?

如果这些可能会解决我的方法的问题......

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

感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

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

我还在主题上找到了recipe。维基链接已经完成了一个简单的聊天程序。

答案 1 :(得分:0)

我来这里的派对真的很晚,但我可以为未来的读者提供一些有用的建议。

这很难的原因是你试图让两个事件循环一起工作。你有Twisted reactor和wxWidgets循环。网格循环有两种方法

  1. 在Twisted中使用一个特殊情况的反应器,用于将Twisted和wx事件组合成一个循环。 Twisted的设计考虑到了这一点,因此为此目的酿造定制反应器并不是一件难事。
  2. 在单独的线程中运行Twisted reactor和wx事件循环。在这种情况下,您依靠操作系统将执行时间委派给每个事件循环。
  3. 我实际上今天刚刚完成了使用Twisted和PyQt这两种策略。 Qt和wxWidgets不是 不同所以我认为你可以用最小的努力来调整我的解决方案。请注意,我在这里没有使用Perspective Broker。一旦你理解我如何使用它,添加Perspective Broker层将非常简单。

    首先,我用方法#1描述我的解决方案,它依赖于pyqt4reactor。这是完整的工作代码(你需要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

    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 Emitter(QtCore.QObject):
    
        signal = QtCore.pyqtSignal(str)
    

    协议本身非常简单。当您致电.send时,它会通过传输方式写入数据。

    数据接收稍微复杂一些。为了让Twisted代码通知传入聊天的Qt事件循环,我们赋予协议一个Emitter,它是一个可以发出单个信号的QObject。在主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中运行的反应器之外,这里有趣的区别在于我们在代码的Twisted部分中连接回调。特别是我们使用辅助函数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信号,我们希望在函数的结果可用时发出,以及我们函数的额外参数。反应器调用我们的函数并附加一个回调结果,它将发出我们提供的信号。

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

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