判断哪个描述符ID属于哪个客户端——QTcpSocket

Determine which descriptor ID belongs to which client - QTcpSocket

我正在创建一个应用程序,其中服务器和客户端 运行 在同一台机器上,见图。

我希望用户能够将数据从服务器发送到特定客户端(=特定 window)。为此,用户需要知道哪个ID属于哪个客户端(例如相应的ID可以显示在每个window的标题中)。

是否可以在客户端获取对应的描述符ID?如果没有,我怎么能达到同样的结果呢?

这是 pyside2 中的示例代码,但我不介意该解决方案是否使用 C++ qt。

QTCP服务器:

import sys
from typing import List

from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *


class MainWindow(QMainWindow):
    new_message = Signal(bytes)
    _connection_set: List[QTcpSocket] = []

    def __init__(self):
        super().__init__()
        self.server = QTcpServer()

        # layout
        self.setWindowTitle("QTCPServer")
        self._central_widget = QWidget()
        self._main_layout = QVBoxLayout()
        self.status_bar = QStatusBar()
        self.text_browser_received_messages = QTextBrowser()
        self._controller_layout = QHBoxLayout()
        self.combobox_receiver = QComboBox()
        self.lineEdit_message = QLineEdit()
        self._controller_layout.addWidget(self.combobox_receiver)
        self._controller_layout.addWidget(self.lineEdit_message)
        self._buttons_layout = QHBoxLayout()
        self.send_message_button = QPushButton("Send Message")
        self.send_message_button.clicked.connect(self.send_message_button_clicked)
        self._buttons_layout.addWidget(self.send_message_button)
        # end layout

        if self.server.listen(QHostAddress.Any, 8080):
            self.new_message.connect(self.display_message)
            self.server.newConnection.connect(self.new_connection)
            self.status_bar.showMessage("Server is listening...")
        else:
            QMessageBox.critical(self, "QTCPServer", f"Unable to start the server: {self.server.errorString()}.")

            self.server.close()
            self.server.deleteLater()

            sys.exit()

        # set layout
        self.setStatusBar(self.status_bar)
        self.setCentralWidget(self._central_widget)
        self._central_widget.setLayout(self._main_layout)
        self._main_layout.addWidget(self.text_browser_received_messages)
        self._main_layout.addLayout(self._controller_layout)
        self._main_layout.addLayout(self._buttons_layout)

    def new_connection(self) -> None:
        while self.server.hasPendingConnections():
            self.append_to_socket_list(self.server.nextPendingConnection())

    def append_to_socket_list(self, socket: QTcpSocket):
        self._connection_set.insert(len(self._connection_set), socket)
        self.connect(socket, SIGNAL("readyRead()"), self.read_socket)
        self.connect(socket, SIGNAL("disconnected()"), self.discard_socket)
        self.combobox_receiver.addItem(str(socket.socketDescriptor()))
        self.display_message(f"INFO :: Client with socket:{socket.socketDescriptor()} has just entered the room")

    def read_socket(self):
        socket: QTcpSocket = self.sender()
        buffer = QByteArray()

        socket_stream = QDataStream(socket)
        socket_stream.setVersion(QDataStream.Qt_5_15)

        socket_stream.startTransaction()
        socket_stream >> buffer

        if not socket_stream.commitTransaction():
            message = f"{socket.socketDescriptor()} :: Waiting for more data to come.."
            self.new_message.emit(message)
            return

        header = buffer.mid(0, 128)
        file_type = header.split(",")[0].split(":")[1]
        buffer = buffer.mid(128)

        if file_type == "message":
            message = f"{socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
            self.new_message.emit(message)

    def discard_socket(self):
        socket: QTcpSocket = self.sender()

        it = self._connection_set.index(socket)

        if it != len(self._connection_set):
            self.display_message(f"INFO :: A client has just left the room")
            del self._connection_set[it]
        socket.deleteLater()

        self.refresh_combobox()

    def send_message_button_clicked(self):
        receiver = self.combobox_receiver.currentText()
        if receiver == "Broadcast":
            for socket in self._connection_set:
                self.send_message(socket)
        else:
            for socket in self._connection_set:
                if socket.socketDescriptor() == int(receiver):
                    self.send_message(socket)
                    return
        self.lineEdit_message.clear()

    def send_message(self, socket: QTcpSocket):
        if not socket:
            QMessageBox.critical(self, "QTCPServer", "Not connected")
            return

        if not socket.isOpen():
            QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
            return

        string = self.lineEdit_message.text()
        socket_stream = QDataStream(socket)
        socket_stream.setVersion(QDataStream.Qt_5_15)
        header = QByteArray()
        string_size = len(string.encode('utf-8'))
        fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
        header.prepend(fstring.encode())
        header.resize(128)

        byte_array = QByteArray(string.encode())
        byte_array.prepend(header)

        socket_stream.setVersion(QDataStream.Qt_5_15)
        socket_stream << byte_array

    def display_message(self, string):
        self.text_browser_received_messages.append(string)

    def refresh_combobox(self):
        self.combobox_receiver.clear()
        self.combobox_receiver.addItem("Broadcast")
        for socket in self._connection_set:
            self.combobox_receiver.addItem(str(socket.socketDescriptor()))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

QTCPClient

import sys

from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *


class MainWindow(QMainWindow):
    new_message = Signal(bytes)

    def __init__(self):
        super().__init__()
        self.socket = QTcpSocket(self)
        # layout
        self.setWindowTitle("QTCPClient")
        self._central_widget = QWidget()
        self._main_layout = QVBoxLayout()
        self.status_bar = QStatusBar()
        self.text_browser_received_messages = QTextBrowser()
        self._controller_layout = QHBoxLayout()
        self.lineEdit_message = QLineEdit()
        self._controller_layout.addWidget(self.lineEdit_message)
        self._buttons_layout = QHBoxLayout()
        self.send_message_button = QPushButton("Send Message")
        self.send_message_button.clicked.connect(self.on_send_message_button_clicked)
        self._buttons_layout.addWidget(self.send_message_button)
        # end layout

        self.new_message.connect(self.display_message)
        self.connect(self.socket, SIGNAL("readyRead()"), self.read_socket)
        self.connect(self.socket, SIGNAL("disconnected()"), self.discard_socket)

        # set layout
        self.setStatusBar(self.status_bar)
        self.setCentralWidget(self._central_widget)
        self._central_widget.setLayout(self._main_layout)
        self._main_layout.addWidget(self.text_browser_received_messages)
        self._main_layout.addLayout(self._controller_layout)
        self._main_layout.addLayout(self._buttons_layout)

        self.socket.connectToHost(QHostAddress.LocalHost, 8080)

        if self.socket.waitForConnected():
            self.status_bar.showMessage("Connected to Server")
        else:
            QMessageBox.critical(self, "QTCPClient", f"The following error occurred: {self.socket.errorString()}.")
            if self.socket.isOpen():
                self.socket.close()
            sys.exit()

    def discard_socket(self):
        self.socket.deleteLater()
        self.socket = None
        self.status_bar.showMessage("Disconnected!")

    def read_socket(self):
        buffer = QByteArray()

        socket_stream = QDataStream(self.socket)
        socket_stream.setVersion(QDataStream.Qt_5_15)

        socket_stream.startTransaction()
        socket_stream >> buffer

        if not socket_stream.commitTransaction():
            message = f"{self.socket.socketDescriptor()} :: Waiting for more data to come.."
            self.new_message.emit(message)
            return

        header = buffer.mid(0, 128)
        file_type = header.split(",")[0].split(":")[1]

        buffer = buffer.mid(128)

        if file_type == "message":
            message = f"{self.socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
            self.new_message.emit(message)

    def on_send_message_button_clicked(self):
        if not self.socket:
            QMessageBox.critical(self, "QTCPServer", "Not connected")
            return

        if not self.socket.isOpen():
            QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
            return

        string = self.lineEdit_message.text()
        socket_stream = QDataStream(self.socket)
        socket_stream.setVersion(QDataStream.Qt_5_15)
        header = QByteArray()
        string_size = len(string.encode('utf-8'))
        fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
        header.prepend(fstring.encode())
        header.resize(128)

        byte_array = QByteArray(string.encode())
        byte_array.prepend(header)

        socket_stream << byte_array

        self.lineEdit_message.clear()

    def display_message(self, string: str):
        self.text_browser_received_messages.append(string)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

套接字描述符仅对构造函数有效,它们在两侧不匹配。

一种可能性是在连接后立即自动向客户端发送第一个“握手”消息,客户端将将该消息识别为“描述符 ID”类型,并最终设置其 window 标题.

在您的代码的以下更改中,我使用了一个简单的 fileType:descriptor header,并且描述符 ID 实际上作为整数值发送到数据流中。如果您想发送任何其他值,您显然可以在那里使用字符串。

    # server
    def append_to_socket_list(self, socket: QTcpSocket):
        # ...

        descriptor = int(socket.socketDescriptor())
        socket_stream = QDataStream(socket)
        fstring = 'fileType:descriptor,fileName:null,fileSize:{},'.format(descriptor.bit_length())
        header = QByteArray()
        header.prepend(fstring.encode())
        header.resize(128)

        socket_stream << header
        socket_stream.writeInt32(descriptor)
    # client
    def read_socket(self):
        # ...
        header = buffer.mid(0, 128)
        fields = header.split(",")
        file_type = fields[0].split(":")[1]

        buffer = buffer.mid(128)

        if file_type == "descriptor":
            self.id = socket_stream.readInt32()
            self.setWindowTitle("QTCPClient - id {}".format(self.id))

一些建议:

  • 两个信号都有 bytes 签名,但这是错误的,因为您将这些信号作为 str 类型发出;如果不确定,可以使用基本的 object 类型;

  • self.connect 语法被认为是过时的,使用“新”(嗯,不再那么新)样式一:object.signal.connect(slot);例如:

    self.socket.readyRead.connect(self.read_socket)

  • 使用 QApplication.quit() 而不是 sys.exit(),以便应用程序 正确地 在实际退出 [=74] 之前完成它需要的一切=]解释器;

  • 您应该使用用户数据,而不是使用组合的文本值:

    descriptor = socket.socketDescriptor()
    self.combobox_receiver.addItem(str(descriptor), descriptor)
    然后您可以使用 self.combobox_receiver.currentData() 访问它(您可以添加具有 -1 值的“广播”项);您甚至可以将套接字本身添加为用户数据;

  • 要正确拆分 header 而不会得到最后一个字段的乱码结果,您 必须 添加最后一个逗号,否则 split() 将 return 整个 字符串的其余部分;

PyQt 用户注意:socketDescriptor() returns a sip.voidptr,要获得实际值使用 int(socket.socketDescriptor()).