2012-02-20 16 views
5

Szukam utworzyć QTcpServer za pomocą PyQt, który może jednocześnie zwracać dane do 2 lub więcej klientów. Zakładam, że będzie to wymagać wątkowania.PyQt QTcpServer: Jak zwrócić dane do wielu klientów?

Użycie przykładu threadedfortuneserver.py jako przypadku testowego (dołączonego do PyQt4, w moim systemie znajduje się w/usr/share/doc/python-qt4-doc/examples/network), chcę połączyć wielu klientów i za każdym razem, gdy jeden z klientów prosi o fortunę, inni klienci otrzymują również aktualną wiadomość: "Klient X właśnie otrzymał fortunę" bla bla bla ".

Rozumiem, jak działa program fortuneserver/client, ale wygląda na to, że połączenia z klientami są natychmiast kończone po wysłaniu fortuny do klienta. Moje konkretne pytania:

  1. Czy to możliwe, aby utrzymać wszystkie połączenia otwarte tak, że każdy czas jeden z klientów żąda fortuny, pozostali klienci mogą być aktualizowane?

  2. Jeśli tak, jaki jest najlepszy sposób śledzenia i pętli podłączonych klientów?

To poważna przeszkodą dla mnie, bo chcę się rozwijać aplikację, gdzie kilka klienci mogą wchodzić w interakcje, a każdy klient może być aktualizowana o działaniach innych klientów.

Z góry dziękuję za pomoc, daj mi znać, jeśli są jakieś inne informacje, które mogę podać.

Znalazłem this thread, ale nie było wystarczająco dużo konkretnych informacji, z których można skorzystać. Inne dyskusje dotyczyły pakietu gniazd Pythona, ale rozumiem, że podczas używania PyQt serwer powinien być serwerem QTcpServer, więc wszystko gra ładnie.

*** EDYCJA ***

Oto początkowe etapy mojego rozwiązania. Stworzyłem podstawowy serwer i klienta. Serwer po prostu odsyła z powrotem to, co klient wprowadził do pola edycji linii.

Opieram to na przykładzie "buildingservices" z rozdziału 18 z Rapid GUI Programming with Python and Qt.

Najważniejszą zmianą, którą wprowadziłem, jest to, że wątki działają nieprzerwanie, a ich gniazda pozostają otwarte, nasłuchując danych wysyłanych przez klienta.

Obsługuje wielu klientów dobrze. Jest to z pewnością brzydkie, ale uważam, że jest to dobry punkt wyjścia.

Chciałbym móc powiadomić każdego klienta, gdy tylko jeden klient wprowadzi tekst (na przykład typowy program do czatu).

Również, aby dać ci wyobrażenie, z kim masz do czynienia, NIE jestem profesjonalnym programistą. Jestem fizykiem, który ma wiele lat niezdyscyplinowanego pisania scenariuszy i błąkania się pod moim pasem. Ale chciałbym spróbować stworzyć podstawowe programy serwerowe/klienckie, które mogą przekazywać dane.

Dzięki za pomoc lub sugestie!

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

KLIENT:

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

Odpowiedz

8

Jak zapewne irytująco oczywista dla większości z was, nie w pełni zrozumieć, w jaki sposób radzić sobie z wątków! Nie martw się, znalazłem sposób na zaprojektowanie serwera, który może wysyłać dane do wielu klientów, bez znalezienia dodatkowego wątku.

Całkiem proste, naprawdę, ale nie jestem najszybszym z kotów w najlepszych czasach.

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

KLIENT

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

Podsumowując, każdy klient otwiera połączenie gniazda, a gniazdo jest dołączany do listy wszystkich gniazd klienckich. Następnie, gdy jeden z klientów wysyła tekst, pętle serwera nad gniazdami klienta, znajduje ten, który ma bytesAvailable, odczytuje go, a następnie wysyła wiadomość do innych klientów.

Chciałbym usłyszeć, co inni mogą myśleć o tym podejściu. Pułapki, problemy itp.

Dzięki!

Powiązane problemy