我正在学习wxPython和twisted的Perspective Broker。我被分配将它们一起使用来生成聊天客户端(我已经编写了服务器和基于控制台的客户端。
这就是让我难倒的地方:PB 有自己的"流",带有回调等,这与 wxpython 的事件驱动流并不直观地吻合。我应该使用什么样的程序结构来让两者合作?
我尝试使用程序的扭曲 pb 客户端部分以本地方法从服务器获取和存储信息,然后 wxpython gui 可以调用这些信息以响应某些事件,并在开始时用于设置在线用户和组的列表。我想我遇到了序列问题——在 wx 代码调用它们之前没有存储必要的变量,因为两者都是同时启动的。也许为帧创建插入时间延迟等会有所帮助,但这感觉像是一个笨拙的解决方案,如果有的话。
另一种方法是将服务器引用直接传递给wxPython框架(和子面板/笔记本)。在这里我遇到了问题,因为回调需要不同的类,而 wx 需要同一类中的信息......也许有一种方法可以迫使它们进入同一个模具,但同样,感觉非常笨拙(而且我还没有设法让它工作。
是否有解决此问题的资源?标准方法?
如果这些可能说明我的方法存在问题......
这是我的服务器代码:http://pastebin.com/84fmhsRVGUI 客户端代码:http://pastebin.com/UimXe4RY
谢谢你的帮助。
你可能想看看 Twisted 和 wxPython 上的这两个页面:
- http://wiki.wxpython.org/wxPythonAndTwisted
- http://twistedmatrix.com/documents/current/core/howto/choosing-reactor.html
我还找到了关于这个主题的食谱。维基链接已经完成了一个简单的聊天程序。
我在这里参加聚会真的很晚,但我可以为未来的读者提供一些有用的建议。
这很难的原因是,您正在尝试让两个事件循环协同工作。你有扭曲的反应器和wxWidgets循环。有两种方法可以对循环进行网格划分
- 使用扭曲中的特殊情况反应器,该反应器旨在将扭曲和 wx 事件组合到单个循环中。Twisted 的设计考虑到了这一点,因此为此目的酿造定制反应器并不难。
- 在单独的线程中运行扭曲反应器和 wx 事件循环。在这种情况下,你依赖于操作系统将执行时间委托给每个事件循环。
实际上,我今天刚刚完成了与Twisted和PyQt一起使用的这两种策略。Qt和wxWidgets并没有那么不同,所以我认为你可以用最小的努力来调整我的解决方案。请注意,我在这里没有使用透视代理。一旦你了解了我是如何让它工作的,添加透视代理层将非常容易。
首先,我用依赖于pyqt4反应器的方法#1描述我的解决方案。这是完整的工作代码(你需要pyqt4actor,它可以在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
时,它会通过其传输写入数据。
数据接收稍微复杂一些。为了让扭曲代码通知Qt事件循环传入的聊天,我们赋予协议一个发射器,这是一个可以发出单个信号的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连接。这给出了一个延迟,该延迟将以结果协议作为其参数进行调用。我们的回调函数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 信号,以及函数的额外参数。反应器调用我们的函数,并将回调附加到生成的延迟,这将发出我们提供的信号。
我希望这对:)的人有所帮助
如果有人看到简化,请告诉我。