2012-02-20 95 views
5

我期待創建一個使用PyQt的QTcpServer,它可以同時將數據返回給2個或更多的客戶端。我認爲這將需要線程。使用threadedfortuneserver.py示例作爲測試用例(包含在PyQt4中,在我的系統上可以在/ usr/share/doc/python-qt4-doc/examples/network中找到),我想連接多個客戶端每當其中一位客戶要求發財時,其他客戶也會收到一條消息,例如「客戶X剛剛收到財富'等等等等'」。PyQt QTcpServer:如何將數據返回給多個客戶端?

我明白財富服務器/客戶端程序是如何工作的,但似乎客戶端連接在財富發送回客戶端後立即終止。我的具體問題是:

  1. 是否可以保持所有連接的開放,使每 一次客戶端的請求一個發財,其他客戶端可以 更新?

  2. 如果是這樣,跟蹤和循環連接的客戶端的最佳方式是什麼?

這是一個嚴重的絆腳石適合我,因爲我要開發一個應用程序,其中一些客戶端可以交互,和每個客戶端可以對其他客戶的行爲進行更新。

在此先感謝您的幫助,請告訴我是否有任何其他信息可以提供。

我發現this thread但沒有足夠的具體信息可供使用。其他的討論都是針對Python套接字包的,但我的理解是,當使用PyQt時,服務器應該是QTcpServer,所以一切都很好。

***編輯***

這是我的解決方案的開始階段。我創建了一個基本的服務器和客戶端。服務器只是將客戶端輸入的內容發送回行編輯框。

我基於第18章的「buildingservices」示例。

我所做的主要改變是,現在線程保持無限期運行,並且它們的套接字保持打開狀態,偵聽客戶端發送的數據。

它可以處理多個客戶端。這確實很難看,但我認爲這是一個很好的起點。

我希望能夠通知每個客戶端,每當一個客戶端輸入文本(如一個典型的聊天程序,說)。

此外,爲了讓您瞭解您與誰打交道,我不是專業程序員。我是一位物理學家,有着多年無紀律的腳本,並在我的腰帶上擺弄。但我想嘗試開發可傳遞數據的基本服務器/客戶端程序。

感謝您的任何幫助或建議!

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

客戶:

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

回答

8

由於很可能是明顯的令人惱怒大多數的你,我沒有完全理解如何處理線程!不用擔心,我發現了一種設計服務器的方法,可以將數據發送到多個客戶端,並且可以找到一個輔助線程。

很簡單,真的,但我不是最好的貓最快的時候。

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

CLIENT

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字段的服務器,將其讀入,然後將消息發送給其他客戶端。

我會好奇聽到其他人可能會想到這種方法。陷阱,問題等

謝謝!

相關問題