2

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()
ollie dunn
  • 305
  • 4
  • 13
  • Which platform are you on? – ebarr Apr 21 '14 at 01:38
  • I'm developing it on windows but also need to be deployed on Linux so ideally it would be platform agnostic. – ollie dunn Apr 21 '14 at 04:45
  • What counts as a hacky solution? I might consider subclassing `TCPServer` such that it keeps a reference to the parent GUI. Then you can put GUI control logic in the handler. – ebarr Apr 21 '14 at 09:01
  • Why do you need to get information from the client into the `startServer()` method? Do you just want to know how to transfer information from your `handle` method, which is running in a thread, back into the GUI thread? – three_pineapples Apr 21 '14 at 11:18
  • @three_pineapples, that is correct, I have updated the comment accordingly. How to get information sent from the client back to the GUI that is running the server in a thread safe manner. – ollie dunn Apr 22 '14 at 00:44
  • Cool, I'll post an answer shortly – three_pineapples Apr 22 '14 at 00:52

2 Answers2

2

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()
Community
  • 1
  • 1
three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • Thanks for that, works well. Looks like I might as well use QThread for the server as well. Will have a play around and work out what feels the cleanest. – ollie dunn Apr 22 '14 at 09:21
  • I'm not actually sure you need to put the TCP server code in it's own thread. Not 100% sure about your specific implementation, but when I did stuff with the Python `BaseHTTPServer` class (which looks similar to `TCPSevrer` in some ways), when you called `server_forever()` it then ran in it's own thread anyway (and `handle()` definitely ran in a separate thread. Might be worth double checking! – three_pineapples Apr 22 '14 at 10:23
0

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.

Community
  • 1
  • 1
User
  • 14,131
  • 2
  • 40
  • 59
  • Thanks for the info. That does work, either as suggested or as subclassing the TCPServer. I wander though if it is thread safe? As the GUI and the server are running in separate threads, is it safe to share objects between threads like this? – ollie dunn Apr 21 '14 at 10:34
  • It is safe to share objects like this (readonly). If you set attributes of the objects or access the GUI from the thread then it can be not threadsafe. You can use locks or queues, etc, if you need to, to make it threadsafe. I recommend [Pyro](https://github.com/irmen/Pyro4) to share objects. – User Apr 21 '14 at 19:39
  • 1
    AFAIK, python locks don't make the Qt classes thread-safe because the library may do work internally with the GIL released. I'm not sure how Pyro would go either. Queues, are the safest option for communication! – three_pineapples Apr 22 '14 at 01:35