【问题标题】:How to use wxreactor with twisted Perspective Broker [PB] to write a chat client如何使用 wxreactor 和 twisted Perspective Broker [PB] 编写聊天客户端
【发布时间】:2012-07-05 13:44:34
【问题描述】:

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

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

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

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

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

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

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

感谢您的帮助。

【问题讨论】:

标签: python wxpython twisted perspective broker


【解决方案1】:

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

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

【讨论】:

  • 谢谢。我确实查看了您链接到的资源,但是(可能由于我缺乏经验)我没有看到如何将其应用于 Perspective Broker 的使用。 PB 的回调结构让我很难过。 ;-) 然而,我很难找到任何适用于 PB 的东西。你知道什么吗?
  • 我没用过wx+twisted。如果你在 wxPython 或 Twisted 邮件列表上提问,我相信你会得到回复。我知道 wxPython 列表中有几个人做过这种事情,虽然我不记得他们是否使用过 PB。
【解决方案2】:

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

这很难的原因是您试图让两个事件循环一起工作。你有 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 信号,以及我们函数的额外参数。 reactor 调用我们的函数并将回调附加到生成的 deferred 中,这将发出我们提供的信号。

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

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

【讨论】:

    猜你喜欢
    • 2016-01-21
    • 2013-08-14
    • 1970-01-01
    • 2015-06-15
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多