判断哪个描述符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())
.
我正在创建一个应用程序,其中服务器和客户端 运行 在同一台机器上,见图。
我希望用户能够将数据从服务器发送到特定客户端(=特定 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())
.