qt 6.5.1 original
35
examples/network/CMakeLists.txt
Normal file
@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(NOT TARGET Qt6::Network)
|
||||
return()
|
||||
endif()
|
||||
if(NOT INTEGRITY)
|
||||
qt_internal_add_example(dnslookup)
|
||||
endif()
|
||||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(blockingfortuneclient)
|
||||
qt_internal_add_example(broadcastreceiver)
|
||||
qt_internal_add_example(broadcastsender)
|
||||
qt_internal_add_example(http)
|
||||
qt_internal_add_example(threadedfortuneserver)
|
||||
qt_internal_add_example(torrent)
|
||||
qt_internal_add_example(multicastreceiver)
|
||||
qt_internal_add_example(multicastsender)
|
||||
qt_internal_add_example(fortuneclient)
|
||||
qt_internal_add_example(fortuneserver)
|
||||
endif()
|
||||
if(QT_FEATURE_processenvironment AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(network-chat)
|
||||
endif()
|
||||
if(QT_FEATURE_ssl AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(securesocketclient)
|
||||
endif()
|
||||
if(QT_FEATURE_dtls AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(secureudpserver)
|
||||
qt_internal_add_example(secureudpclient)
|
||||
endif()
|
||||
if(QT_FEATURE_sctp AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(multistreamserver)
|
||||
qt_internal_add_example(multistreamclient)
|
||||
endif()
|
9
examples/network/README
Normal file
@ -0,0 +1,9 @@
|
||||
Qt is provided with an extensive set of network classes to support both
|
||||
client-based and server side network programming.
|
||||
|
||||
These examples demonstrate the fundamental aspects of network programming
|
||||
with Qt.
|
||||
|
||||
|
||||
Documentation for these examples can be found via the Examples
|
||||
link in the main Qt documentation.
|
39
examples/network/blockingfortuneclient/CMakeLists.txt
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(blockingfortuneclient LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/blockingfortuneclient")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(blockingfortuneclient
|
||||
blockingclient.cpp blockingclient.h
|
||||
fortunethread.cpp fortunethread.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(blockingfortuneclient PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(blockingfortuneclient PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS blockingfortuneclient
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
133
examples/network/blockingfortuneclient/blockingclient.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "blockingclient.h"
|
||||
|
||||
BlockingClient::BlockingClient(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
hostLabel = new QLabel(tr("&Server name:"));
|
||||
portLabel = new QLabel(tr("S&erver port:"));
|
||||
|
||||
// find out which IP to connect to
|
||||
QString ipAddress;
|
||||
const QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
|
||||
// use the first non-localhost IPv4 address
|
||||
for (const QHostAddress &entry : ipAddressesList) {
|
||||
if (entry != QHostAddress::LocalHost && entry.toIPv4Address()) {
|
||||
ipAddress = entry.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we did not find one, use IPv4 localhost
|
||||
if (ipAddress.isEmpty())
|
||||
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
|
||||
|
||||
hostLineEdit = new QLineEdit(ipAddress);
|
||||
portLineEdit = new QLineEdit;
|
||||
portLineEdit->setValidator(new QIntValidator(1, 65535, this));
|
||||
|
||||
|
||||
hostLabel->setBuddy(hostLineEdit);
|
||||
portLabel->setBuddy(portLineEdit);
|
||||
|
||||
statusLabel = new QLabel(tr("This examples requires that you run the "
|
||||
"Fortune Server example as well."));
|
||||
statusLabel->setWordWrap(true);
|
||||
|
||||
getFortuneButton = new QPushButton(tr("Get Fortune"));
|
||||
getFortuneButton->setDefault(true);
|
||||
getFortuneButton->setEnabled(false);
|
||||
|
||||
quitButton = new QPushButton(tr("Quit"));
|
||||
|
||||
buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
connect(getFortuneButton, &QPushButton::clicked,
|
||||
this, &BlockingClient::requestNewFortune);
|
||||
connect(quitButton, &QPushButton::clicked,
|
||||
this, &BlockingClient::close);
|
||||
|
||||
connect(hostLineEdit, &QLineEdit::textChanged,
|
||||
this, &BlockingClient::enableGetFortuneButton);
|
||||
connect(portLineEdit, &QLineEdit::textChanged,
|
||||
this, &BlockingClient::enableGetFortuneButton);
|
||||
//! [0]
|
||||
connect(&thread, &FortuneThread::newFortune,
|
||||
this, &BlockingClient::showFortune);
|
||||
connect(&thread, &FortuneThread::error,
|
||||
this, &BlockingClient::displayError);
|
||||
//! [0]
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(hostLabel, 0, 0);
|
||||
mainLayout->addWidget(hostLineEdit, 0, 1);
|
||||
mainLayout->addWidget(portLabel, 1, 0);
|
||||
mainLayout->addWidget(portLineEdit, 1, 1);
|
||||
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
|
||||
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Blocking Fortune Client"));
|
||||
portLineEdit->setFocus();
|
||||
}
|
||||
|
||||
//! [1]
|
||||
void BlockingClient::requestNewFortune()
|
||||
{
|
||||
getFortuneButton->setEnabled(false);
|
||||
thread.requestNewFortune(hostLineEdit->text(),
|
||||
portLineEdit->text().toInt());
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
void BlockingClient::showFortune(const QString &nextFortune)
|
||||
{
|
||||
if (nextFortune == currentFortune) {
|
||||
requestNewFortune();
|
||||
return;
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
currentFortune = nextFortune;
|
||||
statusLabel->setText(currentFortune);
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
//! [3]
|
||||
|
||||
void BlockingClient::displayError(int socketError, const QString &message)
|
||||
{
|
||||
switch (socketError) {
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
QMessageBox::information(this, tr("Blocking Fortune Client"),
|
||||
tr("The host was not found. Please check the "
|
||||
"host and port settings."));
|
||||
break;
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
QMessageBox::information(this, tr("Blocking Fortune Client"),
|
||||
tr("The connection was refused by the peer. "
|
||||
"Make sure the fortune server is running, "
|
||||
"and check that the host name and port "
|
||||
"settings are correct."));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::information(this, tr("Blocking Fortune Client"),
|
||||
tr("The following error occurred: %1.")
|
||||
.arg(message));
|
||||
}
|
||||
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void BlockingClient::enableGetFortuneButton()
|
||||
{
|
||||
bool enable(!hostLineEdit->text().isEmpty() && !portLineEdit->text().isEmpty());
|
||||
getFortuneButton->setEnabled(enable);
|
||||
}
|
48
examples/network/blockingfortuneclient/blockingclient.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BLOCKINGCLIENT_H
|
||||
#define BLOCKINGCLIENT_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "fortunethread.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QAction;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [0]
|
||||
class BlockingClient : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlockingClient(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void requestNewFortune();
|
||||
void showFortune(const QString &fortune);
|
||||
void displayError(int socketError, const QString &message);
|
||||
void enableGetFortuneButton();
|
||||
|
||||
private:
|
||||
QLabel *hostLabel;
|
||||
QLabel *portLabel;
|
||||
QLineEdit *hostLineEdit;
|
||||
QLineEdit *portLineEdit;
|
||||
QLabel *statusLabel;
|
||||
QPushButton *getFortuneButton;
|
||||
QPushButton *quitButton;
|
||||
QDialogButtonBox *buttonBox;
|
||||
|
||||
FortuneThread thread;
|
||||
QString currentFortune;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
@ -0,0 +1,12 @@
|
||||
QT += network widgets
|
||||
|
||||
HEADERS = blockingclient.h \
|
||||
fortunethread.h
|
||||
SOURCES = blockingclient.cpp \
|
||||
main.cpp \
|
||||
fortunethread.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/blockingfortuneclient
|
||||
INSTALLS += target
|
||||
|
90
examples/network/blockingfortuneclient/fortunethread.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "fortunethread.h"
|
||||
|
||||
FortuneThread::FortuneThread(QObject *parent)
|
||||
: QThread(parent), quit(false)
|
||||
{
|
||||
}
|
||||
|
||||
//! [0]
|
||||
FortuneThread::~FortuneThread()
|
||||
{
|
||||
mutex.lock();
|
||||
quit = true;
|
||||
cond.wakeOne();
|
||||
mutex.unlock();
|
||||
wait();
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1] //! [2]
|
||||
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
|
||||
{
|
||||
//! [1]
|
||||
QMutexLocker locker(&mutex);
|
||||
this->hostName = hostName;
|
||||
this->port = port;
|
||||
//! [3]
|
||||
if (!isRunning())
|
||||
start();
|
||||
else
|
||||
cond.wakeOne();
|
||||
}
|
||||
//! [2] //! [3]
|
||||
|
||||
//! [4]
|
||||
void FortuneThread::run()
|
||||
{
|
||||
mutex.lock();
|
||||
//! [4] //! [5]
|
||||
QString serverName = hostName;
|
||||
quint16 serverPort = port;
|
||||
mutex.unlock();
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
while (!quit) {
|
||||
//! [7]
|
||||
const int Timeout = 5 * 1000;
|
||||
|
||||
QTcpSocket socket;
|
||||
socket.connectToHost(serverName, serverPort);
|
||||
//! [6] //! [8]
|
||||
|
||||
if (!socket.waitForConnected(Timeout)) {
|
||||
emit error(socket.error(), socket.errorString());
|
||||
return;
|
||||
}
|
||||
//! [8] //! [11]
|
||||
|
||||
QDataStream in(&socket);
|
||||
in.setVersion(QDataStream::Qt_6_5);
|
||||
QString fortune;
|
||||
//! [11] //! [12]
|
||||
|
||||
do {
|
||||
if (!socket.waitForReadyRead(Timeout)) {
|
||||
emit error(socket.error(), socket.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
in.startTransaction();
|
||||
in >> fortune;
|
||||
} while (!in.commitTransaction());
|
||||
//! [12] //! [15]
|
||||
|
||||
mutex.lock();
|
||||
emit newFortune(fortune);
|
||||
//! [7]
|
||||
|
||||
cond.wait(&mutex);
|
||||
serverName = hostName;
|
||||
serverPort = port;
|
||||
mutex.unlock();
|
||||
}
|
||||
//! [15]
|
||||
}
|
36
examples/network/blockingfortuneclient/fortunethread.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef FORTUNETHREAD_H
|
||||
#define FORTUNETHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
//! [0]
|
||||
class FortuneThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FortuneThread(QObject *parent = nullptr);
|
||||
~FortuneThread();
|
||||
|
||||
void requestNewFortune(const QString &hostName, quint16 port);
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void newFortune(const QString &fortune);
|
||||
void error(int socketError, const QString &message);
|
||||
|
||||
private:
|
||||
QString hostName;
|
||||
quint16 port;
|
||||
QMutex mutex;
|
||||
QWaitCondition cond;
|
||||
bool quit;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
14
examples/network/blockingfortuneclient/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "blockingclient.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
BlockingClient client;
|
||||
client.show();
|
||||
return app.exec();
|
||||
}
|
38
examples/network/broadcastreceiver/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(broadcastreceiver LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/broadcastreceiver")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(broadcastreceiver
|
||||
main.cpp
|
||||
receiver.cpp receiver.h
|
||||
)
|
||||
|
||||
set_target_properties(broadcastreceiver PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(broadcastreceiver PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS broadcastreceiver
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
11
examples/network/broadcastreceiver/broadcastreceiver.pro
Normal file
@ -0,0 +1,11 @@
|
||||
QT += network widgets
|
||||
requires(qtConfig(udpsocket))
|
||||
|
||||
HEADERS = receiver.h
|
||||
SOURCES = receiver.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/broadcastreceiver
|
||||
INSTALLS += target
|
||||
|
14
examples/network/broadcastreceiver/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "receiver.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Receiver receiver;
|
||||
receiver.show();
|
||||
return app.exec();
|
||||
}
|
56
examples/network/broadcastreceiver/receiver.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QUdpSocket>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "receiver.h"
|
||||
|
||||
Receiver::Receiver(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
statusLabel = new QLabel(tr("Listening for broadcasted messages"));
|
||||
statusLabel->setWordWrap(true);
|
||||
|
||||
auto quitButton = new QPushButton(tr("&Quit"));
|
||||
|
||||
//! [0]
|
||||
udpSocket = new QUdpSocket(this);
|
||||
udpSocket->bind(45454, QUdpSocket::ShareAddress);
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
connect(udpSocket, &QUdpSocket::readyRead,
|
||||
this, &Receiver::processPendingDatagrams);
|
||||
//! [1]
|
||||
connect(quitButton, &QPushButton::clicked,
|
||||
qApp, &QCoreApplication::quit);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(quitButton);
|
||||
buttonLayout->addStretch(1);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Broadcast Receiver"));
|
||||
}
|
||||
|
||||
void Receiver::processPendingDatagrams()
|
||||
{
|
||||
QByteArray datagram;
|
||||
//! [2]
|
||||
while (udpSocket->hasPendingDatagrams()) {
|
||||
datagram.resize(int(udpSocket->pendingDatagramSize()));
|
||||
udpSocket->readDatagram(datagram.data(), datagram.size());
|
||||
statusLabel->setText(tr("Received datagram: \"%1\"")
|
||||
.arg(datagram.constData()));
|
||||
}
|
||||
//! [2]
|
||||
}
|
29
examples/network/broadcastreceiver/receiver.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef RECEIVER_H
|
||||
#define RECEIVER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QUdpSocket;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Receiver : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Receiver(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void processPendingDatagrams();
|
||||
|
||||
private:
|
||||
QLabel *statusLabel = nullptr;
|
||||
QUdpSocket *udpSocket = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
38
examples/network/broadcastsender/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(broadcastsender LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/broadcastsender")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(broadcastsender
|
||||
main.cpp
|
||||
sender.cpp sender.h
|
||||
)
|
||||
|
||||
set_target_properties(broadcastsender PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(broadcastsender PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS broadcastsender
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
10
examples/network/broadcastsender/broadcastsender.pro
Normal file
@ -0,0 +1,10 @@
|
||||
QT += network widgets
|
||||
requires(qtConfig(udpsocket))
|
||||
|
||||
HEADERS = sender.h
|
||||
SOURCES = sender.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/broadcastsender
|
||||
INSTALLS += target
|
14
examples/network/broadcastsender/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "sender.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Sender sender;
|
||||
sender.show();
|
||||
return app.exec();
|
||||
}
|
53
examples/network/broadcastsender/sender.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
#include <QtCore>
|
||||
|
||||
#include "sender.h"
|
||||
|
||||
Sender::Sender(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
statusLabel = new QLabel(tr("Ready to broadcast datagrams on port 45454"));
|
||||
statusLabel->setWordWrap(true);
|
||||
|
||||
startButton = new QPushButton(tr("&Start"));
|
||||
auto quitButton = new QPushButton(tr("&Quit"));
|
||||
|
||||
auto buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(startButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
//! [0]
|
||||
udpSocket = new QUdpSocket(this);
|
||||
//! [0]
|
||||
|
||||
connect(startButton, &QPushButton::clicked, this, &Sender::startBroadcasting);
|
||||
connect(quitButton, &QPushButton::clicked, qApp, &QCoreApplication::quit);
|
||||
connect(&timer, &QTimer::timeout, this, &Sender::broadcastDatagram);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Broadcast Sender"));
|
||||
}
|
||||
|
||||
void Sender::startBroadcasting()
|
||||
{
|
||||
startButton->setEnabled(false);
|
||||
timer.start(1000);
|
||||
}
|
||||
|
||||
void Sender::broadcastDatagram()
|
||||
{
|
||||
statusLabel->setText(tr("Now broadcasting datagram %1").arg(messageNo));
|
||||
//! [1]
|
||||
QByteArray datagram = "Broadcast message " + QByteArray::number(messageNo);
|
||||
udpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 45454);
|
||||
//! [1]
|
||||
++messageNo;
|
||||
}
|
35
examples/network/broadcastsender/sender.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SENDER_H
|
||||
#define SENDER_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QUdpSocket;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Sender : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Sender(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void startBroadcasting();
|
||||
void broadcastDatagram();
|
||||
|
||||
private:
|
||||
QLabel *statusLabel = nullptr;
|
||||
QPushButton *startButton = nullptr;
|
||||
QUdpSocket *udpSocket = nullptr;
|
||||
QTimer timer;
|
||||
int messageNo = 1;
|
||||
};
|
||||
|
||||
#endif
|
30
examples/network/dnslookup/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(dnslookup LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/dnslookup")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Network)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(dnslookup
|
||||
dnslookup.cpp dnslookup.h
|
||||
)
|
||||
|
||||
target_link_libraries(dnslookup PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
install(TARGETS dnslookup
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
224
examples/network/dnslookup/dnslookup.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
// Copyright (C) 2016 Jeremy Lainé <jeremy.laine@m4x.org>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "dnslookup.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDnsLookup>
|
||||
#include <QHostAddress>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
static std::optional<QDnsLookup::Type> typeFromParameter(QStringView type)
|
||||
{
|
||||
if (type.compare(u"a", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::A;
|
||||
if (type.compare(u"aaaa", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::AAAA;
|
||||
if (type.compare(u"any", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::ANY;
|
||||
if (type.compare(u"cname", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::CNAME;
|
||||
if (type.compare(u"mx", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::MX;
|
||||
if (type.compare(u"ns", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::NS;
|
||||
if (type.compare(u"ptr", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::PTR;
|
||||
if (type.compare(u"srv", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::SRV;
|
||||
if (type.compare(u"txt", Qt::CaseInsensitive) == 0)
|
||||
return QDnsLookup::TXT;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
//! [0]
|
||||
|
||||
struct CommandLineParseResult
|
||||
{
|
||||
enum class Status {
|
||||
Ok,
|
||||
Error,
|
||||
VersionRequested,
|
||||
HelpRequested
|
||||
};
|
||||
Status statusCode = Status::Ok;
|
||||
std::optional<QString> errorString = std::nullopt;
|
||||
};
|
||||
|
||||
CommandLineParseResult parseCommandLine(QCommandLineParser &parser, DnsQuery *query)
|
||||
{
|
||||
using Status = CommandLineParseResult::Status;
|
||||
|
||||
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
|
||||
const QCommandLineOption nameServerOption("n", "The name server to use.", "nameserver");
|
||||
parser.addOption(nameServerOption);
|
||||
const QCommandLineOption typeOption("t", "The lookup type.", "type");
|
||||
parser.addOption(typeOption);
|
||||
parser.addPositionalArgument("name", "The name to look up.");
|
||||
const QCommandLineOption helpOption = parser.addHelpOption();
|
||||
const QCommandLineOption versionOption = parser.addVersionOption();
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments()))
|
||||
return { Status::Error, parser.errorText() };
|
||||
|
||||
if (parser.isSet(versionOption))
|
||||
return { Status::VersionRequested };
|
||||
|
||||
if (parser.isSet(helpOption))
|
||||
return { Status::HelpRequested };
|
||||
|
||||
if (parser.isSet(nameServerOption)) {
|
||||
const QString nameserver = parser.value(nameServerOption);
|
||||
query->nameServer = QHostAddress(nameserver);
|
||||
if (query->nameServer.isNull()
|
||||
|| query->nameServer.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) {
|
||||
return { Status::Error,
|
||||
u"Bad nameserver address: %1"_qs.arg(nameserver) };
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.isSet(typeOption)) {
|
||||
const QString typeParameter = parser.value(typeOption);
|
||||
if (std::optional<QDnsLookup::Type> type = typeFromParameter(typeParameter))
|
||||
query->type = *type;
|
||||
else
|
||||
return { Status::Error, u"Bad record type: %1"_qs.arg(typeParameter) };
|
||||
}
|
||||
|
||||
const QStringList positionalArguments = parser.positionalArguments();
|
||||
if (positionalArguments.isEmpty())
|
||||
return { Status::Error, u"Argument 'name' missing."_qs };
|
||||
if (positionalArguments.size() > 1)
|
||||
return { Status::Error, u"Several 'name' arguments specified."_qs };
|
||||
query->name = positionalArguments.first();
|
||||
|
||||
return { Status::Ok };
|
||||
}
|
||||
|
||||
//! [0]
|
||||
|
||||
DnsManager::DnsManager()
|
||||
: dns(new QDnsLookup(this))
|
||||
{
|
||||
connect(dns, &QDnsLookup::finished, this, &DnsManager::showResults);
|
||||
}
|
||||
|
||||
void DnsManager::execute()
|
||||
{
|
||||
// lookup type
|
||||
dns->setType(query.type);
|
||||
if (!query.nameServer.isNull())
|
||||
dns->setNameserver(query.nameServer);
|
||||
dns->setName(query.name);
|
||||
dns->lookup();
|
||||
}
|
||||
|
||||
void DnsManager::showResults()
|
||||
{
|
||||
if (dns->error() != QDnsLookup::NoError)
|
||||
std::printf("Error: %i (%s)\n", dns->error(), qPrintable(dns->errorString()));
|
||||
|
||||
// CNAME records
|
||||
const QList<QDnsDomainNameRecord> cnameRecords = dns->canonicalNameRecords();
|
||||
for (const QDnsDomainNameRecord &record : cnameRecords) {
|
||||
std::printf("%s\t%i\tIN\tCNAME\t%s\n", qPrintable(record.name()), record.timeToLive(),
|
||||
qPrintable(record.value()));
|
||||
}
|
||||
|
||||
// A and AAAA records
|
||||
const QList<QDnsHostAddressRecord> aRecords = dns->hostAddressRecords();
|
||||
for (const QDnsHostAddressRecord &record : aRecords) {
|
||||
const char *type =
|
||||
(record.value().protocol() == QAbstractSocket::IPv6Protocol) ? "AAAA" : "A";
|
||||
std::printf("%s\t%i\tIN\t%s\t%s\n", qPrintable(record.name()), record.timeToLive(), type,
|
||||
qPrintable(record.value().toString()));
|
||||
}
|
||||
|
||||
// MX records
|
||||
const QList<QDnsMailExchangeRecord> mxRecords = dns->mailExchangeRecords();
|
||||
for (const QDnsMailExchangeRecord &record : mxRecords) {
|
||||
std::printf("%s\t%i\tIN\tMX\t%u %s\n", qPrintable(record.name()), record.timeToLive(),
|
||||
record.preference(), qPrintable(record.exchange()));
|
||||
}
|
||||
|
||||
// NS records
|
||||
const QList<QDnsDomainNameRecord> nsRecords = dns->nameServerRecords();
|
||||
for (const QDnsDomainNameRecord &record : nsRecords) {
|
||||
std::printf("%s\t%i\tIN\tNS\t%s\n", qPrintable(record.name()), record.timeToLive(),
|
||||
qPrintable(record.value()));
|
||||
}
|
||||
|
||||
// PTR records
|
||||
const QList<QDnsDomainNameRecord> ptrRecords = dns->pointerRecords();
|
||||
for (const QDnsDomainNameRecord &record : ptrRecords) {
|
||||
std::printf("%s\t%i\tIN\tPTR\t%s\n", qPrintable(record.name()), record.timeToLive(),
|
||||
qPrintable(record.value()));
|
||||
}
|
||||
|
||||
// SRV records
|
||||
const QList<QDnsServiceRecord> srvRecords = dns->serviceRecords();
|
||||
for (const QDnsServiceRecord &record : srvRecords) {
|
||||
std::printf("%s\t%i\tIN\tSRV\t%u %u %u %s\n", qPrintable(record.name()),
|
||||
record.timeToLive(), record.priority(), record.weight(), record.port(),
|
||||
qPrintable(record.target()));
|
||||
}
|
||||
|
||||
// TXT records
|
||||
const QList<QDnsTextRecord> txtRecords = dns->textRecords();
|
||||
for (const QDnsTextRecord &record : txtRecords) {
|
||||
QStringList values;
|
||||
const QList<QByteArray> dnsRecords = record.values();
|
||||
for (const QByteArray &ba : dnsRecords)
|
||||
values << "\"" + QString::fromLatin1(ba) + "\"";
|
||||
std::printf("%s\t%i\tIN\tTXT\t%s\n", qPrintable(record.name()), record.timeToLive(),
|
||||
qPrintable(values.join(' ')));
|
||||
}
|
||||
|
||||
QCoreApplication::instance()->quit();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
//! [1]
|
||||
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
|
||||
QCoreApplication::setApplicationName(QCoreApplication::translate("QDnsLookupExample",
|
||||
"DNS Lookup Example"));
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("QDnsLookupExample",
|
||||
"An example demonstrating the "
|
||||
"class QDnsLookup."));
|
||||
DnsQuery query;
|
||||
using Status = CommandLineParseResult::Status;
|
||||
CommandLineParseResult parseResult = parseCommandLine(parser, &query);
|
||||
switch (parseResult.statusCode) {
|
||||
case Status::Ok:
|
||||
break;
|
||||
case Status::Error:
|
||||
std::fputs(qPrintable(parseResult.errorString.value_or(u"Unknown error occurred"_qs)),
|
||||
stderr);
|
||||
std::fputs("\n\n", stderr);
|
||||
std::fputs(qPrintable(parser.helpText()), stderr);
|
||||
return 1;
|
||||
case Status::VersionRequested:
|
||||
parser.showVersion();
|
||||
Q_UNREACHABLE_RETURN(0);
|
||||
case Status::HelpRequested:
|
||||
parser.showHelp();
|
||||
Q_UNREACHABLE_RETURN(0);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
DnsManager manager;
|
||||
manager.setQuery(query);
|
||||
QTimer::singleShot(0, &manager, SLOT(execute()));
|
||||
|
||||
return app.exec();
|
||||
}
|
36
examples/network/dnslookup/dnslookup.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 Jeremy Lainé <jeremy.laine@m4x.org>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QDnsLookup>
|
||||
#include <QHostAddress>
|
||||
|
||||
//! [0]
|
||||
|
||||
struct DnsQuery
|
||||
{
|
||||
DnsQuery() : type(QDnsLookup::A) {}
|
||||
|
||||
QDnsLookup::Type type;
|
||||
QHostAddress nameServer;
|
||||
QString name;
|
||||
};
|
||||
|
||||
//! [0]
|
||||
|
||||
class DnsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DnsManager();
|
||||
void setQuery(const DnsQuery &q) { query = q; }
|
||||
|
||||
public slots:
|
||||
void execute();
|
||||
void showResults();
|
||||
|
||||
private:
|
||||
QDnsLookup *dns;
|
||||
DnsQuery query;
|
||||
};
|
||||
|
9
examples/network/dnslookup/dnslookup.pro
Normal file
@ -0,0 +1,9 @@
|
||||
TEMPLATE = app
|
||||
QT = core network
|
||||
CONFIG += cmdline
|
||||
HEADERS += dnslookup.h
|
||||
SOURCES += dnslookup.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/dnslookup
|
||||
INSTALLS += target
|
BIN
examples/network/doc/images/blockingfortuneclient-example.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
examples/network/doc/images/broadcastreceiver-example.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
examples/network/doc/images/broadcastsender-example.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
examples/network/doc/images/fortuneclient-example.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
examples/network/doc/images/fortuneserver-example.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
examples/network/doc/images/http-example.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
examples/network/doc/images/multicastreceiver-example.webp
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
examples/network/doc/images/multicastsender-example.webp
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
examples/network/doc/images/network-chat-example.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
examples/network/doc/images/securesocketclient.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
examples/network/doc/images/securesocketclient2.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
examples/network/doc/images/secureudpclient-example.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
examples/network/doc/images/secureudpserver-example.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
examples/network/doc/images/threadedfortuneserver-example.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/network/doc/images/torrent-example.png
Normal file
After Width: | Height: | Size: 18 KiB |
175
examples/network/doc/src/blockingfortuneclient.qdoc
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example blockingfortuneclient
|
||||
\title Blocking Fortune Client
|
||||
\examplecategory {Networking}
|
||||
\meta tags {tcp,network,threading,synchronous-io}
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to create a client for a network service.
|
||||
|
||||
\image blockingfortuneclient-example.png
|
||||
|
||||
QTcpSocket supports two general approaches to network programming:
|
||||
|
||||
\list
|
||||
|
||||
\li \e{The asynchronous (non-blocking) approach.} Operations are scheduled
|
||||
and performed when control returns to Qt's event loop. When the operation
|
||||
is finished, QTcpSocket emits a signal. For example,
|
||||
QTcpSocket::connectToHost() returns immediately, and when the connection
|
||||
has been established, QTcpSocket emits
|
||||
\l{QTcpSocket::connected()}{connected()}.
|
||||
|
||||
\li \e{The synchronous (blocking) approach.} In non-GUI and multithreaded
|
||||
applications, you can call the \c waitFor...() functions (e.g.,
|
||||
QTcpSocket::waitForConnected()) to suspend the calling thread until the
|
||||
operation has completed, instead of connecting to signals.
|
||||
|
||||
\endlist
|
||||
|
||||
The implementation is very similar to the
|
||||
\l{fortuneclient}{Fortune Client} example, but instead of having
|
||||
QTcpSocket as a member of the main class, doing asynchronous networking in
|
||||
the main thread, we will do all network operations in a separate thread
|
||||
and use QTcpSocket's blocking API.
|
||||
|
||||
The purpose of this example is to demonstrate a pattern that you can use
|
||||
to simplify your networking code, without losing responsiveness in your
|
||||
user interface. Use of Qt's blocking network API often leads to
|
||||
simpler code, but because of its blocking behavior, it should only be used
|
||||
in non-GUI threads to prevent the user interface from freezing. But
|
||||
contrary to what many think, using threads with QThread does not
|
||||
necessarily add unmanagable complexity to your application.
|
||||
|
||||
We will start with the FortuneThread class, which handles the network
|
||||
code.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.h 0
|
||||
|
||||
FortuneThread is a QThread subclass that provides an API for scheduling
|
||||
requests for fortunes, and it has signals for delivering fortunes and
|
||||
reporting errors. You can call requestNewFortune() to request a new
|
||||
fortune, and the result is delivered by the newFortune() signal. If any
|
||||
error occurs, the error() signal is emitted.
|
||||
|
||||
It's important to notice that requestNewFortune() is called from the main,
|
||||
GUI thread, but the host name and port values it stores will be accessed
|
||||
from FortuneThread's thread. Because we will be reading and writing
|
||||
FortuneThread's data members from different threads concurrently, we use
|
||||
QMutex to synchronize access.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 2
|
||||
|
||||
The requestNewFortune() function stores the host name and port of the
|
||||
fortune server as member data, and we lock the mutex with QMutexLocker to
|
||||
protect this data. We then start the thread, unless it is already
|
||||
running. We will come back to the QWaitCondition::wakeOne() call later.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 4
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 5
|
||||
|
||||
In the run() function, we start by acquiring the mutex lock, fetching the
|
||||
host name and port from the member data, and then releasing the lock
|
||||
again. The case that we are protecting ourselves against is that \c
|
||||
requestNewFortune() could be called at the same time as we are fetching
|
||||
this data. QString is \l reentrant but \e not \l{thread-safe}, and we must
|
||||
also avoid the unlikely risk of reading the host name from one request,
|
||||
and port of another. And as you might have guessed, FortuneThread can only
|
||||
handle one request at a time.
|
||||
|
||||
The run() function now enters a loop:
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 6
|
||||
|
||||
The loop will continue requesting fortunes for as long as \e quit is
|
||||
false. We start our first request by creating a QTcpSocket on the stack,
|
||||
and then we call \l{QTcpSocket::connectToHost()}{connectToHost()}. This
|
||||
starts an asynchronous operation which, after control returns to Qt's
|
||||
event loop, will cause QTcpSocket to emit
|
||||
\l{QTcpSocket::connected()}{connected()} or
|
||||
\l{QTcpSocket::error()}{error()}.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 8
|
||||
|
||||
But since we are running in a non-GUI thread, we do not have to worry
|
||||
about blocking the user interface. So instead of entering an event loop,
|
||||
we simply call QTcpSocket::waitForConnected(). This function will wait,
|
||||
blocking the calling thread, until QTcpSocket emits connected() or an
|
||||
error occurs. If connected() is emitted, the function returns true; if the
|
||||
connection failed or timed out (which in this example happens after 5
|
||||
seconds), false is returned. QTcpSocket::waitForConnected(), like the
|
||||
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
|
||||
API}.
|
||||
|
||||
After this statement, we have a connected socket to work with.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 11
|
||||
|
||||
Now we can create a QDataStream object, passing the socket to
|
||||
QDataStream's constructor, and as in the other client examples we set
|
||||
the stream protocol version to QDataStream::Qt_4_0.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 12
|
||||
|
||||
We proceed by initiating a loop that waits for the fortune string data by
|
||||
calling QTcpSocket::waitForReadyRead(). If it returns false, we abort the
|
||||
operation. After this statement, we start a stream read transaction. We
|
||||
exit the loop when QDataStream::commitTransaction() returns true, which
|
||||
means successful fortune string loading. The resulting fortune is
|
||||
delivered by emitting newFortune():
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 15
|
||||
|
||||
The final part of our loop is that we acquire the mutex so that we can
|
||||
safely read from our member data. We then let the thread go to sleep by
|
||||
calling QWaitCondition::wait(). At this point, we can go back to
|
||||
requestNewFortune() and look closely at the call to wakeOne():
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 1
|
||||
\dots
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 3
|
||||
|
||||
What happened here was that because the thread falls asleep waiting for a
|
||||
new request, we needed to wake it up again when a new request
|
||||
arrives. QWaitCondition is often used in threads to signal a wakeup call
|
||||
like this.
|
||||
|
||||
\snippet blockingfortuneclient/fortunethread.cpp 0
|
||||
|
||||
Finishing off the FortuneThread walkthrough, this is the destructor that
|
||||
sets \e quit to true, wakes up the thread and waits for the thread to exit
|
||||
before returning. This lets the \c while loop in run() will finish its current
|
||||
iteration. When run() returns, the thread will terminate and be destroyed.
|
||||
|
||||
Now for the BlockingClient class:
|
||||
|
||||
\snippet blockingfortuneclient/blockingclient.h 0
|
||||
|
||||
BlockingClient is very similar to the Client class in the
|
||||
\l{fortuneclient}{Fortune Client} example, but in this class
|
||||
we store a FortuneThread member instead of a pointer to a QTcpSocket.
|
||||
When the user clicks the "Get Fortune" button, the same slot is called,
|
||||
but its implementation is slightly different:
|
||||
|
||||
\snippet blockingfortuneclient/blockingclient.cpp 0
|
||||
|
||||
We connect our FortuneThread's two signals newFortune() and error() (which
|
||||
are somewhat similar to QTcpSocket::readyRead() and QTcpSocket::error() in
|
||||
the previous example) to requestNewFortune() and displayError().
|
||||
|
||||
\snippet blockingfortuneclient/blockingclient.cpp 1
|
||||
|
||||
The requestNewFortune() slot calls FortuneThread::requestNewFortune(),
|
||||
which \e schedules the request. When the thread has received a new fortune
|
||||
and emits newFortune(), our showFortune() slot is called:
|
||||
|
||||
\snippet blockingfortuneclient/blockingclient.cpp 2
|
||||
\codeline
|
||||
\snippet blockingfortuneclient/blockingclient.cpp 3
|
||||
|
||||
Here, we simply display the fortune we received as the argument.
|
||||
|
||||
\sa {Fortune Client}, {Fortune Server}
|
||||
*/
|
14
examples/network/doc/src/broadcastreceiver.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example broadcastreceiver
|
||||
\title Broadcast Receiver Example
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to receive information broadcasted over a local network.
|
||||
|
||||
This example uses the Qt Network APIs to demonstrate how to receive
|
||||
messages broadcasted over a local network.
|
||||
|
||||
\image broadcastreceiver-example.png
|
||||
*/
|
14
examples/network/doc/src/broadcastsender.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example broadcastsender
|
||||
\title Broadcast Sender Example
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to broadcast information to multiple clients on a local network.
|
||||
|
||||
This example uses Qt Network APIs to demonstrate how to broadcast messages
|
||||
to multiple clients over a local network.
|
||||
|
||||
\image broadcastsender-example.png
|
||||
*/
|
138
examples/network/doc/src/fortuneclient.qdoc
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example fortuneclient
|
||||
\title Fortune Client
|
||||
\examplecategory {Networking}
|
||||
\meta tags {tcp,network}
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to create a client for a network service.
|
||||
|
||||
This example uses QTcpSocket, and is intended to be run alongside the
|
||||
\l{fortuneserver}{Fortune Server} example or
|
||||
the \l{threadedfortuneserver}{Threaded Fortune Server} example.
|
||||
|
||||
\image fortuneclient-example.png Screenshot of the Fortune Client example
|
||||
|
||||
This example uses a simple QDataStream-based data transfer protocol to
|
||||
request a line of text from a fortune server (from the
|
||||
\l{fortuneserver}{Fortune Server} example). The client requests a
|
||||
fortune by simply connecting to the server. The server then responds with
|
||||
a QString which contains the fortune text.
|
||||
|
||||
QTcpSocket supports two general approaches to network programming:
|
||||
|
||||
\list
|
||||
|
||||
\li \e{The asynchronous (non-blocking) approach.} Operations are scheduled
|
||||
and performed when control returns to Qt's event loop. When the operation
|
||||
is finished, QTcpSocket emits a signal. For example,
|
||||
QTcpSocket::connectToHost() returns immediately, and when the connection
|
||||
has been established, QTcpSocket emits
|
||||
\l{QTcpSocket::connected()}{connected()}.
|
||||
|
||||
\li \e{The synchronous (blocking) approach.} In non-GUI and multithreaded
|
||||
applications, you can call the \c waitFor...() functions (e.g.,
|
||||
QTcpSocket::waitForConnected()) to suspend the calling thread until the
|
||||
operation has completed, instead of connecting to signals.
|
||||
|
||||
\endlist
|
||||
|
||||
In this example, we will demonstrate the asynchronous approach. The
|
||||
\l{blockingfortuneclient}{Blocking Fortune Client} example
|
||||
illustrates the synchronous approach.
|
||||
|
||||
Our class contains some data and a few private slots:
|
||||
|
||||
\snippet fortuneclient/client.h 0
|
||||
|
||||
Other than the widgets that make up the GUI, the data members include a
|
||||
QTcpSocket pointer, a QDataStream object that operates on the socket, and
|
||||
a copy of the fortune text currently displayed.
|
||||
|
||||
The socket is initialized in the Client constructor. We'll pass the main
|
||||
widget as parent, so that we won't have to worry about deleting the
|
||||
socket:
|
||||
|
||||
\snippet fortuneclient/client.cpp 0
|
||||
\dots
|
||||
\snippet fortuneclient/client.cpp 1
|
||||
|
||||
The protocol is based on QDataStream, so we set the stream device to the
|
||||
newly created socket. We then explicitly set the protocol version of the
|
||||
stream to QDataStream::Qt_4_0 to ensure that we're using the same version
|
||||
as the fortune server, no matter which version of Qt the client and
|
||||
server use.
|
||||
|
||||
The only QTcpSocket signals we need in this example are
|
||||
QTcpSocket::readyRead(), signifying that data has been received, and
|
||||
QTcpSocket::errorOccurred(), which we will use to catch any connection errors:
|
||||
|
||||
\dots
|
||||
\snippet fortuneclient/client.cpp 3
|
||||
\dots
|
||||
\snippet fortuneclient/client.cpp 5
|
||||
|
||||
Clicking the \uicontrol{Get Fortune} button will invoke the \c
|
||||
requestNewFortune() slot:
|
||||
|
||||
\snippet fortuneclient/client.cpp 6
|
||||
|
||||
Because we allow the user to click \uicontrol{Get Fortune} before the
|
||||
previous connection finished closing, we start off by aborting the
|
||||
previous connection by calling QTcpSocket::abort(). (On an unconnected
|
||||
socket, this function does nothing.) We then proceed to connecting to the
|
||||
fortune server by calling QTcpSocket::connectToHost(), passing the
|
||||
hostname and port from the user interface as arguments.
|
||||
|
||||
As a result of calling \l{QTcpSocket::connectToHost()}{connectToHost()},
|
||||
one of two things can happen:
|
||||
|
||||
\list
|
||||
\li \e{The connection is established.} In this case, the server will send us a
|
||||
fortune. QTcpSocket will emit \l{QTcpSocket::readyRead()}{readyRead()}
|
||||
every time it receives a block of data.
|
||||
|
||||
\li \e{An error occurs.} We need to inform the user if the connection
|
||||
failed or was broken. In this case, QTcpSocket will emit
|
||||
\l{QTcpSocket::errorOccurred()}{errorOccurred()}, and \c Client::displayError() will be
|
||||
called.
|
||||
\endlist
|
||||
|
||||
Let's go through the \l{QTcpSocket::errorOccurred()}{errorOccurred()} case first:
|
||||
|
||||
\snippet fortuneclient/client.cpp 13
|
||||
|
||||
We pop up all errors in a dialog using
|
||||
QMessageBox::information(). QTcpSocket::RemoteHostClosedError is silently
|
||||
ignored, because the fortune server protocol ends with the server closing
|
||||
the connection.
|
||||
|
||||
Now for the \l{QTcpSocket::readyRead()}{readyRead()} alternative. This
|
||||
signal is connected to \c Client::readFortune():
|
||||
|
||||
\snippet fortuneclient/client.cpp 8
|
||||
|
||||
Now, TCP is based on sending a stream of data, so we cannot expect to get
|
||||
the entire fortune in one go. Especially on a slow network, the data can
|
||||
be received in several small fragments. QTcpSocket buffers up all incoming
|
||||
data and emits \l{QTcpSocket::readyRead()}{readyRead()} for every new
|
||||
block that arrives, and it is our job to ensure that we have received all
|
||||
the data we need before we start parsing.
|
||||
|
||||
For this purpose we use a QDataStream read transaction. It keeps reading
|
||||
stream data into an internal buffer and rolls it back in case of an
|
||||
incomplete read. We start by calling startTransaction() which also resets
|
||||
the stream status to indicate that new data was received on the socket.
|
||||
We proceed by using QDataStream's streaming operator to read the fortune
|
||||
from the socket into a QString. Once read, we complete the transaction by
|
||||
calling QDataStream::commitTransaction(). If we did not receive a full
|
||||
packet, this function restores the stream data to the initial position,
|
||||
after which we can wait for a new readyRead() signal.
|
||||
|
||||
After a successful read transaction, we call QLabel::setText() to display
|
||||
the fortune.
|
||||
|
||||
\sa {Fortune Server}, {Blocking Fortune Client}
|
||||
*/
|
75
examples/network/doc/src/fortuneserver.qdoc
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example fortuneserver
|
||||
\title Fortune Server
|
||||
\examplecategory {Networking}
|
||||
\meta tags {tcp,network,server}
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to create a server for a network service.
|
||||
|
||||
This example is intended to be run alongside the
|
||||
\l{fortuneclient}{Fortune Client} example or the
|
||||
\l{blockingfortuneclient}{Blocking Fortune Client} example.
|
||||
|
||||
\image fortuneserver-example.png Screenshot of the Fortune Server example
|
||||
|
||||
It uses QTcpServer to accept incoming TCP connections, and a
|
||||
simple QDataStream based data transfer protocol to write a fortune to the
|
||||
connecting client (from the \l{fortuneclient}{Fortune Client}
|
||||
example), before closing the connection.
|
||||
|
||||
\snippet fortuneserver/server.h 0
|
||||
|
||||
The server is implemented using a simple class with only one slot, for
|
||||
handling incoming connections.
|
||||
|
||||
\snippet fortuneserver/server.cpp 1
|
||||
|
||||
In its constructor, our Server object calls QTcpServer::listen() to set up
|
||||
a QTcpServer to listen on all addresses, on an arbitrary port. In then
|
||||
displays the port QTcpServer picked in a label, so that user knows which
|
||||
port the fortune client should connect to.
|
||||
|
||||
\snippet fortuneserver/server.cpp 2
|
||||
|
||||
Our server generates a list of random fortunes that it can send to
|
||||
connecting clients.
|
||||
|
||||
\snippet fortuneserver/server.cpp 3
|
||||
|
||||
When a client connects to our server, QTcpServer will emit
|
||||
QTcpServer::newConnection(). In turn, this will invoke our
|
||||
sendFortune() slot:
|
||||
|
||||
\snippet fortuneserver/server.cpp 4
|
||||
|
||||
The purpose of this slot is to select a random line from our list of
|
||||
fortunes, encode it into a QByteArray using QDataStream, and then write it
|
||||
to the connecting socket. This is a common way to transfer binary data
|
||||
using QTcpSocket. First we create a QByteArray and a QDataStream object,
|
||||
passing the bytearray to QDataStream's constructor. We then explicitly set
|
||||
the protocol version of QDataStream to QDataStream::Qt_5_10 to ensure that
|
||||
we can communicate with clients from future versions of Qt (see
|
||||
QDataStream::setVersion()). We continue by streaming in a random fortune.
|
||||
|
||||
\snippet fortuneserver/server.cpp 7
|
||||
|
||||
We then call QTcpServer::nextPendingConnection(), which returns the
|
||||
QTcpSocket representing the server side of the connection. By connecting
|
||||
QTcpSocket::disconnected() to QObject::deleteLater(), we ensure that the
|
||||
socket will be deleted after disconnecting.
|
||||
|
||||
\snippet fortuneserver/server.cpp 8
|
||||
|
||||
The encoded fortune is written using QTcpSocket::write(), and we finally
|
||||
call QTcpSocket::disconnectFromHost(), which will close the connection
|
||||
after QTcpSocket has finished writing the fortune to the network. Because
|
||||
QTcpSocket works asynchronously, the data will be written after this
|
||||
function returns, and control goes back to Qt's event loop. The socket
|
||||
will then close, which in turn will cause QObject::deleteLater() to delete
|
||||
it.
|
||||
|
||||
\sa {Fortune Client}, {Threaded Fortune Server}
|
||||
*/
|
81
examples/network/doc/src/http.qdoc
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example http
|
||||
\examplecategory {Networking}
|
||||
\meta tags {http,network,https,proxy}
|
||||
\title HTTP Client
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates a simple HTTP client.
|
||||
|
||||
This example demonstrates how a simple HTTP client can fetch files
|
||||
from remote hosts.
|
||||
|
||||
\image http-example.webp
|
||||
|
||||
The main work of this example is done in the HttpWindow class.
|
||||
Thus we will focus on that.
|
||||
|
||||
\snippet http/httpwindow.cpp qnam-download
|
||||
|
||||
Using QNetworkAccessManager, we begin the download of a resource as
|
||||
pointed to by the \c url. If you are unfamiliar with it or the function used,
|
||||
QNetworkAccessManager::get(), or simply want to look into it in more detail,
|
||||
take a look at its documentation and the documentation for
|
||||
QNetworkReply and QNetworkRequest.
|
||||
|
||||
\snippet http/httpwindow.cpp connecting-reply-to-slots
|
||||
|
||||
Above, we connect some of the reply's signals to slots in the class.
|
||||
These slots will take care of both incoming data and finalizing the
|
||||
download/handling errors.
|
||||
|
||||
\snippet http/httpwindow.cpp networkreply-readyread-1
|
||||
|
||||
As for handling the incoming data, since we don't know the maximum
|
||||
download size of any potential input and we don't want to exhaust
|
||||
the memory of any computer which might run the example program, we
|
||||
handle incoming data in QNetworkReply::readyRead() instead of in
|
||||
QNetworkReply::finished().
|
||||
|
||||
\snippet http/httpwindow.cpp networkreply-readyread-2
|
||||
|
||||
Then we write the data to file as it arrives. It is less convenient,
|
||||
but the application will consume less memory at its peak!
|
||||
|
||||
\snippet http/httpwindow.cpp sslerrors-1
|
||||
|
||||
With the QNetworkReply::sslErrors() signal we can also handle errors that may
|
||||
occur during the TLS handshake when connecting to secure websites (i.e. HTTPS).
|
||||
|
||||
\snippet http/httpwindow.cpp sslerrors-2
|
||||
|
||||
In this example, we show a dialog to the user so that they can choose whether
|
||||
or not to ignore the errors.
|
||||
|
||||
\snippet http/httpwindow.cpp networkreply-error-handling-1
|
||||
\snippet http/httpwindow.cpp networkreply-error-handling-2
|
||||
|
||||
If an error occurs then QNetworkReply will emit the
|
||||
QNetworkReply::errorOccurred() signal, followed by the
|
||||
QNetworkReply::finished() signal. In this example, we only connect to the
|
||||
latter. We handle any potential error(s) in the respective slot by deleting
|
||||
the file we were writing to, and display the error with our status label.
|
||||
|
||||
\snippet http/httpwindow.cpp qnam-auth-required-1
|
||||
|
||||
If you connect to a website that uses
|
||||
\l{https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication}{HTTP authentication},
|
||||
assuming you didn't supply the credentials that should be used ahead of time,
|
||||
you can handle missing credentials when the website requests it. With QNetworkAccessManager,
|
||||
we do this in a slot connected to the signal
|
||||
QNetworkAccessManager::authenticationRequired(). We make this connection once,
|
||||
in the constructor.
|
||||
|
||||
\snippet http/httpwindow.cpp qnam-auth-required-2
|
||||
|
||||
In this example, we show a dialog where the user can either insert a
|
||||
username and password, or cancel. Canceling causes the request to fail.
|
||||
|
||||
*/
|
14
examples/network/doc/src/multicastreceiver.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example multicastreceiver
|
||||
\title Multicast Receiver
|
||||
\examplecategory {Networking}
|
||||
\meta tags {network,multicast,ipv6,ipv4,udp}
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to receive information sent to a multicast group.
|
||||
|
||||
This example demonstrates how to receive messages sent to a multicast group.
|
||||
\image multicastreceiver-example.webp
|
||||
*/
|
16
examples/network/doc/src/multicastsender.qdoc
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example multicastsender
|
||||
\title Multicast Sender
|
||||
\examplecategory {Networking}
|
||||
\meta tags {network,multicast,ipv6,ipv4,udp}
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to send messages to a multicast group.
|
||||
|
||||
This example demonstrates how to send messages to the clients of a
|
||||
multicast group.
|
||||
|
||||
\image multicastsender-example.webp
|
||||
*/
|
14
examples/network/doc/src/network-chat.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example network-chat
|
||||
\title Network Chat Example
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates a stateful peer-to-peer Chat client.
|
||||
|
||||
This example uses broadcasting with QUdpSocket and QNetworkInterface to
|
||||
discover its peers.
|
||||
|
||||
\image network-chat-example.png
|
||||
*/
|
16
examples/network/doc/src/securesocketclient.qdoc
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example securesocketclient
|
||||
\title Secure Socket Client Example
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates how to communicate over an encrypted (SSL) connection.
|
||||
|
||||
This example uses QSslSocket to demonstrate how to communicate over an
|
||||
encrypted connection, deal with authenticity problems, and display security
|
||||
and certificate information.
|
||||
|
||||
\image securesocketclient.png
|
||||
\image securesocketclient2.png
|
||||
*/
|
100
examples/network/doc/src/secureudpclient.qdoc
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example secureudpclient
|
||||
\title DTLS client
|
||||
\ingroup examples-network
|
||||
\brief This example demonstrates how to implement client-side DTLS connections.
|
||||
|
||||
\image secureudpclient-example.png Screenshot of the DTLS client example.
|
||||
|
||||
\note The DTLS client example is intended to be run alongside the \l{secureudpserver}{DTLS server} example.
|
||||
|
||||
The example DTLS client can establish several DTLS connections to one
|
||||
or many DTLS servers. A client-side DTLS connection is implemented by the
|
||||
DtlsAssociation class. This class uses QUdpSocket to read and write datagrams
|
||||
and QDtls for encryption:
|
||||
|
||||
\snippet secureudpclient/association.h 0
|
||||
|
||||
The constructor sets the minimal TLS configuration for the new DTLS connection,
|
||||
and sets the address and the port of the server:
|
||||
|
||||
\dots
|
||||
\snippet secureudpclient/association.cpp 1
|
||||
\dots
|
||||
|
||||
The QDtls::handshakeTimeout() signal is connected to the handleTimeout() slot
|
||||
to deal with packet loss and retransmission during the handshake phase:
|
||||
|
||||
\dots
|
||||
\snippet secureudpclient/association.cpp 2
|
||||
\dots
|
||||
|
||||
To ensure we receive only the datagrams from the server, we connect our UDP socket to the server:
|
||||
|
||||
\dots
|
||||
\snippet secureudpclient/association.cpp 3
|
||||
\dots
|
||||
|
||||
The QUdpSocket::readyRead() signal is connected to the readyRead() slot:
|
||||
|
||||
\dots
|
||||
\snippet secureudpclient/association.cpp 13
|
||||
\dots
|
||||
|
||||
When a secure connection to a server is established, a DtlsAssociation object
|
||||
will be sending short ping messages to the server, using a timer:
|
||||
|
||||
\snippet secureudpclient/association.cpp 4
|
||||
|
||||
startHandshake() starts a handshake with the server:
|
||||
|
||||
\snippet secureudpclient/association.cpp 5
|
||||
|
||||
The readyRead() slot reads a datagram sent by the server:
|
||||
|
||||
\snippet secureudpclient/association.cpp 6
|
||||
|
||||
If the handshake was already completed, this datagram is decrypted:
|
||||
|
||||
\snippet secureudpclient/association.cpp 7
|
||||
|
||||
otherwise, we try to continue the handshake:
|
||||
|
||||
\snippet secureudpclient/association.cpp 8
|
||||
|
||||
When the handshake has completed, we send our first ping message:
|
||||
|
||||
\snippet secureudpclient/association.cpp 9
|
||||
|
||||
The pskRequired() slot provides the Pre-Shared Key (PSK) needed during the handshake
|
||||
phase:
|
||||
|
||||
\snippet secureudpclient/association.cpp 14
|
||||
|
||||
\note For the sake of brevity, the definition of pskRequired() is oversimplified.
|
||||
The documentation for the QSslPreSharedKeyAuthenticator class explains in detail
|
||||
how this slot can be properly implemented.
|
||||
|
||||
pingTimeout() sends an encrypted message to the server:
|
||||
|
||||
\snippet secureudpclient/association.cpp 10
|
||||
|
||||
During the handshake phase the client must handle possible timeouts, which
|
||||
can happen due to packet loss. The handshakeTimeout() slot retransmits
|
||||
the handshake messages:
|
||||
|
||||
\snippet secureudpclient/association.cpp 11
|
||||
|
||||
Before a client connection is destroyed, its DTLS connection must be shut down:
|
||||
|
||||
\snippet secureudpclient/association.cpp 12
|
||||
|
||||
Error messages, informational messages, and decrypted responses from servers
|
||||
are displayed by the UI:
|
||||
|
||||
\snippet secureudpclient/mainwindow.cpp 0
|
||||
*/
|
||||
|
107
examples/network/doc/src/secureudpserver.qdoc
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example secureudpserver
|
||||
\title DTLS server
|
||||
\ingroup examples-network
|
||||
\brief This examples demonstrates how to implement a simple DTLS server.
|
||||
|
||||
\image secureudpserver-example.png Screenshot of the DTLS server example.
|
||||
|
||||
\note The DTLS server example is intended to be run alongside the \l{secureudpclient}{DTLS client} example.
|
||||
|
||||
The server is implemented by the DtlsServer class. It uses QUdpSocket,
|
||||
QDtlsClientVerifier, and QDtls to test each client's reachability, complete a handshake,
|
||||
and read and write encrypted messages.
|
||||
|
||||
\snippet secureudpserver/server.h 0
|
||||
|
||||
The constructor connects the QUdpSocket::readyRead() signal to its
|
||||
readyRead() slot and sets the minimal needed TLS configuration:
|
||||
|
||||
\snippet secureudpserver/server.cpp 1
|
||||
|
||||
\note The server is not using a certificate and is relying on Pre-Shared
|
||||
Key (PSK) handshake.
|
||||
|
||||
listen() binds QUdpSocket:
|
||||
|
||||
\snippet secureudpserver/server.cpp 2
|
||||
|
||||
The readyRead() slot processes incoming datagrams:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 3
|
||||
\dots
|
||||
|
||||
After extracting an address and a port number, the server first tests
|
||||
if it's a datagram from an already known peer:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 4
|
||||
\dots
|
||||
|
||||
If it is a new, unknown address and port, the datagram is processed as a
|
||||
potential ClientHello message, sent by a DTLS client:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 5
|
||||
\dots
|
||||
|
||||
If it's a known DTLS client, the server either decrypts the datagram:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 6
|
||||
\dots
|
||||
|
||||
or continues a handshake with this peer:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 7
|
||||
\dots
|
||||
|
||||
handleNewConnection() verifies it's a reachable DTLS client, or sends a
|
||||
HelloVerifyRequest:
|
||||
|
||||
\snippet secureudpserver/server.cpp 8
|
||||
\dots
|
||||
|
||||
If the new client was verified to be a reachable DTLS client, the server creates
|
||||
and configures a new QDtls object, and starts a server-side handshake:
|
||||
|
||||
\dots
|
||||
\snippet secureudpserver/server.cpp 9
|
||||
\dots
|
||||
|
||||
doHandshake() progresses through the handshake phase:
|
||||
|
||||
\snippet secureudpserver/server.cpp 11
|
||||
|
||||
During the handshake phase, the QDtls::pskRequired() signal is emitted and
|
||||
the pskRequired() slot provides the preshared key:
|
||||
|
||||
\snippet secureudpserver/server.cpp 13
|
||||
|
||||
\note For the sake of brevity, the definition of pskRequired() is oversimplified.
|
||||
The documentation for the QSslPreSharedKeyAuthenticator class explains in detail
|
||||
how this slot can be properly implemented.
|
||||
|
||||
After the handshake is completed for the network peer, an encrypted DTLS
|
||||
connection is considered to be established and the server decrypts subsequent
|
||||
datagrams, sent by the peer, by calling decryptDatagram(). The server also
|
||||
sends an encrypted response to the peer:
|
||||
|
||||
\snippet secureudpserver/server.cpp 12
|
||||
|
||||
The server closes its DTLS connections by calling QDtls::shutdown():
|
||||
|
||||
\snippet secureudpserver/server.cpp 14
|
||||
|
||||
During its operation, the server reports errors, informational messages, and
|
||||
decrypted datagrams, by emitting signals errorMessage(), warningMessage(),
|
||||
infoMessage(), and datagramReceived(). These messages are logged by the server's
|
||||
UI:
|
||||
|
||||
\snippet secureudpserver/mainwindow.cpp 0
|
||||
*/
|
85
examples/network/doc/src/threadedfortuneserver.qdoc
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example threadedfortuneserver
|
||||
\title Threaded Fortune Server
|
||||
\examplecategory {Networking}
|
||||
\meta tags {tcp,network,threading,server,synchronous-io}
|
||||
\ingroup examples-network
|
||||
|
||||
\brief The Threaded Fortune Server example shows how to create a server for a
|
||||
simple network service that uses threads to handle requests from different
|
||||
clients. It is intended to be run alongside the Fortune Client example.
|
||||
|
||||
\image threadedfortuneserver-example.png
|
||||
|
||||
The implementation of this example is similar to that of the
|
||||
\l{fortuneserver}{Fortune Server} example, but here we will
|
||||
implement a subclass of QTcpServer that starts each connection in a
|
||||
different thread.
|
||||
|
||||
For this we need two classes: FortuneServer, a QTcpServer subclass, and
|
||||
FortuneThread, which inherits QThread.
|
||||
|
||||
\snippet threadedfortuneserver/fortuneserver.h 0
|
||||
|
||||
FortuneServer inherits QTcpServer and reimplements
|
||||
QTcpServer::incomingConnection(). We also use it for storing the list of
|
||||
random fortunes.
|
||||
|
||||
\snippet threadedfortuneserver/fortuneserver.cpp 0
|
||||
|
||||
We use FortuneServer's constructor to simply generate the list of
|
||||
fortunes.
|
||||
|
||||
\snippet threadedfortuneserver/fortuneserver.cpp 1
|
||||
|
||||
Our implementation of QTcpServer::incomingConnection() creates a
|
||||
FortuneThread object, passing the incoming socket descriptor and a random
|
||||
fortune to FortuneThread's constructor. By connecting FortuneThread's
|
||||
finished() signal to QObject::deleteLater(), we ensure that the thread
|
||||
gets deleted once it has finished. We can then call QThread::start(),
|
||||
which starts the thread.
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.h 0
|
||||
|
||||
Moving on to the FortuneThread class, this is a QThread subclass whose job
|
||||
is to write the fortune to the connected socket. The class reimplements
|
||||
QThread::run(), and it has a signal for reporting errors.
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.cpp 0
|
||||
|
||||
FortuneThread's constructor simply stores the socket descriptor and
|
||||
fortune text, so that they are available for run() later on.
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.cpp 1
|
||||
|
||||
The first thing our run() function does is to create a QTcpSocket object
|
||||
on the stack. What's worth noticing is that we are creating this object
|
||||
inside the thread, which automatically associates the socket to the
|
||||
thread's event loop. This ensures that Qt will not try to deliver events
|
||||
to our socket from the main thread while we are accessing it from
|
||||
FortuneThread::run().
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.cpp 2
|
||||
|
||||
The socket is initialized by calling QTcpSocket::setSocketDescriptor(),
|
||||
passing our socket descriptor as an argument. We expect this to succeed,
|
||||
but just to be sure, (although unlikely, the system may run out of
|
||||
resources,) we catch the return value and report any error.
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.cpp 3
|
||||
|
||||
As with the \l{fortuneserver}{Fortune Server} example, we encode
|
||||
the fortune into a QByteArray using QDataStream.
|
||||
|
||||
\snippet threadedfortuneserver/fortunethread.cpp 4
|
||||
|
||||
But unlike the previous example, we finish off by calling
|
||||
QTcpSocket::waitForDisconnected(), which blocks the calling thread until
|
||||
the socket has disconnected. Because we are running in a separate thread,
|
||||
the GUI will remain responsive.
|
||||
|
||||
\sa {Fortune Server}, {Fortune Client}, {Blocking Fortune Client}
|
||||
*/
|
14
examples/network/doc/src/torrent.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example torrent
|
||||
\title Torrent Example
|
||||
\ingroup examples-network
|
||||
\brief Demonstrates complex TCP/IP operations.
|
||||
|
||||
This example demonstrates some of the complex TCP/IP operations
|
||||
supported by the Qt Network APIs.
|
||||
|
||||
\image torrent-example.png
|
||||
*/
|
38
examples/network/fortuneclient/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fortuneclient LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/fortuneclient")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(fortuneclient
|
||||
client.cpp client.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(fortuneclient PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(fortuneclient PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS fortuneclient
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
176
examples/network/fortuneclient/client.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "client.h"
|
||||
|
||||
//! [0]
|
||||
Client::Client(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, hostCombo(new QComboBox)
|
||||
, portLineEdit(new QLineEdit)
|
||||
, getFortuneButton(new QPushButton(tr("Get Fortune")))
|
||||
, tcpSocket(new QTcpSocket(this))
|
||||
{
|
||||
//! [0]
|
||||
hostCombo->setEditable(true);
|
||||
// find out name of this machine
|
||||
QString name = QHostInfo::localHostName();
|
||||
if (!name.isEmpty()) {
|
||||
hostCombo->addItem(name);
|
||||
QString domain = QHostInfo::localDomainName();
|
||||
if (!domain.isEmpty())
|
||||
hostCombo->addItem(name + QChar('.') + domain);
|
||||
}
|
||||
if (name != QLatin1String("localhost"))
|
||||
hostCombo->addItem(QString("localhost"));
|
||||
// find out IP addresses of this machine
|
||||
const QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
|
||||
// add non-localhost addresses
|
||||
for (const QHostAddress &entry : ipAddressesList) {
|
||||
if (!entry.isLoopback())
|
||||
hostCombo->addItem(entry.toString());
|
||||
}
|
||||
// add localhost addresses
|
||||
for (const QHostAddress &entry : ipAddressesList) {
|
||||
if (entry.isLoopback())
|
||||
hostCombo->addItem(entry.toString());
|
||||
}
|
||||
|
||||
portLineEdit->setValidator(new QIntValidator(1, 65535, this));
|
||||
|
||||
auto hostLabel = new QLabel(tr("&Server name:"));
|
||||
hostLabel->setBuddy(hostCombo);
|
||||
auto portLabel = new QLabel(tr("S&erver port:"));
|
||||
portLabel->setBuddy(portLineEdit);
|
||||
|
||||
statusLabel = new QLabel(tr("This examples requires that you run the "
|
||||
"Fortune Server example as well."));
|
||||
|
||||
getFortuneButton->setDefault(true);
|
||||
getFortuneButton->setEnabled(false);
|
||||
|
||||
auto quitButton = new QPushButton(tr("Quit"));
|
||||
|
||||
auto buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
//! [1]
|
||||
in.setDevice(tcpSocket);
|
||||
in.setVersion(QDataStream::Qt_6_5);
|
||||
//! [1]
|
||||
|
||||
connect(hostCombo, &QComboBox::editTextChanged,
|
||||
this, &Client::enableGetFortuneButton);
|
||||
connect(portLineEdit, &QLineEdit::textChanged,
|
||||
this, &Client::enableGetFortuneButton);
|
||||
connect(getFortuneButton, &QAbstractButton::clicked,
|
||||
this, &Client::requestNewFortune);
|
||||
connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
|
||||
//! [2] //! [3]
|
||||
connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune);
|
||||
//! [2] //! [4]
|
||||
connect(tcpSocket, &QAbstractSocket::errorOccurred,
|
||||
//! [3]
|
||||
this, &Client::displayError);
|
||||
//! [4]
|
||||
|
||||
QGridLayout *mainLayout = nullptr;
|
||||
if (QGuiApplication::styleHints()->showIsFullScreen() || QGuiApplication::styleHints()->showIsMaximized()) {
|
||||
auto outerVerticalLayout = new QVBoxLayout(this);
|
||||
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
|
||||
auto outerHorizontalLayout = new QHBoxLayout;
|
||||
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
|
||||
auto groupBox = new QGroupBox(QGuiApplication::applicationDisplayName());
|
||||
mainLayout = new QGridLayout(groupBox);
|
||||
outerHorizontalLayout->addWidget(groupBox);
|
||||
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
|
||||
outerVerticalLayout->addLayout(outerHorizontalLayout);
|
||||
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
|
||||
} else {
|
||||
mainLayout = new QGridLayout(this);
|
||||
}
|
||||
mainLayout->addWidget(hostLabel, 0, 0);
|
||||
mainLayout->addWidget(hostCombo, 0, 1);
|
||||
mainLayout->addWidget(portLabel, 1, 0);
|
||||
mainLayout->addWidget(portLineEdit, 1, 1);
|
||||
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
|
||||
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
|
||||
|
||||
setWindowTitle(QGuiApplication::applicationDisplayName());
|
||||
portLineEdit->setFocus();
|
||||
//! [5]
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
void Client::requestNewFortune()
|
||||
{
|
||||
getFortuneButton->setEnabled(false);
|
||||
tcpSocket->abort();
|
||||
//! [7]
|
||||
tcpSocket->connectToHost(hostCombo->currentText(),
|
||||
portLineEdit->text().toInt());
|
||||
//! [7]
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [8]
|
||||
void Client::readFortune()
|
||||
{
|
||||
in.startTransaction();
|
||||
|
||||
QString nextFortune;
|
||||
in >> nextFortune;
|
||||
|
||||
if (!in.commitTransaction())
|
||||
return;
|
||||
|
||||
if (nextFortune == currentFortune) {
|
||||
QTimer::singleShot(0, this, &Client::requestNewFortune);
|
||||
return;
|
||||
}
|
||||
|
||||
currentFortune = nextFortune;
|
||||
statusLabel->setText(currentFortune);
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
//! [8]
|
||||
|
||||
//! [13]
|
||||
void Client::displayError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
switch (socketError) {
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
break;
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
QMessageBox::information(this, tr("Fortune Client"),
|
||||
tr("The host was not found. Please check the "
|
||||
"host name and port settings."));
|
||||
break;
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
QMessageBox::information(this, tr("Fortune Client"),
|
||||
tr("The connection was refused by the peer. "
|
||||
"Make sure the fortune server is running, "
|
||||
"and check that the host name and port "
|
||||
"settings are correct."));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::information(this, tr("Fortune Client"),
|
||||
tr("The following error occurred: %1.")
|
||||
.arg(tcpSocket->errorString()));
|
||||
}
|
||||
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
//! [13]
|
||||
|
||||
void Client::enableGetFortuneButton()
|
||||
{
|
||||
getFortuneButton->setEnabled(!hostCombo->currentText().isEmpty() &&
|
||||
!portLineEdit->text().isEmpty());
|
||||
|
||||
}
|
45
examples/network/fortuneclient/client.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDialog>
|
||||
#include <QTcpSocket>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QTcpSocket;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [0]
|
||||
class Client : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Client(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void requestNewFortune();
|
||||
void readFortune();
|
||||
void displayError(QAbstractSocket::SocketError socketError);
|
||||
void enableGetFortuneButton();
|
||||
|
||||
private:
|
||||
QComboBox *hostCombo = nullptr;
|
||||
QLineEdit *portLineEdit = nullptr;
|
||||
QLabel *statusLabel = nullptr;
|
||||
QPushButton *getFortuneButton = nullptr;
|
||||
|
||||
QTcpSocket *tcpSocket = nullptr;
|
||||
QDataStream in;
|
||||
QString currentFortune;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
10
examples/network/fortuneclient/fortuneclient.pro
Normal file
@ -0,0 +1,10 @@
|
||||
QT += network widgets
|
||||
requires(qtConfig(combobox))
|
||||
|
||||
HEADERS = client.h
|
||||
SOURCES = client.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/fortuneclient
|
||||
INSTALLS += target
|
14
examples/network/fortuneclient/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
#include "client.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setApplicationDisplayName(Client::tr("Fortune Client"));
|
||||
Client client;
|
||||
client.show();
|
||||
return app.exec();
|
||||
}
|
38
examples/network/fortuneserver/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fortuneserver LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/fortuneserver")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(fortuneserver
|
||||
main.cpp
|
||||
server.cpp server.h
|
||||
)
|
||||
|
||||
set_target_properties(fortuneserver PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(fortuneserver PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS fortuneserver
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
9
examples/network/fortuneserver/fortuneserver.pro
Normal file
@ -0,0 +1,9 @@
|
||||
QT += network widgets
|
||||
|
||||
HEADERS = server.h
|
||||
SOURCES = server.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/fortuneserver
|
||||
INSTALLS += target
|
15
examples/network/fortuneserver/main.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setApplicationDisplayName(Server::tr("Fortune Server"));
|
||||
Server server;
|
||||
server.show();
|
||||
return app.exec();
|
||||
}
|
111
examples/network/fortuneserver/server.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
#include <QtCore>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
Server::Server(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, statusLabel(new QLabel)
|
||||
{
|
||||
statusLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
|
||||
initServer();
|
||||
|
||||
//! [2]
|
||||
fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
|
||||
<< tr("You've got to think about tomorrow.")
|
||||
<< tr("You will be surprised by a loud noise.")
|
||||
<< tr("You will feel hungry again in another hour.")
|
||||
<< tr("You might have mail.")
|
||||
<< tr("You cannot kill time without injuring eternity.")
|
||||
<< tr("Computers are not intelligent. They only think they are.");
|
||||
//! [2]
|
||||
auto quitButton = new QPushButton(tr("Quit"));
|
||||
quitButton->setAutoDefault(false);
|
||||
connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
|
||||
//! [3]
|
||||
connect(tcpServer, &QTcpServer::newConnection, this, &Server::sendFortune);
|
||||
//! [3]
|
||||
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(quitButton);
|
||||
buttonLayout->addStretch(1);
|
||||
|
||||
QVBoxLayout *mainLayout = nullptr;
|
||||
if (QGuiApplication::styleHints()->showIsFullScreen() || QGuiApplication::styleHints()->showIsMaximized()) {
|
||||
auto outerVerticalLayout = new QVBoxLayout(this);
|
||||
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
|
||||
auto outerHorizontalLayout = new QHBoxLayout;
|
||||
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
|
||||
auto groupBox = new QGroupBox(QGuiApplication::applicationDisplayName());
|
||||
mainLayout = new QVBoxLayout(groupBox);
|
||||
outerHorizontalLayout->addWidget(groupBox);
|
||||
outerHorizontalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
|
||||
outerVerticalLayout->addLayout(outerHorizontalLayout);
|
||||
outerVerticalLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
|
||||
} else {
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
}
|
||||
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
setWindowTitle(QGuiApplication::applicationDisplayName());
|
||||
}
|
||||
|
||||
void Server::initServer()
|
||||
{
|
||||
//! [0] //! [1]
|
||||
tcpServer = new QTcpServer(this);
|
||||
if (!tcpServer->listen()) {
|
||||
QMessageBox::critical(this, tr("Fortune Server"),
|
||||
tr("Unable to start the server: %1.")
|
||||
.arg(tcpServer->errorString()));
|
||||
close();
|
||||
return;
|
||||
}
|
||||
//! [0]
|
||||
QString ipAddress;
|
||||
const QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
|
||||
// use the first non-localhost IPv4 address
|
||||
for (const QHostAddress &entry : ipAddressesList) {
|
||||
if (entry != QHostAddress::LocalHost && entry.toIPv4Address()) {
|
||||
ipAddress = entry.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we did not find one, use IPv4 localhost
|
||||
if (ipAddress.isEmpty())
|
||||
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
|
||||
statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n"
|
||||
"Run the Fortune Client example now.")
|
||||
.arg(ipAddress).arg(tcpServer->serverPort()));
|
||||
//! [1]
|
||||
}
|
||||
|
||||
//! [4]
|
||||
void Server::sendFortune()
|
||||
{
|
||||
//! [5]
|
||||
QByteArray block;
|
||||
QDataStream out(&block, QIODevice::WriteOnly);
|
||||
out.setVersion(QDataStream::Qt_6_5);
|
||||
|
||||
out << fortunes[QRandomGenerator::global()->bounded(fortunes.size())];
|
||||
//! [4] //! [7]
|
||||
|
||||
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
|
||||
connect(clientConnection, &QAbstractSocket::disconnected,
|
||||
clientConnection, &QObject::deleteLater);
|
||||
//! [7] //! [8]
|
||||
|
||||
clientConnection->write(block);
|
||||
clientConnection->disconnectFromHost();
|
||||
//! [5]
|
||||
}
|
||||
//! [8]
|
36
examples/network/fortuneserver/server.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QTcpServer;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [0]
|
||||
class Server : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Server(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void sendFortune();
|
||||
|
||||
private:
|
||||
void initServer();
|
||||
|
||||
QLabel *statusLabel = nullptr;
|
||||
QTcpServer *tcpServer = nullptr;
|
||||
QList<QString> fortunes;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
39
examples/network/http/CMakeLists.txt
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(http LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/http")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(http
|
||||
authenticationdialog.ui
|
||||
httpwindow.cpp httpwindow.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(http PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(http PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS http
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
133
examples/network/http/authenticationdialog.ui
Normal file
@ -0,0 +1,133 @@
|
||||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>389</width>
|
||||
<height>243</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Http authentication required</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>You need to supply a Username and a Password to access this site</string>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QLineEdit" name="userEdit" />
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<widget class="QLineEdit" name="passwordEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2" >
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="text" >
|
||||
<string>Site:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QLabel" name="siteDescription" >
|
||||
<property name="font" >
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>%1 at %2</string>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
10
examples/network/http/http.pro
Normal file
@ -0,0 +1,10 @@
|
||||
QT += network widgets
|
||||
|
||||
HEADERS += httpwindow.h
|
||||
SOURCES += httpwindow.cpp \
|
||||
main.cpp
|
||||
FORMS += authenticationdialog.ui
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/http
|
||||
INSTALLS += target
|
306
examples/network/http/httpwindow.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "httpwindow.h"
|
||||
|
||||
#include "ui_authenticationdialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
#include <QUrl>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#if QT_CONFIG(ssl)
|
||||
const char defaultUrl[] = "https://www.qt.io/";
|
||||
#else
|
||||
const char defaultUrl[] = "http://www.qt.io/";
|
||||
#endif
|
||||
const char defaultFileName[] = "index.html";
|
||||
|
||||
ProgressDialog::ProgressDialog(const QUrl &url, QWidget *parent)
|
||||
: QProgressDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Download Progress"));
|
||||
setLabelText(tr("Downloading %1.").arg(url.toDisplayString()));
|
||||
setMinimum(0);
|
||||
setValue(0);
|
||||
setMinimumDuration(0);
|
||||
setMinimumSize(QSize(400, 75));
|
||||
}
|
||||
|
||||
void ProgressDialog::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
setMaximum(totalBytes);
|
||||
setValue(bytesRead);
|
||||
}
|
||||
|
||||
HttpWindow::HttpWindow(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, statusLabel(new QLabel(tr("Please enter the URL of a file you want to download.\n\n"), this))
|
||||
, urlLineEdit(new QLineEdit(defaultUrl))
|
||||
, downloadButton(new QPushButton(tr("Download")))
|
||||
, launchCheckBox(new QCheckBox(tr("Launch file")))
|
||||
, defaultFileLineEdit(new QLineEdit(defaultFileName))
|
||||
, downloadDirectoryLineEdit(new QLineEdit)
|
||||
{
|
||||
setWindowTitle(tr("HTTP Client"));
|
||||
|
||||
//! [qnam-auth-required-1]
|
||||
connect(&qnam, &QNetworkAccessManager::authenticationRequired,
|
||||
this, &HttpWindow::slotAuthenticationRequired);
|
||||
//! [qnam-auth-required-1]
|
||||
#if QT_CONFIG(networkproxy)
|
||||
connect(&qnam, &QNetworkAccessManager::proxyAuthenticationRequired,
|
||||
this, &HttpWindow::slotProxyAuthenticationRequired);
|
||||
#endif
|
||||
|
||||
QFormLayout *formLayout = new QFormLayout;
|
||||
urlLineEdit->setClearButtonEnabled(true);
|
||||
connect(urlLineEdit, &QLineEdit::textChanged, this, &HttpWindow::enableDownloadButton);
|
||||
formLayout->addRow(tr("&URL:"), urlLineEdit);
|
||||
QString downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
||||
if (downloadDirectory.isEmpty() || !QFileInfo(downloadDirectory).isDir())
|
||||
downloadDirectory = QDir::currentPath();
|
||||
downloadDirectoryLineEdit->setText(QDir::toNativeSeparators(downloadDirectory));
|
||||
formLayout->addRow(tr("&Download directory:"), downloadDirectoryLineEdit);
|
||||
formLayout->addRow(tr("Default &file:"), defaultFileLineEdit);
|
||||
launchCheckBox->setChecked(true);
|
||||
formLayout->addRow(launchCheckBox);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addLayout(formLayout);
|
||||
|
||||
mainLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
|
||||
|
||||
statusLabel->setWordWrap(true);
|
||||
mainLayout->addWidget(statusLabel);
|
||||
|
||||
downloadButton->setDefault(true);
|
||||
connect(downloadButton, &QAbstractButton::clicked, this, &HttpWindow::downloadFile);
|
||||
QPushButton *quitButton = new QPushButton(tr("Quit"));
|
||||
quitButton->setAutoDefault(false);
|
||||
connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(downloadButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
|
||||
urlLineEdit->setFocus();
|
||||
}
|
||||
HttpWindow::~HttpWindow() = default;
|
||||
|
||||
void HttpWindow::startRequest(const QUrl &requestedUrl)
|
||||
{
|
||||
url = requestedUrl;
|
||||
httpRequestAborted = false;
|
||||
|
||||
//! [qnam-download]
|
||||
reply.reset(qnam.get(QNetworkRequest(url)));
|
||||
//! [qnam-download]
|
||||
//! [connecting-reply-to-slots]
|
||||
connect(reply.get(), &QNetworkReply::finished, this, &HttpWindow::httpFinished);
|
||||
//! [networkreply-readyread-1]
|
||||
connect(reply.get(), &QIODevice::readyRead, this, &HttpWindow::httpReadyRead);
|
||||
//! [networkreply-readyread-1]
|
||||
#if QT_CONFIG(ssl)
|
||||
//! [sslerrors-1]
|
||||
connect(reply.get(), &QNetworkReply::sslErrors, this, &HttpWindow::sslErrors);
|
||||
//! [sslerrors-1]
|
||||
#endif
|
||||
//! [connecting-reply-to-slots]
|
||||
|
||||
ProgressDialog *progressDialog = new ProgressDialog(url, this);
|
||||
progressDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(progressDialog, &QProgressDialog::canceled, this, &HttpWindow::cancelDownload);
|
||||
connect(reply.get(), &QNetworkReply::downloadProgress,
|
||||
progressDialog, &ProgressDialog::networkReplyProgress);
|
||||
connect(reply.get(), &QNetworkReply::finished, progressDialog, &ProgressDialog::hide);
|
||||
progressDialog->show();
|
||||
|
||||
statusLabel->setText(tr("Downloading %1...").arg(url.toString()));
|
||||
}
|
||||
|
||||
void HttpWindow::downloadFile()
|
||||
{
|
||||
const QString urlSpec = urlLineEdit->text().trimmed();
|
||||
if (urlSpec.isEmpty())
|
||||
return;
|
||||
|
||||
const QUrl newUrl = QUrl::fromUserInput(urlSpec);
|
||||
if (!newUrl.isValid()) {
|
||||
QMessageBox::information(this, tr("Error"),
|
||||
tr("Invalid URL: %1: %2").arg(urlSpec, newUrl.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
QString fileName = newUrl.fileName();
|
||||
if (fileName.isEmpty())
|
||||
fileName = defaultFileLineEdit->text().trimmed();
|
||||
if (fileName.isEmpty())
|
||||
fileName = defaultFileName;
|
||||
QString downloadDirectory = QDir::cleanPath(downloadDirectoryLineEdit->text().trimmed());
|
||||
bool useDirectory = !downloadDirectory.isEmpty() && QFileInfo(downloadDirectory).isDir();
|
||||
if (useDirectory)
|
||||
fileName.prepend(downloadDirectory + '/');
|
||||
|
||||
if (QFile::exists(fileName)) {
|
||||
QString alreadyExists = useDirectory
|
||||
? tr("There already exists a file called %1. Overwrite?")
|
||||
: tr("There already exists a file called %1 in the current directory. "
|
||||
"Overwrite?");
|
||||
QMessageBox::StandardButton response = QMessageBox::question(this,
|
||||
tr("Overwrite Existing File"),
|
||||
alreadyExists.arg(QDir::toNativeSeparators(fileName)),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (response == QMessageBox::No)
|
||||
return;
|
||||
QFile::remove(fileName);
|
||||
}
|
||||
|
||||
file = openFileForWrite(fileName);
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
downloadButton->setEnabled(false);
|
||||
|
||||
// schedule the request
|
||||
startRequest(newUrl);
|
||||
}
|
||||
|
||||
std::unique_ptr<QFile> HttpWindow::openFileForWrite(const QString &fileName)
|
||||
{
|
||||
std::unique_ptr<QFile> file = std::make_unique<QFile>(fileName);
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::information(this, tr("Error"),
|
||||
tr("Unable to save the file %1: %2.")
|
||||
.arg(QDir::toNativeSeparators(fileName),
|
||||
file->errorString()));
|
||||
return nullptr;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
void HttpWindow::cancelDownload()
|
||||
{
|
||||
statusLabel->setText(tr("Download canceled."));
|
||||
httpRequestAborted = true;
|
||||
reply->abort();
|
||||
downloadButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void HttpWindow::httpFinished()
|
||||
{
|
||||
QFileInfo fi;
|
||||
if (file) {
|
||||
fi.setFile(file->fileName());
|
||||
file->close();
|
||||
file.reset();
|
||||
}
|
||||
|
||||
//! [networkreply-error-handling-1]
|
||||
QNetworkReply::NetworkError error = reply->error();
|
||||
const QString &errorString = reply->errorString();
|
||||
//! [networkreply-error-handling-1]
|
||||
reply.reset();
|
||||
//! [networkreply-error-handling-2]
|
||||
if (error != QNetworkReply::NoError) {
|
||||
QFile::remove(fi.absoluteFilePath());
|
||||
// For "request aborted" we handle the label and button in cancelDownload()
|
||||
if (!httpRequestAborted) {
|
||||
statusLabel->setText(tr("Download failed:\n%1.").arg(errorString));
|
||||
downloadButton->setEnabled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//! [networkreply-error-handling-2]
|
||||
|
||||
statusLabel->setText(tr("Downloaded %1 bytes to %2\nin\n%3")
|
||||
.arg(fi.size())
|
||||
.arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath())));
|
||||
if (launchCheckBox->isChecked())
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath()));
|
||||
downloadButton->setEnabled(true);
|
||||
}
|
||||
|
||||
//! [networkreply-readyread-2]
|
||||
void HttpWindow::httpReadyRead()
|
||||
{
|
||||
// This slot gets called every time the QNetworkReply has new data.
|
||||
// We read all of its new data and write it into the file.
|
||||
// That way we use less RAM than when reading it at the finished()
|
||||
// signal of the QNetworkReply
|
||||
if (file)
|
||||
file->write(reply->readAll());
|
||||
}
|
||||
//! [networkreply-readyread-2]
|
||||
|
||||
void HttpWindow::enableDownloadButton()
|
||||
{
|
||||
downloadButton->setEnabled(!urlLineEdit->text().isEmpty());
|
||||
}
|
||||
|
||||
//! [qnam-auth-required-2]
|
||||
void HttpWindow::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator)
|
||||
{
|
||||
QDialog authenticationDialog;
|
||||
Ui::Dialog ui;
|
||||
ui.setupUi(&authenticationDialog);
|
||||
authenticationDialog.adjustSize();
|
||||
ui.siteDescription->setText(tr("%1 at %2").arg(authenticator->realm(), url.host()));
|
||||
|
||||
// Did the URL have information? Fill the UI.
|
||||
// This is only relevant if the URL-supplied credentials were wrong
|
||||
ui.userEdit->setText(url.userName());
|
||||
ui.passwordEdit->setText(url.password());
|
||||
|
||||
if (authenticationDialog.exec() == QDialog::Accepted) {
|
||||
authenticator->setUser(ui.userEdit->text());
|
||||
authenticator->setPassword(ui.passwordEdit->text());
|
||||
}
|
||||
}
|
||||
//! [qnam-auth-required-2]
|
||||
|
||||
#if QT_CONFIG(ssl)
|
||||
//! [sslerrors-2]
|
||||
void HttpWindow::sslErrors(const QList<QSslError> &errors)
|
||||
{
|
||||
QString errorString;
|
||||
for (const QSslError &error : errors) {
|
||||
if (!errorString.isEmpty())
|
||||
errorString += '\n';
|
||||
errorString += error.errorString();
|
||||
}
|
||||
|
||||
if (QMessageBox::warning(this, tr("TLS Errors"),
|
||||
tr("One or more TLS errors has occurred:\n%1").arg(errorString),
|
||||
QMessageBox::Ignore | QMessageBox::Abort)
|
||||
== QMessageBox::Ignore) {
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
}
|
||||
//! [sslerrors-2]
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(networkproxy)
|
||||
void HttpWindow::slotProxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)
|
||||
{
|
||||
QDialog authenticationDialog;
|
||||
Ui::Dialog ui;
|
||||
ui.setupUi(&authenticationDialog);
|
||||
authenticationDialog.adjustSize();
|
||||
ui.siteDescription->setText(tr("A network proxy at %1 is requesting credentials for realm: %2")
|
||||
.arg(proxy.hostName(), authenticator->realm()));
|
||||
|
||||
// If the user passed credentials in the URL to http_proxy or similar they may be available to
|
||||
// us. Otherwise this will just leave the fields empty
|
||||
ui.userEdit->setText(proxy.user());
|
||||
ui.passwordEdit->setText(proxy.password());
|
||||
|
||||
if (authenticationDialog.exec() == QDialog::Accepted) {
|
||||
authenticator->setUser(ui.userEdit->text());
|
||||
authenticator->setPassword(ui.passwordEdit->text());
|
||||
}
|
||||
}
|
||||
#endif
|
79
examples/network/http/httpwindow.h
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef HTTPWINDOW_H
|
||||
#define HTTPWINDOW_H
|
||||
|
||||
#include <QProgressDialog>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QFile;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSslError;
|
||||
class QAuthenticator;
|
||||
class QNetworkReply;
|
||||
class QCheckBox;
|
||||
#if QT_CONFIG(networkproxy)
|
||||
class QNetworkProxy;
|
||||
#endif
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class ProgressDialog : public QProgressDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProgressDialog(const QUrl &url, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void networkReplyProgress(qint64 bytesRead, qint64 totalBytes);
|
||||
};
|
||||
|
||||
class HttpWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpWindow(QWidget *parent = nullptr);
|
||||
~HttpWindow();
|
||||
|
||||
void startRequest(const QUrl &requestedUrl);
|
||||
|
||||
private slots:
|
||||
void downloadFile();
|
||||
void cancelDownload();
|
||||
void httpFinished();
|
||||
void httpReadyRead();
|
||||
void enableDownloadButton();
|
||||
void slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator);
|
||||
#if QT_CONFIG(ssl)
|
||||
void sslErrors(const QList<QSslError> &errors);
|
||||
#endif
|
||||
#if QT_CONFIG(networkproxy)
|
||||
void slotProxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::unique_ptr<QFile> openFileForWrite(const QString &fileName);
|
||||
|
||||
QLabel *statusLabel;
|
||||
QLineEdit *urlLineEdit;
|
||||
QPushButton *downloadButton;
|
||||
QCheckBox *launchCheckBox;
|
||||
QLineEdit *defaultFileLineEdit;
|
||||
QLineEdit *downloadDirectoryLineEdit;
|
||||
|
||||
QUrl url;
|
||||
QNetworkAccessManager qnam;
|
||||
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply;
|
||||
std::unique_ptr<QFile> file;
|
||||
bool httpRequestAborted = false;
|
||||
};
|
||||
|
||||
#endif
|
20
examples/network/http/main.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QScreen>
|
||||
|
||||
#include "httpwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
HttpWindow httpWin;
|
||||
const QRect availableSize = httpWin.screen()->availableGeometry();
|
||||
httpWin.resize(availableSize.width() / 5, availableSize.height() / 5);
|
||||
httpWin.move((availableSize.width() - httpWin.width()) / 2, (availableSize.height() - httpWin.height()) / 2);
|
||||
httpWin.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
38
examples/network/multicastreceiver/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(multicastreceiver LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/multicastreceiver")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(multicastreceiver
|
||||
main.cpp
|
||||
receiver.cpp receiver.h
|
||||
)
|
||||
|
||||
set_target_properties(multicastreceiver PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(multicastreceiver PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS multicastreceiver
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
14
examples/network/multicastreceiver/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "receiver.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Receiver receiver;
|
||||
receiver.show();
|
||||
return app.exec();
|
||||
}
|
11
examples/network/multicastreceiver/multicastreceiver.pro
Normal file
@ -0,0 +1,11 @@
|
||||
QT += network widgets
|
||||
requires(qtConfig(udpsocket))
|
||||
|
||||
HEADERS = receiver.h
|
||||
SOURCES = receiver.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/multicastreceiver
|
||||
INSTALLS += target
|
||||
|
64
examples/network/multicastreceiver/receiver.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "receiver.h"
|
||||
|
||||
Receiver::Receiver(QWidget *parent)
|
||||
: QDialog(parent),
|
||||
groupAddress4(QStringLiteral("239.255.43.21")),
|
||||
groupAddress6(QStringLiteral("ff12::2115"))
|
||||
{
|
||||
statusLabel = new QLabel(tr("Listening for multicast messages on both IPv4 and IPv6"));
|
||||
auto quitButton = new QPushButton(tr("&Quit"));
|
||||
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(quitButton);
|
||||
buttonLayout->addStretch(1);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Multicast Receiver"));
|
||||
|
||||
udpSocket4.bind(QHostAddress::AnyIPv4, 45454, QUdpSocket::ShareAddress);
|
||||
udpSocket4.joinMulticastGroup(groupAddress4);
|
||||
|
||||
if (!udpSocket6.bind(QHostAddress::AnyIPv6, 45454, QUdpSocket::ShareAddress) ||
|
||||
!udpSocket6.joinMulticastGroup(groupAddress6))
|
||||
statusLabel->setText(tr("Listening for multicast messages on IPv4 only"));
|
||||
|
||||
connect(&udpSocket4, &QUdpSocket::readyRead,
|
||||
this, &Receiver::processPendingDatagrams);
|
||||
connect(&udpSocket6, &QUdpSocket::readyRead,
|
||||
this, &Receiver::processPendingDatagrams);
|
||||
connect(quitButton, &QPushButton::clicked,
|
||||
qApp, &QCoreApplication::quit);
|
||||
}
|
||||
|
||||
void Receiver::processPendingDatagrams()
|
||||
{
|
||||
QByteArray datagram;
|
||||
|
||||
// using QUdpSocket::readDatagram (API since Qt 4)
|
||||
while (udpSocket4.hasPendingDatagrams()) {
|
||||
datagram.resize(qsizetype(udpSocket4.pendingDatagramSize()));
|
||||
udpSocket4.readDatagram(datagram.data(), datagram.size());
|
||||
statusLabel->setText(tr("Received IPv4 datagram: \"%1\"")
|
||||
.arg(datagram.constData()));
|
||||
}
|
||||
|
||||
// using QUdpSocket::receiveDatagram (API since Qt 5.8)
|
||||
while (udpSocket6.hasPendingDatagrams()) {
|
||||
QNetworkDatagram dgram = udpSocket6.receiveDatagram();
|
||||
statusLabel->setText(statusLabel->text() +
|
||||
tr("\nReceived IPv6 datagram from [%2]:%3: \"%1\"")
|
||||
.arg(dgram.data().constData(), dgram.senderAddress().toString())
|
||||
.arg(dgram.senderPort()));
|
||||
}
|
||||
}
|
33
examples/network/multicastreceiver/receiver.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef RECEIVER_H
|
||||
#define RECEIVER_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHostAddress>
|
||||
#include <QUdpSocket>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Receiver : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Receiver(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void processPendingDatagrams();
|
||||
|
||||
private:
|
||||
QLabel *statusLabel = nullptr;
|
||||
QUdpSocket udpSocket4;
|
||||
QUdpSocket udpSocket6;
|
||||
QHostAddress groupAddress4;
|
||||
QHostAddress groupAddress6;
|
||||
};
|
||||
|
||||
#endif
|
38
examples/network/multicastsender/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(multicastsender LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/multicastsender")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(multicastsender
|
||||
main.cpp
|
||||
sender.cpp sender.h
|
||||
)
|
||||
|
||||
set_target_properties(multicastsender PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(multicastsender PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS multicastsender
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
14
examples/network/multicastsender/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "sender.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Sender sender;
|
||||
sender.show();
|
||||
return app.exec();
|
||||
}
|
10
examples/network/multicastsender/multicastsender.pro
Normal file
@ -0,0 +1,10 @@
|
||||
HEADERS = sender.h
|
||||
SOURCES = sender.cpp \
|
||||
main.cpp
|
||||
QT += network widgets
|
||||
requires(qtConfig(udpsocket))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/multicastsender
|
||||
INSTALLS += target
|
||||
|
72
examples/network/multicastsender/sender.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "sender.h"
|
||||
|
||||
Sender::Sender(QWidget *parent)
|
||||
: QDialog(parent),
|
||||
groupAddress4(QStringLiteral("239.255.43.21")),
|
||||
groupAddress6(QStringLiteral("ff12::2115"))
|
||||
{
|
||||
// force binding to their respective families
|
||||
udpSocket4.bind(QHostAddress(QHostAddress::AnyIPv4), 0);
|
||||
udpSocket6.bind(QHostAddress(QHostAddress::AnyIPv6), udpSocket4.localPort());
|
||||
|
||||
QString msg = tr("Ready to multicast datagrams to groups %1 and [%2] on port 45454").arg(groupAddress4.toString());
|
||||
if (udpSocket6.state() != QAbstractSocket::BoundState)
|
||||
msg = tr("IPv6 failed. Ready to multicast datagrams to group %1 on port 45454").arg(groupAddress4.toString());
|
||||
else
|
||||
msg = msg.arg(groupAddress6.toString());
|
||||
statusLabel = new QLabel(msg);
|
||||
|
||||
auto ttlLabel = new QLabel(tr("TTL for IPv4 multicast datagrams:"));
|
||||
auto ttlSpinBox = new QSpinBox;
|
||||
ttlSpinBox->setRange(0, 255);
|
||||
|
||||
auto ttlLayout = new QHBoxLayout;
|
||||
ttlLayout->addWidget(ttlLabel);
|
||||
ttlLayout->addWidget(ttlSpinBox);
|
||||
|
||||
startButton = new QPushButton(tr("&Start"));
|
||||
auto quitButton = new QPushButton(tr("&Quit"));
|
||||
|
||||
auto buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(startButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
connect(ttlSpinBox, &QSpinBox::valueChanged, this, &Sender::ttlChanged);
|
||||
connect(startButton, &QPushButton::clicked, this, &Sender::startSending);
|
||||
connect(quitButton, &QPushButton::clicked, this, &Sender::close);
|
||||
connect(&timer, &QTimer::timeout, this, &Sender::sendDatagram);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addLayout(ttlLayout);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Multicast Sender"));
|
||||
ttlSpinBox->setValue(1);
|
||||
}
|
||||
|
||||
void Sender::ttlChanged(int newTtl)
|
||||
{
|
||||
// we only set the TTL on the IPv4 socket, as that changes the multicast scope
|
||||
udpSocket4.setSocketOption(QAbstractSocket::MulticastTtlOption, newTtl);
|
||||
}
|
||||
|
||||
void Sender::startSending()
|
||||
{
|
||||
startButton->setEnabled(false);
|
||||
timer.start(1000);
|
||||
}
|
||||
|
||||
void Sender::sendDatagram()
|
||||
{
|
||||
statusLabel->setText(tr("Now sending datagram %1").arg(messageNo));
|
||||
QByteArray datagram = "Multicast message " + QByteArray::number(messageNo);
|
||||
udpSocket4.writeDatagram(datagram, groupAddress4, 45454);
|
||||
if (udpSocket6.state() == QAbstractSocket::BoundState)
|
||||
udpSocket6.writeDatagram(datagram, groupAddress6, 45454);
|
||||
++messageNo;
|
||||
}
|
34
examples/network/multicastsender/sender.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SENDER_H
|
||||
#define SENDER_H
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
#include <QtCore>
|
||||
|
||||
class Sender : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Sender(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void ttlChanged(int newTtl);
|
||||
void startSending();
|
||||
void sendDatagram();
|
||||
|
||||
private:
|
||||
QLabel *statusLabel = nullptr;
|
||||
QPushButton *startButton = nullptr;
|
||||
QUdpSocket udpSocket4;
|
||||
QUdpSocket udpSocket6;
|
||||
QTimer timer;
|
||||
QHostAddress groupAddress4;
|
||||
QHostAddress groupAddress6;
|
||||
int messageNo = 1;
|
||||
};
|
||||
|
||||
#endif
|
42
examples/network/multistreamclient/CMakeLists.txt
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(multistreamclient LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/multistreamclient")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(multistreamclient
|
||||
chatconsumer.cpp chatconsumer.h
|
||||
client.cpp client.h
|
||||
consumer.h
|
||||
main.cpp
|
||||
movieconsumer.cpp movieconsumer.h
|
||||
timeconsumer.cpp timeconsumer.h
|
||||
)
|
||||
|
||||
set_target_properties(multistreamclient PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(multistreamclient PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS multistreamclient
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
46
examples/network/multistreamclient/chatconsumer.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "chatconsumer.h"
|
||||
#include <QWidget>
|
||||
#include <QTextEdit>
|
||||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QString>
|
||||
|
||||
ChatConsumer::ChatConsumer(QObject *parent)
|
||||
: Consumer(parent)
|
||||
{
|
||||
frameWidget = new QWidget;
|
||||
frameWidget->setFocusPolicy(Qt::TabFocus);
|
||||
|
||||
textEdit = new QTextEdit;
|
||||
textEdit->setFocusPolicy(Qt::NoFocus);
|
||||
textEdit->setReadOnly(true);
|
||||
|
||||
lineEdit = new QLineEdit;
|
||||
frameWidget->setFocusProxy(lineEdit);
|
||||
|
||||
connect(lineEdit, &QLineEdit::returnPressed, this, &ChatConsumer::returnPressed);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(frameWidget);
|
||||
layout->setContentsMargins( 0, 0, 0, 0);
|
||||
layout->addWidget(textEdit);
|
||||
layout->addWidget(lineEdit);
|
||||
}
|
||||
|
||||
QWidget *ChatConsumer::widget()
|
||||
{
|
||||
return frameWidget;
|
||||
}
|
||||
|
||||
void ChatConsumer::readDatagram(const QByteArray &ba)
|
||||
{
|
||||
textEdit->append(QString::fromUtf8(ba));
|
||||
}
|
||||
|
||||
void ChatConsumer::returnPressed()
|
||||
{
|
||||
emit writeDatagram(lineEdit->text().toUtf8());
|
||||
lineEdit->clear();
|
||||
}
|
32
examples/network/multistreamclient/chatconsumer.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CHATCONSUMER_H
|
||||
#define CHATCONSUMER_H
|
||||
|
||||
#include "consumer.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextEdit;
|
||||
class QLineEdit;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class ChatConsumer : public Consumer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ChatConsumer(QObject *parent = nullptr);
|
||||
|
||||
QWidget *widget() override;
|
||||
void readDatagram(const QByteArray &ba) override;
|
||||
|
||||
private slots:
|
||||
void returnPressed();
|
||||
|
||||
private:
|
||||
QWidget *frameWidget;
|
||||
QTextEdit *textEdit;
|
||||
QLineEdit *lineEdit;
|
||||
};
|
||||
|
||||
#endif
|
171
examples/network/multistreamclient/client.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "client.h"
|
||||
#include "movieconsumer.h"
|
||||
#include "timeconsumer.h"
|
||||
#include "chatconsumer.h"
|
||||
|
||||
#include "../shared/sctpchannels.h"
|
||||
|
||||
Client::Client(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, consumers(SctpChannels::NumberOfChannels)
|
||||
{
|
||||
setWindowTitle(tr("Multi-stream Client"));
|
||||
|
||||
sctpSocket = new QSctpSocket(this);
|
||||
|
||||
QLabel *hostLabel = new QLabel(tr("&Server name:"));
|
||||
QLabel *portLabel = new QLabel(tr("S&erver port:"));
|
||||
|
||||
hostCombo = new QComboBox;
|
||||
hostCombo->setEditable(true);
|
||||
// find out name of this machine
|
||||
QString name = QHostInfo::localHostName();
|
||||
if (!name.isEmpty()) {
|
||||
hostCombo->addItem(name);
|
||||
QString domain = QHostInfo::localDomainName();
|
||||
if (!domain.isEmpty())
|
||||
hostCombo->addItem(name + QChar('.') + domain);
|
||||
}
|
||||
if (name != QString("localhost"))
|
||||
hostCombo->addItem(QString("localhost"));
|
||||
// find out IP addresses of this machine
|
||||
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
|
||||
// add non-localhost addresses
|
||||
for (int i = 0; i < ipAddressesList.size(); ++i) {
|
||||
if (!ipAddressesList.at(i).isLoopback())
|
||||
hostCombo->addItem(ipAddressesList.at(i).toString());
|
||||
}
|
||||
// add localhost addresses
|
||||
for (int i = 0; i < ipAddressesList.size(); ++i) {
|
||||
if (ipAddressesList.at(i).isLoopback())
|
||||
hostCombo->addItem(ipAddressesList.at(i).toString());
|
||||
}
|
||||
|
||||
portLineEdit = new QLineEdit;
|
||||
portLineEdit->setValidator(new QIntValidator(1, 65535, this));
|
||||
|
||||
hostLabel->setBuddy(hostCombo);
|
||||
portLabel->setBuddy(portLineEdit);
|
||||
|
||||
connectButton = new QPushButton(tr("Connect"));
|
||||
connectButton->setDefault(true);
|
||||
connectButton->setEnabled(false);
|
||||
|
||||
QPushButton *quitButton = new QPushButton(tr("Quit"));
|
||||
quitButton->setAutoDefault(false);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(connectButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::AcceptRole);
|
||||
|
||||
QLabel *movieLabel = new QLabel(tr("Movie stream:"));
|
||||
consumers[SctpChannels::Movie] = new MovieConsumer(this);
|
||||
QLabel *timeLabel = new QLabel(tr("Time stream:"));
|
||||
consumers[SctpChannels::Time] = new TimeConsumer(this);
|
||||
QLabel *chatLabel = new QLabel(tr("&Chat:"));
|
||||
consumers[SctpChannels::Chat] = new ChatConsumer(this);
|
||||
chatLabel->setBuddy(consumers[SctpChannels::Chat]->widget());
|
||||
|
||||
connect(hostCombo, &QComboBox::editTextChanged, this, &Client::enableConnectButton);
|
||||
connect(portLineEdit, &QLineEdit::textChanged, this, &Client::enableConnectButton);
|
||||
connect(connectButton, &QPushButton::clicked, this, &Client::requestConnect);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &Client::accept);
|
||||
connect(sctpSocket, &QSctpSocket::connected, this, &Client::connected);
|
||||
connect(sctpSocket, &QSctpSocket::disconnected, this, &Client::disconnected);
|
||||
connect(sctpSocket, &QSctpSocket::channelReadyRead, this, &Client::readDatagram);
|
||||
connect(sctpSocket, &QSctpSocket::errorOccurred, this, &Client::displayError);
|
||||
connect(consumers[SctpChannels::Time], &Consumer::writeDatagram, this, &Client::writeDatagram);
|
||||
connect(consumers[SctpChannels::Chat], &Consumer::writeDatagram, this, &Client::writeDatagram);
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(hostLabel, 0, 0);
|
||||
mainLayout->addWidget(hostCombo, 0, 1);
|
||||
mainLayout->addWidget(portLabel, 1, 0);
|
||||
mainLayout->addWidget(portLineEdit, 1, 1);
|
||||
mainLayout->addWidget(buttonBox, 2, 0, 1, 2);
|
||||
mainLayout->addWidget(movieLabel, 3, 0);
|
||||
mainLayout->addWidget(timeLabel, 3, 1);
|
||||
mainLayout->addWidget(consumers[SctpChannels::Movie]->widget(), 4, 0);
|
||||
mainLayout->addWidget(consumers[SctpChannels::Time]->widget(), 4, 1);
|
||||
mainLayout->addWidget(chatLabel, 5, 0);
|
||||
mainLayout->addWidget(consumers[SctpChannels::Chat]->widget(), 6, 0, 1, 2);
|
||||
setLayout(mainLayout);
|
||||
|
||||
portLineEdit->setFocus();
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
delete sctpSocket;
|
||||
}
|
||||
|
||||
void Client::connected()
|
||||
{
|
||||
consumers[SctpChannels::Chat]->widget()->setFocus();
|
||||
}
|
||||
|
||||
void Client::disconnected()
|
||||
{
|
||||
for (Consumer *consumer : consumers)
|
||||
consumer->serverDisconnected();
|
||||
|
||||
sctpSocket->close();
|
||||
}
|
||||
|
||||
void Client::requestConnect()
|
||||
{
|
||||
connectButton->setEnabled(false);
|
||||
sctpSocket->abort();
|
||||
sctpSocket->connectToHost(hostCombo->currentText(),
|
||||
portLineEdit->text().toInt());
|
||||
}
|
||||
|
||||
void Client::readDatagram(int channel)
|
||||
{
|
||||
sctpSocket->setCurrentReadChannel(channel);
|
||||
consumers[channel]->readDatagram(sctpSocket->readDatagram().data());
|
||||
}
|
||||
|
||||
void Client::displayError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
switch (socketError) {
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
QMessageBox::information(this, tr("Multi-stream Client"),
|
||||
tr("The host was not found. Please check the "
|
||||
"host name and port settings."));
|
||||
break;
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
QMessageBox::information(this, tr("Multi-stream Client"),
|
||||
tr("The connection was refused by the peer. "
|
||||
"Make sure the multi-stream server is running, "
|
||||
"and check that the host name and port "
|
||||
"settings are correct."));
|
||||
break;
|
||||
default:
|
||||
QMessageBox::information(this, tr("Multi-stream Client"),
|
||||
tr("The following error occurred: %1.")
|
||||
.arg(sctpSocket->errorString()));
|
||||
}
|
||||
|
||||
enableConnectButton();
|
||||
}
|
||||
|
||||
void Client::enableConnectButton()
|
||||
{
|
||||
connectButton->setEnabled(!hostCombo->currentText().isEmpty() &&
|
||||
!portLineEdit->text().isEmpty());
|
||||
}
|
||||
|
||||
void Client::writeDatagram(const QByteArray &ba)
|
||||
{
|
||||
if (sctpSocket->isValid() && sctpSocket->state() == QAbstractSocket::ConnectedState) {
|
||||
sctpSocket->setCurrentWriteChannel(consumers.indexOf(static_cast<Consumer *>(sender())));
|
||||
sctpSocket->writeDatagram(ba);
|
||||
}
|
||||
}
|
45
examples/network/multistreamclient/client.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QSctpSocket>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QByteArray;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Consumer;
|
||||
|
||||
class Client : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Client(QWidget *parent = nullptr);
|
||||
virtual ~Client();
|
||||
|
||||
private slots:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void requestConnect();
|
||||
void readDatagram(int channel);
|
||||
void displayError(QAbstractSocket::SocketError socketError);
|
||||
void enableConnectButton();
|
||||
void writeDatagram(const QByteArray &ba);
|
||||
|
||||
private:
|
||||
QList<Consumer *> consumers;
|
||||
QSctpSocket *sctpSocket;
|
||||
|
||||
QComboBox *hostCombo;
|
||||
QLineEdit *portLineEdit;
|
||||
QPushButton *connectButton;
|
||||
};
|
||||
|
||||
#endif
|
28
examples/network/multistreamclient/consumer.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CONSUMER_H
|
||||
#define CONSUMER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QWidget;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Consumer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit inline Consumer(QObject *parent = nullptr) : QObject(parent) { }
|
||||
|
||||
virtual QWidget *widget() = 0;
|
||||
virtual void readDatagram(const QByteArray &ba) = 0;
|
||||
virtual void serverDisconnected() { }
|
||||
|
||||
signals:
|
||||
void writeDatagram(const QByteArray &ba);
|
||||
};
|
||||
|
||||
#endif
|
13
examples/network/multistreamclient/main.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
#include "client.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Client client;
|
||||
|
||||
return (client.exec() == QDialog::Accepted) ? 0 : -1;
|
||||
}
|
36
examples/network/multistreamclient/movieconsumer.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "movieconsumer.h"
|
||||
#include <QLabel>
|
||||
#include <QDataStream>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
|
||||
MovieConsumer::MovieConsumer(QObject *parent)
|
||||
: Consumer(parent)
|
||||
{
|
||||
label = new QLabel;
|
||||
label->setFrameStyle(QFrame::Box | QFrame::Raised);
|
||||
label->setFixedSize(128 + label->frameWidth() * 2,
|
||||
64 + label->frameWidth() * 2);
|
||||
}
|
||||
|
||||
QWidget *MovieConsumer::widget()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
void MovieConsumer::readDatagram(const QByteArray &ba)
|
||||
{
|
||||
QDataStream ds(ba);
|
||||
QImage image;
|
||||
|
||||
ds >> image;
|
||||
label->setPixmap(QPixmap::fromImage(image));
|
||||
}
|
||||
|
||||
void MovieConsumer::serverDisconnected()
|
||||
{
|
||||
label->setPixmap(QPixmap());
|
||||
}
|
27
examples/network/multistreamclient/movieconsumer.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef MOVIECONSUMER_H
|
||||
#define MOVIECONSUMER_H
|
||||
|
||||
#include "consumer.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MovieConsumer : public Consumer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MovieConsumer(QObject *parent = nullptr);
|
||||
|
||||
QWidget *widget() override;
|
||||
void readDatagram(const QByteArray &ba) override;
|
||||
void serverDisconnected() override;
|
||||
|
||||
private:
|
||||
QLabel *label;
|
||||
};
|
||||
|
||||
#endif
|
16
examples/network/multistreamclient/multistreamclient.pro
Normal file
@ -0,0 +1,16 @@
|
||||
QT += network widgets
|
||||
|
||||
HEADERS = client.h \
|
||||
consumer.h \
|
||||
movieconsumer.h \
|
||||
timeconsumer.h \
|
||||
chatconsumer.h
|
||||
SOURCES = client.cpp \
|
||||
movieconsumer.cpp \
|
||||
timeconsumer.cpp \
|
||||
chatconsumer.cpp \
|
||||
main.cpp
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/multistreamclient
|
||||
INSTALLS += target
|
47
examples/network/multistreamclient/timeconsumer.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "timeconsumer.h"
|
||||
#include <QLCDNumber>
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <QTimer>
|
||||
|
||||
TimeConsumer::TimeConsumer(QObject *parent)
|
||||
: Consumer(parent)
|
||||
{
|
||||
lcdNumber = new QLCDNumber(8);
|
||||
|
||||
QTimer *timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &TimeConsumer::timerTick);
|
||||
timer->start(100);
|
||||
|
||||
serverDisconnected();
|
||||
}
|
||||
|
||||
QWidget *TimeConsumer::widget()
|
||||
{
|
||||
return lcdNumber;
|
||||
}
|
||||
|
||||
void TimeConsumer::readDatagram(const QByteArray &ba)
|
||||
{
|
||||
QDataStream ds(ba);
|
||||
|
||||
ds >> lastTime;
|
||||
lcdNumber->display(lastTime.toString("hh:mm:ss"));
|
||||
}
|
||||
|
||||
void TimeConsumer::timerTick()
|
||||
{
|
||||
QByteArray buf;
|
||||
QDataStream ds(&buf, QIODeviceBase::WriteOnly);
|
||||
|
||||
ds << lastTime;
|
||||
emit writeDatagram(buf);
|
||||
}
|
||||
|
||||
void TimeConsumer::serverDisconnected()
|
||||
{
|
||||
lcdNumber->display(QLatin1String("--:--:--"));
|
||||
}
|
32
examples/network/multistreamclient/timeconsumer.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef TIMECONSUMER_H
|
||||
#define TIMECONSUMER_H
|
||||
|
||||
#include "consumer.h"
|
||||
#include <QTime>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLCDNumber;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class TimeConsumer : public Consumer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TimeConsumer(QObject *parent = nullptr);
|
||||
|
||||
QWidget *widget() override;
|
||||
void readDatagram(const QByteArray &ba) override;
|
||||
void serverDisconnected() override;
|
||||
|
||||
private slots:
|
||||
void timerTick();
|
||||
|
||||
private:
|
||||
QTime lastTime;
|
||||
QLCDNumber *lcdNumber;
|
||||
};
|
||||
|
||||
#endif
|
42
examples/network/multistreamserver/CMakeLists.txt
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(multistreamserver LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/multistreamserver")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(multistreamserver
|
||||
chatprovider.cpp chatprovider.h
|
||||
main.cpp
|
||||
movieprovider.cpp movieprovider.h
|
||||
provider.h
|
||||
server.cpp server.h
|
||||
timeprovider.cpp timeprovider.h
|
||||
)
|
||||
|
||||
set_target_properties(multistreamserver PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(multistreamserver PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS multistreamserver
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
BIN
examples/network/multistreamserver/animation.gif
Normal file
After Width: | Height: | Size: 42 KiB |
30
examples/network/multistreamserver/chatprovider.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "chatprovider.h"
|
||||
#include <QString>
|
||||
#include <QSctpSocket>
|
||||
#include <QHostAddress>
|
||||
|
||||
ChatProvider::ChatProvider(QObject *parent)
|
||||
: Provider(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ChatProvider::readDatagram(QSctpSocket &from, const QByteArray &ba)
|
||||
{
|
||||
emit writeDatagram(0, QString(QLatin1String("<%1:%2> %3"))
|
||||
.arg(from.peerAddress().toString())
|
||||
.arg(QString::number(from.peerPort()))
|
||||
.arg(QString::fromUtf8(ba)).toUtf8());
|
||||
}
|
||||
|
||||
void ChatProvider::newConnection(QSctpSocket &client)
|
||||
{
|
||||
readDatagram(client, QString(tr("has joined")).toUtf8());
|
||||
}
|
||||
|
||||
void ChatProvider::clientDisconnected(QSctpSocket &client)
|
||||
{
|
||||
readDatagram(client, QString(tr("has left")).toUtf8());
|
||||
}
|
20
examples/network/multistreamserver/chatprovider.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CHATPROVIDER_H
|
||||
#define CHATPROVIDER_H
|
||||
|
||||
#include "provider.h"
|
||||
|
||||
class ChatProvider : public Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ChatProvider(QObject *parent = nullptr);
|
||||
|
||||
void readDatagram(QSctpSocket &from, const QByteArray &ba) override;
|
||||
void newConnection(QSctpSocket &client) override;
|
||||
void clientDisconnected(QSctpSocket &client) override;
|
||||
};
|
||||
|
||||
#endif
|
14
examples/network/multistreamserver/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Server server;
|
||||
|
||||
return (server.exec() == QDialog::Accepted) ? 0 : -1;
|
||||
}
|
26
examples/network/multistreamserver/movieprovider.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "movieprovider.h"
|
||||
#include <QMovie>
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
|
||||
MovieProvider::MovieProvider(QObject *parent)
|
||||
: Provider(parent)
|
||||
{
|
||||
movie = new QMovie(this);
|
||||
movie->setCacheMode(QMovie::CacheAll);
|
||||
movie->setFileName(QLatin1String("animation.gif"));
|
||||
connect(movie, &QMovie::frameChanged, this, &MovieProvider::frameChanged);
|
||||
movie->start();
|
||||
}
|
||||
|
||||
void MovieProvider::frameChanged()
|
||||
{
|
||||
QByteArray buf;
|
||||
QDataStream ds(&buf, QIODevice::WriteOnly);
|
||||
|
||||
ds << movie->currentImage();
|
||||
emit writeDatagram(0, buf);
|
||||
}
|
26
examples/network/multistreamserver/movieprovider.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef MOVIEPROVIDER_H
|
||||
#define MOVIEPROVIDER_H
|
||||
|
||||
#include "provider.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QMovie;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MovieProvider : public Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MovieProvider(QObject *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void frameChanged();
|
||||
|
||||
private:
|
||||
QMovie *movie;
|
||||
};
|
||||
|
||||
#endif
|
18
examples/network/multistreamserver/multistreamserver.pro
Normal file
@ -0,0 +1,18 @@
|
||||
QT += network widgets
|
||||
|
||||
HEADERS = server.h \
|
||||
provider.h \
|
||||
movieprovider.h \
|
||||
timeprovider.h \
|
||||
chatprovider.h
|
||||
SOURCES = server.cpp \
|
||||
movieprovider.cpp \
|
||||
timeprovider.cpp \
|
||||
chatprovider.cpp \
|
||||
main.cpp
|
||||
|
||||
EXAMPLE_FILES = animation.gif
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/network/multistreamserver
|
||||
INSTALLS += target
|
29
examples/network/multistreamserver/provider.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef PROVIDER_H
|
||||
#define PROVIDER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QSctpSocket;
|
||||
class QByteArray;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Provider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit inline Provider(QObject *parent = nullptr) : QObject(parent) { }
|
||||
|
||||
virtual void readDatagram(QSctpSocket &, const QByteArray &) { }
|
||||
virtual void newConnection(QSctpSocket &) { }
|
||||
virtual void clientDisconnected(QSctpSocket &) { }
|
||||
|
||||
signals:
|
||||
void writeDatagram(QSctpSocket *to, const QByteArray &ba);
|
||||
};
|
||||
|
||||
#endif
|
124
examples/network/multistreamserver/server.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2015 Alex Trotsenko <alex1973tr@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
#include <QtAlgorithms>
|
||||
|
||||
#include "server.h"
|
||||
#include "movieprovider.h"
|
||||
#include "timeprovider.h"
|
||||
#include "chatprovider.h"
|
||||
|
||||
#include "../shared/sctpchannels.h"
|
||||
|
||||
Server::Server(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, providers(SctpChannels::NumberOfChannels)
|
||||
{
|
||||
setWindowTitle(tr("Multi-stream Server"));
|
||||
|
||||
sctpServer = new QSctpServer(this);
|
||||
sctpServer->setMaximumChannelCount(NumberOfChannels);
|
||||
|
||||
statusLabel = new QLabel;
|
||||
QPushButton *quitButton = new QPushButton(tr("Quit"));
|
||||
|
||||
providers[SctpChannels::Movie] = new MovieProvider(this);
|
||||
providers[SctpChannels::Time] = new TimeProvider(this);
|
||||
providers[SctpChannels::Chat] = new ChatProvider(this);
|
||||
|
||||
connect(sctpServer, &QSctpServer::newConnection, this, &Server::newConnection);
|
||||
connect(quitButton, &QPushButton::clicked, this, &Server::accept);
|
||||
connect(providers[SctpChannels::Movie], &Provider::writeDatagram, this, &Server::writeDatagram);
|
||||
connect(providers[SctpChannels::Time], &Provider::writeDatagram, this, &Server::writeDatagram);
|
||||
connect(providers[SctpChannels::Chat], &Provider::writeDatagram, this, &Server::writeDatagram);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addWidget(quitButton);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
{
|
||||
qDeleteAll(connections.begin(), connections.end());
|
||||
}
|
||||
|
||||
int Server::exec()
|
||||
{
|
||||
if (!sctpServer->listen()) {
|
||||
QMessageBox::critical(this, windowTitle(),
|
||||
tr("Unable to start the server: %1.")
|
||||
.arg(sctpServer->errorString()));
|
||||
return QDialog::Rejected;
|
||||
}
|
||||
|
||||
QString ipAddress;
|
||||
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
|
||||
// use the first non-localhost IPv4 address
|
||||
for (int i = 0; i < ipAddressesList.size(); ++i) {
|
||||
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
|
||||
ipAddressesList.at(i).toIPv4Address()) {
|
||||
ipAddress = ipAddressesList.at(i).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we did not find one, use IPv4 localhost
|
||||
if (ipAddress.isEmpty())
|
||||
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
|
||||
statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n"
|
||||
"Run the Multi-stream Client example now.")
|
||||
.arg(ipAddress).arg(sctpServer->serverPort()));
|
||||
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
void Server::newConnection()
|
||||
{
|
||||
QSctpSocket *connection = sctpServer->nextPendingDatagramConnection();
|
||||
|
||||
connections.append(connection);
|
||||
connect(connection, &QSctpSocket::channelReadyRead, this, &Server::readDatagram);
|
||||
connect(connection, &QSctpSocket::disconnected, this, &Server::clientDisconnected);
|
||||
|
||||
for (Provider *provider : providers)
|
||||
provider->newConnection(*connection);
|
||||
}
|
||||
|
||||
void Server::clientDisconnected()
|
||||
{
|
||||
QSctpSocket *connection = static_cast<QSctpSocket *>(sender());
|
||||
|
||||
connections.removeOne(connection);
|
||||
connection->disconnect();
|
||||
|
||||
for (Provider *provider : providers)
|
||||
provider->clientDisconnected(*connection);
|
||||
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void Server::readDatagram(int channel)
|
||||
{
|
||||
QSctpSocket *connection = static_cast<QSctpSocket *>(sender());
|
||||
|
||||
connection->setCurrentReadChannel(channel);
|
||||
providers[channel]->readDatagram(*connection, connection->readDatagram().data());
|
||||
}
|
||||
|
||||
void Server::writeDatagram(QSctpSocket *to, const QByteArray &ba)
|
||||
{
|
||||
int channel = providers.indexOf(static_cast<Provider *>(sender()));
|
||||
|
||||
if (to) {
|
||||
to->setCurrentWriteChannel(channel);
|
||||
to->writeDatagram(ba);
|
||||
return;
|
||||
}
|
||||
|
||||
for (QSctpSocket *connection : connections) {
|
||||
connection->setCurrentWriteChannel(channel);
|
||||
connection->writeDatagram(ba);
|
||||
}
|
||||
}
|