我正在通过编写聊天客户端来练习PyQt + Twisted。这需要让两个事件循环发挥得很好。我想弄清楚如何在没有Twisted QTReactor的情况下做到这一点。我想要实现的模式是
callFromThread
调用扭曲函数。我实际上已经让那部分工作了。解决方案的核心是这个功能
def callFromMain(self, func, successSignal, *args):
"""Call an async I/O function with a Qt signal as it's callback
func is the Twisted function you want to invoke. It probably returns a
deferred. successSignal is the signal you want to emit once the asynchronous
call finishes. This signal will be emitted with the result of func as an
argument
"""
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)
问题在于,当我收到“未询问”数据时,我不知道该怎么做。例如,某人 else 可能会发送聊天消息。我需要能够在Twisted线程中检测到数据的接收,并以某种方式将该数据放入Qt线程中。这看起来很棘手,因为在这种情况下,我没有为程序的Twisted部分提供一个用作回调的信号。我怎么能这样做?
下面请找到完整的示例代码来运行我几乎正常工作的聊天客户端和服务器。要使用这些程序,首先将服务器和客户端复制为.py文件,将ui规范复制为.ui文件。运行服务器,然后运行客户端。客户端窗口启动后,单击“连接”按钮。您应该在服务器上看到一条消息,指示新连接。然后尝试键入行编辑并单击“发送”按钮。您将看到数据进入服务器并返回,但它不会显示在客户端的QTextEdit框中。这是因为我无法弄清楚如何从ChatProtocol到程序的Qt部分获取数据。我们该怎么做?
SERVER(asyncore)
import asyncore
import socket
import constants as C
HOST = 'localhost'
PORT = 12344
class ChatServer(asyncore.dispatcher):
"""Receive and forward chat messages
When a new connection is made we spawn a dispatcher for that
connection.
"""
ADDRESS_FAMILY = socket.AF_INET
SOCKET_TYPE = socket.SOCK_STREAM
def __init__(self, host, port):
self.map = {}
self.address = (host,port)
self.clients = []
asyncore.dispatcher.__init__(self, map=self.map)
def serve(self):
"""Bind to socket and start asynchronous loop"""
self.create_socket(self.ADDRESS_FAMILY, self.SOCKET_TYPE)
self.bind(self.address)
print("ChatServer bound to %s %s"%self.address)
self.listen(1)
asyncore.loop(map=self.map)
def writable(self):
return False
def readable(self):
return True
def newMessage(self, data, fromWho):
"""Put data in all clients' buffers"""
print("new data: %s"%data)
for client in self.clients:
client.buffer = client.buffer + data
def handle_accept(self):
"""Deal with newly accepted connection"""
(connSock, clientAddress) = self.accept()
print("New connection accepted from %s %s"%clientAddress)
self.clients.append(ChatHandler(connSock, self.map, self))
class ChatHandler(asyncore.dispatcher):
def __init__(self, sock, map, server):
self.server = server
self.buffer = ''
asyncore.dispatcher.__init__(self, sock, map)
def writable(self):
return len(self.buffer) > 0
def readable(self):
return True
def handle_read(self):
"""Notify server of any new incoming data"""
data = self.recv(4096)
if data:
self.server.newMessage(data, self)
def handle_write(self):
"""send some amount of buffer"""
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
if __name__=='__main__':
if HOST is None:
HOST = raw_input('Host: ')
if PORT is None:
PORT = int(raw_input('Port: '))
s = ChatServer(HOST, PORT)
s.serve()
客户(PyQt + Twisted)
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
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('ui.ui')
self.networkThread = NetworkThread()
self.networkThread.start()
self.ui.sendButton.clicked.connect(self.sendMessage)
self.ui.connectButton.clicked.connect(self.getNetworkConnection)
#Make connections from network thread signals to our slots
self.connect(self.networkThread,
self.networkThread.sigConnected,
self.onConnected)
self.connect(self.networkThread,
self.networkThread.sigNewData,
self.onNewData)
self.ui.show()
def getNetworkConnection(self):
factory = protocol.ClientCreator(reactor, ChatProtocol)
self.networkThread.callFromMain(factory.connectTCP,
self.networkThread.sigConnected,
'localhost', 12344)
def onConnected(self, p):
print("Got a protocol!")
self.cxn = p
def onNewData(self, data):
self.ui.outputBox.append('\r\n'+data)
def sendMessage(self):
message = str(self.ui.inputBox.text())
self.networkThread.callFromMain(self.cxn.send, None, message)
class NetworkThread(QtCore.QThread):
"""Run the twisted reactor in its own thread"""
def __init__(self):
QtCore.QThread.__init__(self)
self.sigConnected = QtCore.SIGNAL("sigConnected")
self.sigNewData = QtCore.SIGNAL("sigNewData")
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 ChatProtocol(protocol.Protocol):
def dataReceived(self, data):
print("Got data: %s"%data)
print("...but I don't know how to pass it to Qt :(")
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_())
UI文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="outputBox"/>
</item>
<item>
<widget class="QLineEdit" name="inputBox"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="sendButton">
<property name="text">
<string>send</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="connectButton">
<property name="text">
<string>connect</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>