我在这里聚会真的很晚了,但我可以为未来的读者提供一些有用的建议。
这很难的原因是你试图让两个事件循环一起工作。你有 Twisted reactor 和 wxWidgets 循环。有两种方法可以使循环网格化
- 在 Twisted 中使用一个特殊的反应器,该反应器旨在将 Twisted 和 wx 事件组合成一个循环。Twisted 的设计考虑到了这一点,因此为此目的酿造定制反应器并不难。
- 在单独的线程中运行 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 中,它将发出我们提供的信号。
我希望这对某人有帮助:)
如果有人看到简化,请告诉我。