0

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

这就是让我难过的地方:PB 有它自己的带有回调等的“流程”,这与 wxpython 的事件驱动流程并不直观地相吻合。我应该使用什么样的程序结构来让两者合作?

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

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

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

如果这些可能说明我的方法存在问题......

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

谢谢您的帮助。

4

2 回答 2

0

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

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

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

实际上,我今天刚刚完成了使用 Twisted 和 PyQt 的这两种策略。Qt 和 wxWidgets 并没有那么不同,所以我认为您可以轻松地调整我的解决方案。请注意,我在这里没有使用 Perspective Broker。一旦你理解了我是如何让它工作的,添加 Perspective Broker 层将非常容易。

首先,我使用依赖 pyqt4reactor 的方法 #1 描述我的解决方案。这是完整的工作代码(您需要 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 信号,以及我们函数的额外参数。反应器调用我们的函数并将一个回调附加到生成的 deferred 中,它将发出我们提供的信号。

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

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

于 2013-09-23T06:50:27.090 回答
0

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

我还找到了有关该主题的食谱。wiki 链接已经完成了一个简单的聊天程序。

于 2012-07-05T14:28:20.020 回答