PyQt QTcpServer: How to return data to multiple clients?

前端 未结 1 558
日久生厌
日久生厌 2021-02-02 04:09

I am looking to create a QTcpServer using PyQt that can simultaneously return data to 2 or more clients. I assume that this will require threading.

Using the threadedfor

相关标签:
1条回答
  • 2021-02-02 05:12

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

    Quite simple, really, but I'm not the quickest of cats at the best 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 client connection opens a socket, and the socket is appended to a list of all client sockets. Then, when one of the clients sends text, the server loops over the client sockets, finds the one that has bytesAvailable, reads it in, and then sends the message to the other clients.

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

    Thanks!

    0 讨论(0)
提交回复
热议问题