2

チャット クライアントを作成して PyQt+Twisted を実践しています。これには、2 つのイベント ループを適切に再生する必要があります。Twisted QTReactor を使わずにこれを行う方法を知りたいです。私が達成しようとしているパターンは

  1. Qt は通常どおり実行され、Twisted は別のスレッド (QThread など) で実行されます。
  2. Qt 関数/メソッドは、 を使用して Twisted 関数を呼び出しますcallFromThread
  3. Twisted によって与えられた deferred をフックして、Qt シグナルの放出を引き起こします。このようにして、Twisted スレッドは Qt スレッドをコールバックできます。

私は実際にその部分を機能させました。ソリューションの核心はこの機能です

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)

問題は、「求められていない」データを取得したときに何をすべきかわからないことです。たとえば、他の誰かチャット メッセージを送信したとします。Twisted スレッドでデータの受信を検出し、そのデータを Qt スレッドに取得できるようにする必要があります。この場合、プログラムの Twisted 部分にコールバックとして使用するシグナルを提供していないため、これは難しいように思えます。どうやってやるの?

以下に、ほぼ動作するチャット クライアントとサーバーを実行するための完全なサンプル コードを示します。これらのプログラムを使用するには、まずサーバーとクライアントを .py ファイルとしてコピーし、UI 仕様を .ui ファイルとしてコピーします。サーバーを実行してから、クライアントを実行します。クライアント ウィンドウが表示されたら、[接続] ボタンをクリックします。新しい接続を示すメッセージがサーバーに表示されます。次に、行編集に入力して、「送信」ボタンをクリックしてみてください。データがサーバーに到達して戻ってくることがわかりますが、クライアントの QTextEdit ボックスには表示されません。これは、ChatProtocol からプログラムの Qt 部分にデータを取得する方法がわからないためです。どうすればこれを行うことができますか?

サーバー (非同期コア)

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 + ツイスト)

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>
4

0 に答える 0