2012-02-20 23 views
5

Estoy buscando crear un QTcpServer usando PyQt que pueda devolver datos simultáneamente a 2 o más clientes. Supongo que esto requerirá enhebrar.PyQt QTcpServer: ¿Cómo devolver datos a múltiples clientes?

Usando el ejemplo de threadedfortuneserver.py como caso de prueba (incluido con PyQt4, en mi sistema se encuentra en/usr/share/doc/python-qt4-doc/examples/network), quiero conectar varios clientes y cada vez que uno de los clientes pide una fortuna, los otros clientes también se actualizan con un mensaje como "El cliente X acaba de recibir la fortuna 'blah blah blah'".

Entiendo cómo funciona el programa fortuneserver/client, pero parece que las conexiones con el cliente se cancelan inmediatamente después de que la fortuna se devuelve al cliente. Mis preguntas específicas son:

  1. ¿Es posible mantener todas las conexiones abiertas para que cada vez que uno de los clientes pide una fortuna, los otros clientes puede actualiza?

  2. En caso afirmativo, ¿cuál es la mejor manera de realizar un seguimiento de los clientes conectados?

Este es un serio obstáculo para mí porque quiero desarrollar una aplicación donde pueden interactuar con varios clientes, y cada cliente puede ser informado sobre las acciones de los otros clientes.

Gracias de antemano por su ayuda, avíseme si hay alguna otra información que pueda proporcionar.

Encontré this thread pero no había suficiente información específica para hacer uso de. Otras discusiones han sido para el paquete de socket de Python, pero tengo entendido que cuando se utiliza PyQt, el servidor debe ser un QTcpServer para que todo funcione bien.

*** *** EDITAR

Aquí están las primeras etapas de mi solución. Creé un servidor y un cliente básico. El servidor simplemente devuelve lo que el cliente ingresó en un cuadro de edición de línea.

Estoy basando esto en el ejemplo "buildingservices" del Capítulo 18 de Rapid GUI Programming with Python and Qt.

El principal cambio que realicé es que ahora los hilos continúan ejecutándose indefinidamente y sus enchufes permanecen abiertos, escuchando los datos que envía el cliente.

Maneja múltiples clientes bien. Sin duda es feo, pero creo que es un buen punto de partida.

Lo que me gustaría es poder notificar a cada cliente cada vez que un cliente ingresa texto (como un programa de chat típico, por ejemplo).

Además, para darle una idea de con quién está tratando, NO soy un programador profesional. Soy un físico con muchos años de scripting indisciplinado y jugueteando bajo mi cinturón. Pero me gustaría tratar de desarrollar programas básicos de servidor/cliente que puedan transmitir datos.

¡Gracias por cualquier ayuda o sugerencia!

SERVIDOR:

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

CLIENTE:

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

Respuesta

8

Como era probablemente la exasperación obvio para la mayoría de ustedes, yo no entendía completamente cómo tratar con hilos! No hay que preocuparse, he descubierto una forma de diseñar un servidor que pueda enviar datos a múltiples clientes sin tener que encontrar un hilo secundario.

Bastante simple, de verdad, pero no soy el más rápido de los gatos en el mejor de los casos.

SERVIDOR:

#!/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_() 

CLIENTE

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

En resumen, cada conexión de cliente abre un socket, y el enchufe se añade a una lista de todos los sockets de cliente. Luego, cuando uno de los clientes envía texto, el servidor recorre los sockets del cliente, busca el que tiene bytesDisponible, lo lee y luego envía el mensaje a los otros clientes.

Me gustaría saber qué piensan los demás sobre este enfoque. Errores, problemas, etc.

¡Gracias!

Cuestiones relacionadas