Инструменты пользователя

Инструменты сайта


develop:qt:network

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
develop:qt:network [2021/09/24 06:01]
admin
develop:qt:network [2021/10/27 04:33] (текущий)
admin
Строка 8: Строка 8:
  
 ===== QTcpServer ===== ===== QTcpServer =====
------ 
 Позволяет принимать Tcp-соединения, порт можно выбрать либо авто, IP так же можно выбрать конкретный либо все IP хоста.\\ Позволяет принимать Tcp-соединения, порт можно выбрать либо авто, IP так же можно выбрать конкретный либо все IP хоста.\\
  
Строка 20: Строка 19:
  
 ===== QAbstractSocket ===== ===== QAbstractSocket =====
------ 
 {{ :develop:qt:qnet_0.png?direct&300|}} {{ :develop:qt:qnet_0.png?direct&300|}}
  
Строка 79: Строка 77:
 ----- -----
  
 +
 +
 +===== QHostAddress =====
 +-----
 +Класс содержит адреса v4/v6.\\
 +Адрес устанавливается и извлекается, можно проверить его тип, поддерживает предопределенные адреса, типа **LocalHost**, **Broadcast**, **Any**.\\
 +
 +
 +
 +
 +===== Пример клиент/сервера =====
 +
 +==== Сервер ====
 +-----
 +Сервер на базе **QTcpServer**, слушает указанный IP и порт.\\
 +При приеме входящего подключения отправляет клиенту указанную инфу и закрывает соединение.\\
 +Данные **передаются в открытом виде** \\
 +
 +При приеме соединения вызывается сигнал **NewConnection()**, его обрабатываем.\\
 +Внутри создаем объект пользовательского подключения, методом **nextPendingConnection()**, и работаем с этим сокетом подключения, отправляемые данные просто пишем в него, как в устройство (QIODevice).\\
 +Запись делаем методом **write()**, на сколько я понял разделение на пакеты происходит уже на уровне протокола, в зависимости от размера данных, и неважно сколько раз мы вызываем write().\\
 +Пакеты по 60-65 кБ.\\
 +
 +В данном примере открываем файл и заносим его содержимое в массив **QByteArray**, можно сразу целиком, можно частями, с указанным размером (чтение автоматом продолжается с нужного места)
 +Далее передаем получившийся **QByteArray** в метод **write()**, тем самым отправляя его клиенту.\\
  
  
 <details> <details>
-<summary>:!: Пример: </summary>+<summary>:!: Пример: Простой tcp-сервер, передача одного файла</summary> 
 +Основан на примере "Fortune Server Example", переделан под файл 
 +sserv.h
 <code cpp-qt> <code cpp-qt>
 +#ifndef SSERV_H
 +#define SSERV_H
  
 +#include <QDialog>
 +#include <QLabel>
 +#include <QVector>
 +#include <QTcpServer>
 +#include <QTcpSocket>
 +#include <QMessageBox>
 +#include <QNetworkInterface>
 +#include <QRandomGenerator>
 +#include <QByteArray>
 +#include <QDataStream>
 +#include <QBoxLayout>
 +#include <QFile>
 +
 +class sserv : public QDialog
 +{
 +    Q_OBJECT
 +
 +    QLabel *labStatusText;
 +    QTcpServer *tcpServer;
 +    //QVector<QString> vecFurtunes;
 +
 +private slots:
 +    void slotSendFortune();
 +
 +public:
 +    sserv(QWidget *parent = nullptr);
 +
 +};
 +#endif // SSERV_H
 +</code>
 +
 +sserv.cpp
 +<code cpp-qt>
 +#include "sserv.h"
 +
 +sserv::sserv(QWidget *parent): QDialog(parent)
 +{
 +    tcpServer= new QTcpServer(this);
 +    if(!tcpServer->listen(QHostAddress::LocalHost, 2233))
 +    {
 +        QMessageBox::critical(this, "Caption", tr("Unable to start: %1").arg(tcpServer->errorString()));
 +        this->close();
 +        return;
 +    }
 +
 +    QString vIpAddr;
 +    QList<QHostAddress> vListIp= QNetworkInterface::allAddresses();
 +    for(int i= 0; i < vListIp.size(); ++i)
 +    {
 +        if(vListIp.at(i) != QHostAddress::LocalHost && vListIp.at(i).toIPv4Address())
 +        {
 +            vIpAddr= vListIp.at(i).toString();
 +            break;
 +        }
 +    }
 +    if(vIpAddr.isEmpty())
 +        vIpAddr= QHostAddress(QHostAddress::LocalHost).toString();
 +
 +    this->setFont(QFont("Arial", 14));
 +    labStatusText= new QLabel(this);
 +    labStatusText->setText(tr("It's running on\n\nIP: %1\nport: %2").arg(vIpAddr).arg(tcpServer->serverPort()));
 +
 +    QBoxLayout *layCenter= new QBoxLayout(QBoxLayout::Direction::LeftToRight, this);
 +    layCenter->addWidget(labStatusText, 0, Qt::AlignCenter);
 +
 +
 +    this->vecFurtunes << "First String" << "Second String" << "Three String" << "Four String" << "Fiftin String";
 +
 +    connect(this->tcpServer, &QTcpServer::newConnection, this, &sserv::slotSendFortune);
 +}
 +
 +
 +void sserv::slotSendFortune()
 +{
 +    // Тут была передача строки из массива "this->vecFurtunes"
 +    /*QByteArray vBlockData;
 +    QDataStream vOutStreem(&vBlockData, QIODevice::WriteOnly);
 +    vOutStreem.setVersion(QDataStream::Qt_5_10);
 +    vOutStreem << this->vecFurtunes[QRandomGenerator::global()->bounded(this->vecFurtunes.size())];
 +
 +    QTcpSocket *vClientConnection= this->tcpServer->nextPendingConnection();
 +    connect(vClientConnection, &QAbstractSocket::disconnected, vClientConnection, &QObject::deleteLater);
 +
 +    vClientConnection->write(vBlockData);
 +    vClientConnection->disconnectFromHost(); */
 +
 +    // Передаем указанный файл
 +    QFile vFile("D:\\pic1.png");
 +    vFile.open(QIODevice::ReadOnly);
 +
 +    QByteArray vBlockData= vFile.readAll(); // read(64)- размер указан в байтах
 +
 +    QTcpSocket *vClientConnection= this->tcpServer->nextPendingConnection();
 +    connect(vClientConnection, &QAbstractSocket::disconnected, vClientConnection, &QObject::deleteLater);
 +    vClientConnection->write(vBlockData);
 +    vClientConnection->disconnectFromHost();
 +}
 </code> </code>
 </details> </details>
  
  
 +==== Клиент ====
 +-----
 +Клиент сам подключается к серверу и сервер сразу же начинает передачу. На клиенте просто обрабатываем сигнал **readyRead()**.\\
 +
 +Объект сокета должен быть глобальным, этим сокетом инициируем соединение и с этого сокета затем читаем доступные данные.
 +Читаем в **QByteArray** и далее работаем с ними. Если сохраняем в файл (как в примере) то необходимо открывать его в режиме **добавления**, т.к. вызов этой функции и собсна запись, происходят **при получении каждого пакета**.\\
 +
 +
 +<details>
 +<summary>:!: Пример: Простой tcp-клиент, получение файла</summary>
 +sc.h
 +<code cpp-qt>
 +#ifndef SC_H
 +#define SC_H
 +
 +#include <QDialog>
 +#include <QLabel>
 +#include <QLineEdit>
 +#include <QPushButton>
 +#include <QTcpSocket>
 +#include <QDataStream>
 +#include <QByteArray>
 +#include <QComboBox>
 +#include <QMessageBox>
 +#include <QHBoxLayout>
 +#include <QVBoxLayout>
 +#include <QTimer>
 +#include <QNetworkInterface>
 +#include <QGridLayout>
 +#include <QFile>
 +#include <QIODevice>
 +
 +class sc : public QDialog
 +{
 +    Q_OBJECT
 +
 +    QComboBox *comboHostServ;
 +    QLineEdit *editPortServ;
 +    QLabel *labComboCapt, *labEditCapt, *labStatus;
 +    QPushButton *butGetFortune, *butQuit;
 +
 +    QTcpSocket *tcpSocket;
 +    QDataStream VInputStream;
 +    QString VCurrFortune;
 +
 +private slots:
 +    void slotRequestNewFortune();
 +    void slotReadFortune();
 +    void slotDisplayError(QAbstractSocket::SocketError vSockError);
 +
 +public:
 +    sc(QWidget *parent = nullptr);
 +};
 +#endif // SC_H
 +</code>
 +
 +sc.cpp
 +<code cpp-qt>
 +#include "sc.h"
 +
 +sc::sc(QWidget *parent): QDialog(parent)
 +{
 +    this->setFont(QFont("Arial", 14));
 +    QGridLayout *layMainGrid= new QGridLayout(this);
 +    layMainGrid->setColumnStretch(1, 1);
 +    layMainGrid->setColumnStretch(2, 1);
 +
 +    labComboCapt= new QLabel("Выберите хост", this);
 +    comboHostServ= new QComboBox(this);
 +    comboHostServ->addItem(QHostAddress(QHostAddress::LocalHost).toString());
 +    layMainGrid->addWidget(labComboCapt, 0, 0);
 +    layMainGrid->addWidget(comboHostServ, 0, 1, 1, -1);
 +
 +    labEditCapt= new QLabel("Укажите порт", this);
 +    editPortServ= new QLineEdit(this);
 +    editPortServ->setText("2233");
 +    layMainGrid->addWidget(labEditCapt, 1, 0);
 +    layMainGrid->addWidget(editPortServ, 1, 1, 1, -1);
 +
 +    labStatus= new QLabel("This is status oO", this);
 +    layMainGrid->addWidget(labStatus, 2, 0, 1, -1, Qt::AlignHCenter);
 +
 +    butGetFortune= new QPushButton("Get Fort", this);
 +    butQuit= new QPushButton("Quit", this);
 +    layMainGrid->addWidget(butGetFortune, 3, 1);
 +    layMainGrid->addWidget(butQuit, 3, 2);
 +
 +    layMainGrid->setSpacing(20); // setRowStretch(0, 10);
 +
 +    tcpSocket= new QTcpSocket(this);
 +    VInputStream.setDevice(tcpSocket);
 +    VInputStream.setVersion(QDataStream::Qt_4_0);
 +
 +    connect(tcpSocket, &QIODevice::readyRead, this, &sc::slotReadFortune);
 +    connect(tcpSocket, &QAbstractSocket::errorOccurred, this, &sc::slotDisplayError);
 +    connect(butGetFortune, &QPushButton::clicked, this, &sc::slotRequestNewFortune);
 +    connect(butQuit, &QPushButton::clicked, this, &QDialog::close);
 +}
 +
 +
 +void sc::slotRequestNewFortune()
 +{
 +    butGetFortune->setEnabled(false);
 +    tcpSocket->abort();
 +    tcpSocket->connectToHost(comboHostServ->currentText(), editPortServ->text().toInt());
 +}
 +
 +
 +void sc::slotReadFortune()
 +{
 +    // Тут было получение строки, не до конца понятна работа с транзакцией
 +    /*VInputStream.startTransaction();
 +    QString vNextFortune;
 +    VInputStream >> vNextFortune;
 +
 +    if(!VInputStream.commitTransaction())
 +        return;
 +
 +    if(vNextFortune== VCurrFortune)
 +    {
 +        QTimer::singleShot(0, this, &sc::slotRequestNewFortune);
 +        return;
 +    }
 +
 +    VCurrFortune= vNextFortune;
 +    labStatus->setText(VCurrFortune);
 +    butGetFortune->setEnabled(true);*/
 +
 +    // Получение файла
 +    QByteArray vGettingData;
 +    vGettingData= tcpSocket->readAll();
 +
 +    QFile vFile("D:\\pic1-getting.png");
 +    vFile.open(QIODevice::Append);
 +    vFile.write(vGettingData);
 +    vFile.close();
 +
 +
 +    butGetFortune->setEnabled(true);
 +}
 +
 +
 +void sc::slotDisplayError(QAbstractSocket::SocketError vSockError)
 +{
 +    switch (vSockError)
 +    {
 +        case QAbstractSocket::RemoteHostClosedError:
 +            break;
 +        case QAbstractSocket::HostNotFoundError:
 +            QMessageBox::information(this, "Header", "Host not found");
 +            break;
 +        case QAbstractSocket::ConnectionRefusedError:
 +            QMessageBox::information(this, "Header", "Connection refured");
 +            break;
 +        default:
 +            QMessageBox::information(this, "Header", "Error - "+ tcpSocket->errorString());
 +            break;
 +    }
 +    butGetFortune->setEnabled(true);
 +}
 +</code>
 +</details>
 +
 +
 +
 +===== Еще пример клиент/сервера =====
 +Сервер поддерживает **множественное подключение**, для **каждого соединения** создается **собственный контекст**, с собственными **буферами**\\
 +
 +При передаче, данные делятся на пакеты дважды, типа программные и физические.\\
 +**Программные** формируются при каждом вызове метода **write()**, если писать через **QDataStream** в **QByteArray**, то перед каждой записью в датастрим будет автоматом добавлено интовое значение, с указанием размера записываемых данных.\\
 +
 +**Физические** формируются уже на транспортном уровне, чаще всего для них отводится 65 536 байт (его размер хранится в двухбайтовом поле заголовка т.е. само это поле может содержать максимум такое значение), размер может меняться по согласованию сторон, содержится в TCP заголовках.\\
 +
 +Передача ведется потоковая, сначала идет служебная инфа, с указанием размера в т.ч., затем все остальное принимаем как содержимое файла, пока не достигнем указанный размер файла.\\
 +
 +Служебную инфу пишем с помощью датаСтрим, что бы был указан размер этого "программного" пакета, и чтобы нам вырезать точное кол-во байт, занимаемое этой служебной инфой, содержимое файла просто читаем пока не достигнем указанного размера.\\
 +
 +Cчитывание 4ех байт остается корректным до тех пор пока программный пакет < 4gb, т.к. в эти 4 байта умещено интовое значение, которое принимает максимум 4 млрд.. байтов ~ 4gb.\\
 +Если программный пакет будет больше, мы просто некорректно считаем значение его размера и следовательно сам пакет не сможем принять (будет '-1')\\
 +
 +<details>
 +<summary>:!: Пример: Сервер</summary>
 +
 +
 +sserv.h
 +<code cpp-qt>
 +
 +</code>
 +
 +sserv.cpp
 +<code cpp-qt>
 +
 +</code>
 +</details>
 +
 +
 +<details>
 +<summary>:!: Пример: Клиент</summary>
 +**Примечания:**\\
 +:?: Установка соединения (**connectToHost()**) проходит асинхронно, после ее вызова уже можно писать данные в сокет (**write()**), данные будут собираться в буфер (**неизвестно какое ограничение**), и будут ожидать записи.\\
 +Правда в случае ошибки соединения, буфер очистится, при проверке, данные были доступны вплоть до последнего сигнала об ошибке, при повторном запуске отправки, буфер уже был очищен.\\
 +:?: Запись можно делать из файла сразу в метод **write()**, без создания дополнительных **QByteArray** и **QDataStream**, как было в прошлых примерах.\\
 +**write()** принимает **QByteArray**, метод **read()** возвращает **QByteArray**, для записи служебных данных можно сделать отдельный метод для конвертации, вот тут та и понадобится **QDataStream**, который корректно запишет нужные данные в **QByteArray**.\\
 +:?: Что касается **проверки и установления** коннекта:\\
 +в нашем случае сокет может быть в след состояниях:\\
 +**0** - не подключен - нужно **вызвать коннект**\\
 +**1,2** - выполняет поиск и начал устанавливать соединение - соединения еще нет, но и делать вроде ничего не надо, правда неизвестно может ли оно быть зависшим в этот состоянии ?, думаю тут- **ничего не делать**\\
 +**3** - установленно - **ничего не делать**\\
 +**6** - вот вот закроется - т.е. запрошен дисконнект, ожидается отправка очереди данных и коннект захлопнется, в таком случае явно нужно снова **вызвать коннект**\\
 +
 +Что касается отправки файла, пишем данные кусками в метод **write()**, он возвращает кол-во записанных байт, это кол-во отнимаем от размера файла, так управляем циклом\\
 +В случае ошибки, **write()** возвращает **-1** и все идет по п#зде, поэтому необходимо проверять значение пред тем как с ним работать (отнимать от общем суммы)\\
 +Так же, после вызова **write()** и до фактической отправки данных нужно какое то время, до этого данные копятся в буфере (видно в **bytesToWrite()**), если писать в цикле то данные **не успевают** уходить в **заполняют буфер** до талого, поэтому нужен метод **wait()**, либо сигнало/слот можно прикрутить какой нибудь\\
 +
 +Данные **пишутся** так же **пакетами**, как были записаны в **write()**, по одному после каждого **wait()**\\
 +Если нет коннекта, данные не уходят, **bytesToWrite()** не очищается, последующие вызовы **write()** возвращают **-1**, **wait()** возвращает **false**\\
 +
 +
 +
 +sc.h
 +<code cpp-qt>
 +
 +</code>
 +
 +sc.cpp
 +<code cpp-qt>
 +
 +</code>
 +</details>
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +<details>
 +<summary>:!: Пример: </summary>
 +<code cpp-qt>
 +
 +</code>
 +</details>
develop/qt/network.1632463315.txt.gz · Последнее изменение: 2021/09/24 06:01 — admin