سؤال

I am dipping my toes into networking and multithreading in python. I have gone through the docs on concurrent.futures and SocketServer and am attempting to use these in the example I am working on. The examples in the docs seem strait forward enough but am struggling to apply them to the example I am working on.

The example is as follows. 2 GUI applications, one that sends information to the other via user interaction and the other displays this information.

So far I have the 2 applications up and running. Application A has the server running in a separate thread. Application B is able to connect to the server and send the desired information.

At this point I cannot seem to find a nice way to get the information displayed in the GUI of application A. I can think of several hacky ways of doing it but am interested in the nicest / most pythonic way. This seems a common problem so there must be a common pattern to use here. So my questions are.

  • While the server is running, how to get the information from the custom request handler to the server.
  • how to get information from the thread to the main application while it is running?

Example code is as follows

Server Window

import SocketServer
import concurrent.futures
import sys
from PyQt4 import QtGui

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)


class MyRequestHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        print('...connected from:', self.client_address)        
        data = self.rfile.readline().strip()
        print('Data from client %s' % data)

class ServerWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setGeometry(1500, 100, 500, 500)

        self._control = QtGui.QWidget()
        self.setCentralWidget(self._control)

        l = QtGui.QVBoxLayout(self._control)
        t = QtGui.QTextEdit()
        l.addWidget(t)

        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
        self.startServerThread()

        self.show()

    def startServerThread(self):
        self.executor.submit(self.startServer)

        # How to get information from the thread while it is still running?

    def startServer(self):
        print('starting server')
        tcpServ = SocketServer.TCPServer(ADDR, MyRequestHandler)
        print('waiting for connection...')
        tcpServ.serve_forever()

        # How to get information from the client (custom request handler)
        # back to the GUI in a thread safe manner?

def launch():
    app = QtGui.QApplication(sys.argv)
    ex = ServerWindow()
    sys.exit(app.exec_())

if __name__ == '__main__':
    launch()

Client Window

import socket
import sys
import functools
from PyQt4 import QtGui

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

class ClientWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setGeometry(1500, 650, 500, 500)

        self._control = QtGui.QWidget()
        self.setCentralWidget(self._control)

        l = QtGui.QVBoxLayout(self._control)
        for i in range(5):
            name = 'test %d' % i
            b = QtGui.QPushButton(name)
            l.addWidget(b)
            b.pressed.connect(functools.partial(self.onButtonClick, name))

        self.show()

    def onButtonClick(self, buttonName):
        print('connecting to server')
        tcpCliSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcpCliSock.connect(ADDR)
        print('Sending name %s' % buttonName)
        tcpCliSock.send(buttonName)
        tcpCliSock.close()


def launch():
    app = QtGui.QApplication(sys.argv)
    ex = ClientWindow()
    sys.exit(app.exec_())

if __name__ == '__main__':
    launch()
هل كانت مفيدة؟

المحلول

So one of the few ways I know of, that transfers data from a thread to the main GUI thread of an application, is to place the data in a python Queue. This Queue is read by a QThread (a Qt threading implementation that supports Qt signals and slots). The QThread makes a blocking call to queue.get(). When data is placed in the Queue by your handle() method, the QThread unblocks, reads the data out of the queue, and emits a thread-safe signal to the GUI thread. As a demonstration, I added the data into the QTextEdit.

So basically you need an intermediary between a python thread and the Qt GUI which generally interacts via signals/slots. The QThread performs this task, linking the thread-safe Queue object, with the thread-safe qt signal emission.

This effectively follows a similar answer I gave here, but here you have a socket instead of a custom thread putting the data in the queue.

You might also be interested in this SO post, which explains why some of the lines of code I have used to make the QThread and connect the signals, etc, are written in the order they are!

P.S. I added a line to shutdown the socket server when the app window is closed (the socket server used to keep running in the background)

Server Window code

import SocketServer
import concurrent.futures
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from Queue import Queue

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

# create a global queue object that both the handle() method and the QThread (see later in the code) can access
queue = Queue()

class MyRequestHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        print('...connected from:', self.client_address)        
        data = self.rfile.readline().strip()
        print('Data from client %s' % data)
        queue.put(data)

class ServerWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.setGeometry(1500, 100, 500, 500)

        self._control = QtGui.QWidget()
        self.setCentralWidget(self._control)

        l = QtGui.QVBoxLayout(self._control)
        self.t = QtGui.QTextEdit()
        l.addWidget(self.t)

        self.executor = futures.ThreadPoolExecutor(max_workers=1)
        self.startServerThread()

        self.show()

    @QtCore.pyqtSlot(str)
    def receive_data(self, data):
        self.t.moveCursor(QtGui.QTextCursor.End)
        self.t.insertPlainText( data )

    def startServerThread(self):
        self.executor.submit(self.startServer)

        # How to get information from the thread while it is still running?

    def startServer(self):
        print('starting server')
        self.tcpServ = SocketServer.TCPServer(ADDR, MyRequestHandler)
        print('waiting for connection...')
        self.tcpServ.serve_forever()

        # How to get information from the client (custom request handler)
        # back to the GUI in a thread safe manner?

# This class runs in a QThread and listens on the end of a queue and emits a signal to the GUI
class MyReceiver(QtCore.QObject):
    mysignal = QtCore.pyqtSignal(str)

    def __init__(self,queue,*args,**kwargs):
        QtCore.QObject.__init__(self,*args,**kwargs)
        self.queue = queue

    @QtCore.pyqtSlot()
    def run(self):
        while True:
            text = self.queue.get()
            self.mysignal.emit(text)


def launch():
    app = QtGui.QApplication(sys.argv)

    ex = ServerWindow()

    # Create thread that will listen on the other end of the queue, and send the text to the textedit in our application
    thread = QtCore.QThread()
    my_receiver = MyReceiver(queue)
    my_receiver.mysignal.connect(ex.receive_data)
    my_receiver.moveToThread(thread)
    thread.started.connect(my_receiver.run)
    thread.start()

    ret_code = app.exec_()
    ex.tcpServ.shutdown()
    sys.exit(ret_code)

if __name__ == '__main__':
    launch()

نصائح أخرى

While the server is running, how to get the information from the custom request handler to the server.

how to get information from the thread to the main application while it is running?

These are my thoughts of how it should work.

class MyRequestHandler(SocketServer.StreamRequestHandler):
    @property
    def application_window(self):
        # you would override setup() for this, usually.
        return self.server.application_window

    def handle(self):
        print(self.application_window)

# ...

    def startServer(self):
        print('starting server')
        tcpServ = SocketServer.TCPServer(ADDR, MyRequestHandler)
        tcpServ.application_window = self # !!!!!!!!!!!!!!!!!!!!!!!!! added
        print('waiting for connection...', self)
        tcpServ.serve_forever()

Maybe you need to adjust something. By the way there are techniques for sharing information between server and client.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top