[ACCEPTED]-PyQt QTcpServer: How to return data to multiple clients?-qtcpserver

Accepted answer
Score: 10

As was probably exasperatingly obvious to 15 most of you, I didn't fully understand how 14 to deal with threads! Not to worry, I have 13 discovered a way to design a server that 12 can send data to multiple clients with nary 11 a secondary thread to be found.

Quite simple, really, but 10 I'm not the quickest of cats at the best 9 of times.

SERVER:

#!/usr/bin/env python3

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *

PORT = 9999
SIZEOF_UINT32 = 4

class ServerDlg(QPushButton):

    def __init__(self, parent=None):
        super(ServerDlg, self).__init__(
                "&Close Server", parent)
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        self.tcpServer = QTcpServer(self)               
        self.tcpServer.listen(QHostAddress("0.0.0.0"), PORT)
        self.connect(self.tcpServer, SIGNAL("newConnection()"), 
                    self.addConnection)
        self.connections = []

        self.connect(self, SIGNAL("clicked()"), self.close)
        font = self.font()
        font.setPointSize(24)
        self.setFont(font)
        self.setWindowTitle("Server")

    def addConnection(self):
        clientConnection = self.tcpServer.nextPendingConnection()
        clientConnection.nextBlockSize = 0
        self.connections.append(clientConnection)

        self.connect(clientConnection, SIGNAL("readyRead()"), 
                self.receiveMessage)
        self.connect(clientConnection, SIGNAL("disconnected()"), 
                self.removeConnection)
        self.connect(clientConnection, SIGNAL("error()"), 
                self.socketError)

    def receiveMessage(self):
        for s in self.connections:
            if s.bytesAvailable() > 0:
                stream = QDataStream(s)
                stream.setVersion(QDataStream.Qt_4_2)

                if s.nextBlockSize == 0:
                    if s.bytesAvailable() < SIZEOF_UINT32:
                        return
                    s.nextBlockSize = stream.readUInt32()
                if s.bytesAvailable() < s.nextBlockSize:
                    return

                textFromClient = stream.readQString()
                s.nextBlockSize = 0
                self.sendMessage(textFromClient, 
                                 s.socketDescriptor())
                s.nextBlockSize = 0

    def sendMessage(self, text, socketId):
        for s in self.connections:
            if s.socketDescriptor() == socketId:
                message = "You> {}".format(text)
            else:
                message = "{}> {}".format(socketId, text)
            reply = QByteArray()
            stream = QDataStream(reply, QIODevice.WriteOnly)
            stream.setVersion(QDataStream.Qt_4_2)
            stream.writeUInt32(0)
            stream.writeQString(message)
            stream.device().seek(0)
            stream.writeUInt32(reply.size() - SIZEOF_UINT32)
            s.write(reply)

    def removeConnection(self):
        pass

    def socketError(self):
        pass


app = QApplication(sys.argv)
form = ServerDlg()
form.show()
form.move(0, 0)
app.exec_()

CLIENT

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *

PORTS = (9998, 9999)
PORT = 9999
SIZEOF_UINT32 = 4

class Form(QDialog):

    def __init__(self, parent=None):
        super(Form, self).__init__(parent)

        # Ititialize socket
        self.socket = QTcpSocket()

        # Initialize data IO variables
        self.nextBlockSize = 0
        self.request = None

        # Create widgets/layout
        self.browser = QTextBrowser()
        self.lineedit = QLineEdit("Enter text here, dummy")
        self.lineedit.selectAll()
        self.connectButton = QPushButton("Connect")
        self.connectButton.setEnabled(True)
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        layout.addWidget(self.connectButton)
        self.setLayout(layout)
        self.lineedit.setFocus()

        # Signals and slots for line edit and connect button
        self.lineedit.returnPressed.connect(self.issueRequest)
        self.connectButton.clicked.connect(self.connectToServer)

        self.setWindowTitle("Client")
        # Signals and slots for networking
        self.socket.readyRead.connect(self.readFromServer)
        self.socket.disconnected.connect(self.serverHasStopped)
        self.connect(self.socket,
                     SIGNAL("error(QAbstractSocket::SocketError)"),
                     self.serverHasError)

    # Update GUI
    def updateUi(self, text):
        self.browser.append(text)

    # Create connection to server
    def connectToServer(self):
        self.connectButton.setEnabled(False)
        self.socket.connectToHost("localhost", PORT)

    def issueRequest(self):
        self.request = QByteArray()
        stream = QDataStream(self.request, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_4_2)
        stream.writeUInt32(0)
        stream.writeQString(self.lineedit.text())
        stream.device().seek(0)
        stream.writeUInt32(self.request.size() - SIZEOF_UINT32)
        self.socket.write(self.request)
        self.nextBlockSize = 0
        self.request = None
        self.lineedit.setText("")

    def readFromServer(self):
        stream = QDataStream(self.socket)
        stream.setVersion(QDataStream.Qt_4_2)

        while True:
            if self.nextBlockSize == 0:
                if self.socket.bytesAvailable() < SIZEOF_UINT32:
                    break
                self.nextBlockSize = stream.readUInt32()
            if self.socket.bytesAvailable() < self.nextBlockSize:
                break
            textFromServer = stream.readQString()
            self.updateUi(textFromServer)
            self.nextBlockSize = 0

    def serverHasStopped(self):
        self.socket.close()
        self.connectButton.setEnabled(True)

    def serverHasError(self):
        self.updateUi("Error: {}".format(
                self.socket.errorString()))
        self.socket.close()
        self.connectButton.setEnabled(True)


app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()

To summarize, each 8 client connection opens a socket, and the 7 socket is appended to a list of all client 6 sockets. Then, when one of the clients sends 5 text, the server loops over the client sockets, finds 4 the one that has bytesAvailable, reads it 3 in, and then sends the message to the other 2 clients.

I would be curious to hear what 1 other people may think of this approach. Pitfalls, issues, etc.

Thanks!

Score: 0

Here is the shareable code for PyQt5!

QTcpServer 1 ==================

import sys
from PyQt5.QtCore import Qt, QDataStream, QByteArray, QIODevice, pyqtSignal
from PyQt5.QtWidgets import QApplication, QPushButton
from PyQt5.QtNetwork import QTcpServer, QHostAddress

PORT = 9999
SIZEOF_UINT32 = 4

class ServerDlg(QPushButton):

    def __init__(self, parent=None):
         super(ServerDlg, self).__init__(
                  "&Close Server", parent)
         self.setWindowFlags(Qt.WindowStaysOnTopHint)

         self.tcpServer = QTcpServer(self)               
         self.tcpServer.listen(QHostAddress("127.0.0.1"), PORT)

         self.tcpServer.newConnection.connect(self.addConnection)
    
         self.connections = []

         self.clicked.connect(self.close)

    
         font = self.font()
         font.setPointSize(24)
         self.setFont(font)
         self.setWindowTitle("Server")

    def addConnection(self):
         clientConnection = self.tcpServer.nextPendingConnection()
         clientConnection.nextBlockSize = 0
         self.connections.append(clientConnection)

         clientConnection.readyRead.connect(self.receiveMessage)
         clientConnection.disconnected.connect(self.removeConnection)
         clientConnection.errorOccurred.connect(self.socketError)

    def receiveMessage(self):
         for s in self.connections:
            if s.bytesAvailable() > 0:
            stream = QDataStream(s)
            stream.setVersion(QDataStream.Qt_4_2)

            if s.nextBlockSize == 0:
                if s.bytesAvailable() < SIZEOF_UINT32:
                    return
                s.nextBlockSize = stream.readUInt32()
            if s.bytesAvailable() < s.nextBlockSize:
                return

            textFromClient = stream.readQString()
            s.nextBlockSize = 0
            self.sendMessage(textFromClient, 
                             s.socketDescriptor())
            s.nextBlockSize = 0
            print('Connections ', self.connections)

    def sendMessage(self, text, socketId):
        print('Text ', text)
        for s in self.connections:
            if s.socketDescriptor() == socketId:
               message = "You> {}".format(text)
            else:
               message = "{}> {}".format(socketId, text)
            reply = QByteArray()
            stream = QDataStream(reply, QIODevice.WriteOnly)
            stream.setVersion(QDataStream.Qt_4_2)
            stream.writeUInt32(0)
            stream.writeQString(message)
            stream.device().seek(0)
            stream.writeUInt32(reply.size() - SIZEOF_UINT32)
            s.write(reply)

    def removeConnection(self):
        pass

    def socketError(self):
        pass


app = QApplication(sys.argv)
form = ServerDlg()
form.show()
form.move(0, 0)
app.exec_()

More Related questions