PyQt QTcpServer:如何将数据返回到多个客户端?

时间:2012-02-20 02:32:51

标签: python pyqt4 qtcpsocket qtcpserver

我希望使用PyQt创建一个QTcpServer,它可以同时将数据返回给两个或更多客户端。我认为这需要线程化。

使用threadedfortuneserver.py示例作为测试用例(包含在PyQt4中,在我的系统上可以在/ usr / share / doc / python-qt4-doc / examples / network中找到),我想连接多个客户端和每当其中一个客户要求发财时,其他客户也会更新一条消息,例如“客户X刚收到财产”等等等等。“

我理解fortuneserver / client程序是如何工作的,但似乎客户端连接在将财富发送回客户端后立即终止。我的具体问题是:

  1. 是否可以保持所有连接处于打开状态 其中一个客户要求发财,其他客户可以 更新

  2. 如果是这样,跟踪和循环连接的客户端的最佳方法是什么?

  3. 这对我来说是一个严重的绊脚石,因为我想开发一个应用程序,其中有几个客户可以互动,每个客户都可以更新其他客户的行为。

    在此先感谢您的帮助,如果我能提供任何其他信息,请与我们联系。

    我找到了this thread,但没有足够的具体信息可供使用。关于Python套接字包的其他讨论,但我的理解是,当使用PyQt时,服务器应该是QTcpServer,所以一切都很好。

    ***编辑***

    以下是我的解决方案的开始阶段。我创建了一个基本的服务器和客户端。服务器只是将客户输入的内容发送回“行编辑”框。

    我的基础是Rapid GUI Programming with Python and Qt第18章的“buildingservices”示例。

    我做的主要改变是,现在线程会无限期地运行,并且它们的套接字保持打开状态,监听客户端发送的数据。

    它可以很好地处理多个客户端。这当然是丑陋的,但我认为这是一个很好的起点。

    我希望能够在一个客户输入文本时通知每个客户(比如典型的聊天程序)。

    另外,为了让您知道自己与谁打交道,我不是专业程序员。我是一名物理学家,拥有多年无纪律的脚本和摆弄我的腰带。但我想尝试开发可以传递数据的基本服务器/客户端程序。

    感谢您提供任何帮助或建议!

    SERVER:

    import sys
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from PyQt4.QtNetwork import *
    
    PORT = 9999
    SIZEOF_UINT16 = 2
    
    class Thread(QThread):
    
        #lock = QReadWriteLock()
    
        def __init__(self, socketId, parent):
            super(Thread, self).__init__(parent)
            self.socketId = socketId
    
        def run(self):
            self.socket = QTcpSocket()
    
            if not self.socket.setSocketDescriptor(self.socketId):
                self.emit(SIGNAL("error(int)"), socket.error())
                return
    
            while self.socket.state() == QAbstractSocket.ConnectedState:
                nextBlockSize = 0
                stream = QDataStream(self.socket)
                stream.setVersion(QDataStream.Qt_4_2)
                if (self.socket.waitForReadyRead(-1) and
                    self.socket.bytesAvailable() >= SIZEOF_UINT16):
                    nextBlockSize = stream.readUInt16()
                else:
                    self.sendError("Cannot read client request")
                    return
                if self.socket.bytesAvailable() < nextBlockSize:
                    if (not self.socket.waitForReadyRead(-1) or
                        self.socket.bytesAvailable() < nextBlockSize):
                        self.sendError("Cannot read client data")
                        return
    
                textFromClient = stream.readQString()
    
                textToClient = "You wrote: \"{}\"".format(textFromClient)
                self.sendReply(textToClient)
    
        def sendError(self, msg):
            reply = QByteArray()
            stream = QDataStream(reply, QIODevice.WriteOnly)
            stream.setVersion(QDataStream.Qt_4_2)
            stream.writeUInt16(0)
            stream.writeQString("ERROR")
            stream.writeQString(msg)
            stream.device().seek(0)
            stream.writeUInt16(reply.size() - SIZEOF_UINT16)
            self.socket.write(reply)
    
        def sendReply(self, text):
            reply = QByteArray()
            stream = QDataStream(reply, QIODevice.WriteOnly)
            stream.setVersion(QDataStream.Qt_4_2)
            stream.writeUInt16(0)
            stream.writeQString(text)
            stream.device().seek(0)
            stream.writeUInt16(reply.size() - SIZEOF_UINT16)
            self.socket.write(reply)
    
    
    class TcpServer(QTcpServer):
    
        def __init__(self, parent=None):
            super(TcpServer, self).__init__(parent)
    
        def incomingConnection(self, socketId):
            self.thread = Thread(socketId, self)
            self.thread.start()
    
    
    class ServerDlg(QPushButton):
    
        def __init__(self, parent=None):
            super(ServerDlg, self).__init__(
                    "&Close Server", parent)
            self.setWindowFlags(Qt.WindowStaysOnTopHint)
    
            self.tcpServer = TcpServer(self)
            if not self.tcpServer.listen(QHostAddress("0.0.0.0"), PORT):
                QMessageBox.critical(self, "Threaded Server",
                        "Failed to start server: {}".format(
                        self.tcpServer.errorString()))
                self.close()
                return
    
            self.connect(self, SIGNAL("clicked()"), self.close)
            font = self.font()
            font.setPointSize(24)
            self.setFont(font)
            self.setWindowTitle("Threaded Server")
    
    app = QApplication(sys.argv)
    form = ServerDlg()
    form.show()
    form.move(0, 0)
    app.exec_()
    

    客户端:

    import sys
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from PyQt4.QtNetwork import *
    
    PORT = 9999
    SIZEOF_UINT16 = 2
    
    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("Texty bits")
            self.lineedit.selectAll()
            self.connectButton = QPushButton("Connect")
            self.connectButton.setDefault(False)
            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.sendToServer)
            self.connectButton.released.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)
            print("Connecting to server")
            self.socket.connectToHost("localhost", PORT)
    
        # Send data to server
        def sendToServer(self):
            self.request = QByteArray()
            stream = QDataStream(self.request, QIODevice.WriteOnly)
            stream.setVersion(QDataStream.Qt_4_2)
            stream.writeUInt16(0)
            stream.writeQString(self.lineedit.text())
            stream.device().seek(0)
            stream.writeUInt16(self.request.size() - SIZEOF_UINT16)
            self.socket.write(self.request)
            self.nextBlockSize = 0
            self.request = None
            self.lineedit.setText("")
    
        # Read data from server and update Text Browser
        def readFromServer(self):
            stream = QDataStream(self.socket)
            stream.setVersion(QDataStream.Qt_4_2)
    
            while True:
                if self.nextBlockSize == 0:
                    if self.socket.bytesAvailable() < SIZEOF_UINT16:
                        break
                    self.nextBlockSize = stream.readUInt16()
                if self.socket.bytesAvailable() < self.nextBlockSize:
                    break
                textFromServer = stream.readQString()
                self.updateUi(textFromServer)
                self.nextBlockSize = 0
    
        def serverHasStopped(self):
            self.socket.close()
    
        def serverHasError(self):
            self.updateUi("Error: {}".format(
                    self.socket.errorString()))
            self.socket.close()
    
    
    app = QApplication(sys.argv)
    form = Form()
    form.show()
    app.exec_()
    

1 个答案:

答案 0 :(得分:10)

对于你们大多数人来说可能是非常明显的,我并不完全明白如何处理线程!不用担心,我发现了一种设计服务器的方法,该服务器可以将数据发送到多个客户端,而无需找到辅助线程。

非常简单,真的,但在最好的时候我不是最快的猫。

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_()

客户端

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_()

总而言之,每个客户端连接都会打开一个套接字,并且套接字会附加到所有客户端套接字的列表中。然后,当其中一个客户端发送文本时,服务器遍历客户端套接字,找到具有bytesAvailable的那个,读入它,然后将消息发送给其他客户端。

我很想知道其他人可能会想到这种方法。陷阱,问题等。

谢谢!