PYQT QTCPSERVER: ¿Cómo devolver datos a varios clientes?
-
28-10-2019 - |
Pregunta
Estoy buscando crear un QTCPServer usando PYQT que pueda devolver los datos simultáneamente a 2 o más clientes. Supongo que esto requerirá roscado.
Utilizando el ejemplo de shiffedf 0Server.py como caso de prueba (incluido con pyqt4, en mi sistema se encuentra en/usr/share/doc/python-qt4-doc/ejemplos/network), quiero conectar múltiples clientes y cada vez uno uno de la vez uno De los clientes solicita una fortuna, los otros clientes también se actualizan con un mensaje como "Cliente X que acaba de recibir la fortuna 'bla, bla, bla'".
Entiendo cómo funciona el programa FortuneServer/Cliente, pero parece que las conexiones del cliente se terminan inmediatamente después de que la fortuna se envía al cliente. Mis preguntas específicas son:
¿Es posible mantener todas las conexiones abiertas para que cada vez que uno de los clientes solicite una fortuna, los otros clientes puedan actualizarse?
Si es así, ¿cuál es la mejor manera de realizar un seguimiento y recorrer los clientes conectados?
Este es un obstáculo serio para mí porque quiero desarrollar una aplicación donde varios clientes puedan interactuar, y cada cliente puede actualizarse 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é este hilo Pero no había suficiente información específica para hacer uso. Otras discusiones han sido para el paquete Python Socket, pero tengo entendido que al usar PYQT, el servidor debe ser un QTCPServer para que todo juegue bien.
*** EDITAR ***
Aquí están las etapas iniciales de mi solución. He creado un servidor y cliente básicos. El servidor solo envía lo que el cliente ingresó en un cuadro de edición de línea.
Estoy basando esto en el ejemplo de "BuilingServices" del Capítulo 18 de Programación de GUI rápida con Python y Qt.
El cambio principal que hice es que ahora los hilos siguen funcionando indefinidamente y sus enchufes permanecen abiertos, escuchando los datos enviados por el cliente.
Maneja bien múltiples clientes. Ciertamente 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 ingrese 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 físico con muchos años de secuencias de comandos indisciplinadas y jugueteando en mi haber. Pero me gustaría tratar de desarrollar programas básicos de servidor/cliente que puedan pasar 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_()
Solución
Como probablemente era exasperadamente obvio para la mayoría de ustedes, ¡no entendí completamente cómo lidiar con los hilos! No se preocupe, he descubierto una forma de diseñar un servidor que pueda enviar datos a varios clientes sin encontrar un hilo secundario.
Muy simple, realmente, pero no soy el gato más rápido 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_()
Para resumir, cada conexión del cliente abre un socket, y el socket se agrega a una lista de todos los enchufes del cliente. Luego, cuando uno de los clientes envía mensajes de texto, el servidor brota sobre los sockets del cliente, encuentra el que tiene una bytes disponible, lo lee y luego envía el mensaje a los otros clientes.
Tendría curiosidad por escuchar lo que otras personas pueden pensar de este enfoque. Trampas, problemas, etc.
¡Gracias!