Pyqt Qtcpserver: Как вернуть данные нескольким клиентам?
-
28-10-2019 - |
Вопрос
Я хочу создать QTCPSERVER, используя PYQT, который может одновременно возвращать данные 2 или более клиентам. Я предполагаю, что это потребует потока.
Используя пример ThreadEdAffustServer.py в качестве тестового примера (включенный в PYQT4, в моей системе он найден в/usr/share/doc/python-qt4-doc/примеры/сеть), я хочу подключить несколько клиентов и каждый раз, когда один Из клиентов просит целое состояние, другие клиенты также обновляются с помощью сообщения, как «Client X только что получил Fortune« Blah Blah Blah ».
Я понимаю, как работает программа Fortuneserver/Client, но кажется, что клиентские подключения немедленно прекращаются после того, как удача отправляется обратно клиенту. Мои конкретные вопросы:
Можно ли держать все соединения открытыми, чтобы каждый раз, когда один из клиентов запрашивает целое состояние, другие клиенты могли быть обновлены?
Если так, как лучше всего отслеживать и зацикливаться на подключенных клиентах?
Это серьезный камень преткновения для меня, потому что я хочу разработать приложение, в котором могут взаимодействовать несколько клиентов, и каждый клиент может быть обновлен о действиях других клиентов.
Заранее спасибо за вашу помощь, дайте мне знать, если есть какая -либо другая информация, которую я могу предоставить.
я нашел эта ветка Но не было достаточно конкретной информации, чтобы использовать. Другие дискуссии были для пакета сокетов Python, но я понимаю, что при использовании PYQT сервер должен быть QTCPSERVER, так что все хорошо играет.
*** РЕДАКТИРОВАТЬ ***
Вот начальные этапы моего решения. Я создал базовый сервер и клиент. Сервер просто отправляет обратно то, что клиент введен в поле редактирования строки.
Я основываю это на примере "Buildingservices" из главы 18 Быстрое программирование с графическим интерфейсом с помощью 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_()
Решение
Как, вероятно, было раздражающе очевидно для большинства из вас, я не до конца понимал, как справиться с темами! Не волнуйтесь, я обнаружил способ разработки сервера, который может отправлять данные нескольким клиентам, нельзя найти вторичный поток.
На самом деле довольно просто, но я не самый быстрый из кошек в лучшие времена.
Сервер:
#!/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_()
Подводя итог, каждое клиентское соединение открывает розетку, и гнездо добавляется в список всех клиентских розетков. Затем, когда один из клиентов отправляет текст, сервер переходит к клиентским гнездам, находит тот, который имеет байтеса, читает его, а затем отправляет сообщение другим клиентам.
Мне было бы любопытно услышать, что другие люди могут подумать об этом подходе. Ловушки, проблемы и т. Д.
Спасибо!