PyQt QTcpServer: как вернуть данные нескольким клиентам? - PullRequest
5 голосов
/ 20 февраля 2012

Я хочу создать QTcpServer, использующий PyQt, который может одновременно возвращать данные 2 или более клиентам.Я предполагаю, что это потребует многопоточности.

Использование примера threadadedfortuneserver.py в качестве контрольного примера (включенного в PyQt4, в моей системе он находится в / usr / share / doc / python-qt4-doc / examples/ network), я хочу подключить несколько клиентов, и каждый раз, когда один из клиентов запрашивает состояние, другие клиенты также получают сообщение типа «Клиент X только что получил состояние« бла-бла-бла »».

Я понимаю, как работает программа fortuneserver / client, но кажется, что клиентские соединения немедленно разрываются после того, как состояние возвращается клиенту.Мои конкретные вопросы:

  1. Можно ли сохранить все соединения открытыми, чтобы каждый раз, когда один из клиентов запрашивал состояние, другие клиенты могли обновляться?

  2. Если это так, каков наилучший способ отслеживания и зацикливания на подключенных клиентах?

Это серьезный камень преткновения для меня, потому что я хочуразработать приложение, в котором могут взаимодействовать несколько клиентов, и каждый клиент может быть в курсе действий других клиентов.

Заранее благодарю за помощь, дайте мне знать, если есть какая-либо другая информация, которую я могу предоставить.

Я нашел эту тему , но не было достаточно конкретной информации, чтобы использовать ее.Другие обсуждения касались пакета сокетов Python, но, насколько я понимаю, при использовании PyQt сервер должен быть QTcpServer, поэтому все будет играть хорошо.

*** EDIT ***

Вот начальные этапы моего решения.Я создал базовый сервер и клиент.Сервер просто отправляет обратно то, что клиент ввел в поле Line Edit.

Я основываю это на примере "buildingservices" из главы 18 Быстрое программирование GUI с Python и Qt .

Основное изменение, которое я сделал, заключается в том, что теперь потоки продолжают работать неопределенно долго, а их сокеты остаются открытыми, прислушиваясь к данным, отправляемым клиентом.

Хорошо обрабатывает несколько клиентов.Это, конечно, некрасиво, но я думаю, что это хорошая отправная точка.

Я хотел бы иметь возможность уведомлять каждого клиента всякий раз, когда один клиент вводит текст (например, типичная программа чата).

Кроме того, чтобы дать вам представление о том, с кем вы имеете дело, я НЕ профессиональный программист.Я - физик с многолетним недисциплинированным написанием сценариев и возни с моим плечом.Но я хотел бы попытаться разработать базовые серверные / клиентские программы, которые могут передавать данные.

Спасибо за любую помощь или предложения!

СЕРВЕР:

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 Ответ

10 голосов
/ 26 февраля 2012

Как, наверное, было невероятно очевидно для большинства из вас, я не до конца понимал, как обращаться с потоками! Не волнуйтесь, я нашел способ спроектировать сервер, который мог бы отправлять данные нескольким клиентам без необходимости поиска дополнительного потока.

Довольно просто, правда, но я не самая быстрая кошка в лучшие времена.

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, читает его и затем отправляет сообщение другим клиентам.

Мне было бы интересно услышать, что другие люди могут подумать об этом подходе. Подводные камни, проблемы и т. Д.

Спасибо!

...