Loopback Example¶
Demonstrates the client-server communication on a local host.
The example demonstrates how the clients and servers on a local host communicate with each other.
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import sys
from PySide6.QtWidgets import QApplication
from dialog import Dialog
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import QByteArray, Qt
from PySide6.QtGui import QGuiApplication
from PySide6.QtNetwork import (QAbstractSocket, QHostAddress, QTcpServer,
QTcpSocket)
from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QMessageBox,
QProgressBar, QPushButton, QVBoxLayout)
class Dialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.total_bytes = 50 * 1024 * 1024 # 50 MB
self.payload_size = 64 * 1024 # 64 KB
self.bytes_to_write = 0
self.bytes_written = 0
self.bytes_received = 0
self.client_progress_bar = QProgressBar()
self.client_status_label = QLabel("Client ready")
self.server_progress_bar = QProgressBar()
self.server_status_label = QLabel("Server ready")
self.start_button = QPushButton("&Start")
self.quit_button = QPushButton("&Quit")
self.button_box = QDialogButtonBox()
self.button_box.addButton(self.start_button, QDialogButtonBox.ActionRole)
self.button_box.addButton(self.quit_button, QDialogButtonBox.RejectRole)
self.start_button.clicked.connect(self.start)
self.quit_button.clicked.connect(self.close)
self.tcp_server = QTcpServer()
self.tcp_client = QTcpSocket()
self.tcp_server.newConnection.connect(self.accept_connection)
self.tcp_client.connected.connect(self.start_transfer)
self.tcp_client.bytesWritten.connect(self.update_client_progress)
self.tcp_client.errorOccurred.connect(self.display_error)
main_layout = QVBoxLayout()
main_layout.addWidget(self.client_progress_bar)
main_layout.addWidget(self.client_status_label)
main_layout.addWidget(self.server_progress_bar)
main_layout.addWidget(self.server_status_label)
main_layout.addStretch(1)
main_layout.addSpacing(10)
main_layout.addWidget(self.button_box)
self.setLayout(main_layout)
self.setWindowTitle("Loopback")
def start(self):
self.start_button.setEnabled(False)
QGuiApplication.setOverrideCursor(Qt.WaitCursor)
self.bytes_written = 0
self.bytes_received = 0
while not self.tcp_server.isListening() and not self.tcp_server.listen():
ret: QMessageBox.StandardButton = QMessageBox.critical(
self,
"Loopback",
f"Unable to start the test {self.tcp_server.errorString()}",
QMessageBox.Retry | QMessageBox.Cancel,
)
if ret == QMessageBox.Cancel:
return
self.server_status_label.setText("Listening")
self.client_status_label.setText("Connecting")
self.tcp_client.connectToHost(QHostAddress.LocalHost, self.tcp_server.serverPort())
def accept_connection(self):
self.tcp_server_connection = self.tcp_server.nextPendingConnection()
if not self.tcp_server_connection:
self.server_status_label.setText("Error: got invalid pending connection")
return
self.tcp_server_connection.readyRead.connect(self.update_server_progress)
self.tcp_server_connection.errorOccurred.connect(self.display_error)
self.tcp_server_connection.disconnected.connect(self.tcp_server_connection.deleteLater)
self.server_status_label.setText("Accepted connection")
self.tcp_server.close()
def start_transfer(self):
# Called when the TCP client has connected to the loopback server
self.bytes_to_write = self.total_bytes - self.tcp_client.write(
QByteArray(self.payload_size, "@")
)
self.client_status_label.setText("Connected")
def update_server_progress(self):
self.bytes_received += self.tcp_server_connection.bytesAvailable()
self.tcp_server_connection.readAll()
self.server_progress_bar.setMaximum(self.total_bytes)
self.server_progress_bar.setValue(self.bytes_received)
self.server_status_label.setText(f"Received {self.bytes_received / (1024 ** 2)} MB")
if self.bytes_received == self.total_bytes:
self.tcp_server_connection.close()
self.start_button.setEnabled(True)
QGuiApplication.restoreOverrideCursor()
def update_client_progress(self, num_bytes: int):
# called when the TCP client has written some bytes
self.bytes_written += num_bytes
# only write more if not finished and when the Qt write buffer is below a certain size
if self.bytes_to_write > 0 and self.tcp_client.bytesToWrite() <= 4 * self.payload_size:
self.bytes_to_write -= self.tcp_client.write(
QByteArray(min(self.bytes_to_write, self.payload_size), "@")
)
self.client_progress_bar.setMaximum(self.total_bytes)
self.client_progress_bar.setValue(self.bytes_written)
self.client_status_label.setText(f"Sent {self.bytes_written / (1024 ** 2)} MB")
def display_error(self, socket_error: QAbstractSocket.SocketError):
if socket_error == QAbstractSocket.SocketError.RemoteHostClosedError:
return
QMessageBox.information(
self,
"Network error",
f"The following error occurred: {self.tcp_client.errorString()}",
)
self.tcp_client.close()
self.tcp_server.close()
self.client_progress_bar.reset()
self.server_progress_bar.reset()
self.client_status_label.setText("Client ready")
self.server_status_label.setText("Server ready")
self.start_button.setEnabled(True)
QGuiApplication.restoreOverrideCursor()