Это старая версия документа!
qt += network
Для реализации серверного функционала используется класс QTcpServer.
Для непосредственного приема передачи используются сокеты (базовый класс QAbstractSocket), есть доп реализации Tcp, Udp, Ssl и т.д. Собсна клиент содержит в себе только объект сокета.
Для сетевых запросов существует класс QNetworkAccessManager.
Позволяет принимать Tcp-соединения, порт можно выбрать либо авто, IP так же можно выбрать конкретный либо все IP хоста.
Вызовите listen(), для начала прослушивания. close() для завершения.
При подключении клиента, вызывается сигнал newConnection(). Вызовите nextPendingConnection() что бы принять соединение как QTcpSocket, который можно использовать для связи с клиентом.
В случае ошибки, serverError() возвращает тип ошибки, а errorString() вернут описание.
Сервер в основном предназначен для использования сигнало/слотами, имеются методы для блокирующей работы, типа «waitFor..».
Является базовым классом для QTcp и QUdp сокетов, и содержит все общие функции этих двух классов. Его можно наследовать, для реализации собственного сокета.
Так же, API QAbstractSocket объединяет большинство различий между двумя протоколами.
Например метод connectToHost(), для Udp, устанавливает виртуальное соединение. QAbstractSocket запоминает адрес и порт, переданные в этот метод, и такие функции как read() или write() читают эти данные оттуда.
В любой момент, объект имеет определенное состояние (метод state()).
Начальное состояние- UnconnectedState.
После вызова connectToHost() сокет сначала входит в HostLookupState, если хост найден тогда переходит в ConnectingState и излучает сигнал hostFound().
После установки соединения, статус переходит в ConnectedState и излучает сигнал connected().
В случае ошибки, на любом этапе, выдается сигнал errorOcurred(). При смене состояния, генерируется stateChanged().
Метод isValid() возвращает bool, готов ли сокет к чтению/записи, но состояние должно быть ConnectedState.
Читать/писать данные можно методами: read(), write(), readLine(), readAll(), getChar(), putChar(), ungetChar().
Сигнал bytesWritten() возникает каждый раз, когда данные были записаны в сокет. Qt не ограничивает размер буфера записи, за ним можно следить используя этот сигнал.
Сигнал readyRead() выдается каждый раз, когда поступает новый блок данных, bytesAvailable() возвращает кол-во доступных байт.
Для чтения подключаемся к слоту readyRead() и читаем все доступные байты, если считать не все, то оставшиеся будут доступны позже, а любые входящие данные будут добавлены во внутренний буфер. Чтобы ограничить размер этого буфера, метод- setReadBufferSize().
Для закрытия соединения, метод disconnectFromHost(), сокет входит в состояние ClosingState и ждет очереди ожидающих данных, после нее, фактически закрывается соединение, переходит в UnconecctedState и посылает сигнал disconnected().
Если нужно немедленно прервать соединение, есть метод abort().
Если удаленный хост закрывает соединение, то сокет выдаст errorOcurrent(QAbstractSocket::RemoteHostClosedError), в течении которого состояние будет все еще ConnectedState, а затем будет сигнал disconnected().
Параметры удаленного узла (переданного в connectedToHost())- peerPort(), peerAddress(), peerName().
Локальные- localPort(), localAddress().
Так же есть ряд методов синхронизации потоков, типа «waitFor..».
Распространенный способ использования- привязка к адресу и порту с помощью bind(), затем вызов write/read/receiveDatagram(), можно не вызывать ее, если просто отправлять дейтаграммы.
Что бы использовать read(), readLine(), write() и т.д. нужно выполнить (условное) подключение к узлу, метод connectToHost().
Сигналы bytesWrite()/readyRead() вызываются при отправке/получении дейтаграмм.
QUdpSocket поддерживает многоадресную рассылку, {join,leave}Multicast{Group,Interface}.
Для работы с дейтаграммами используется класс QNetworkDatagram.
API для доступа к сети построено вокруг этого объекта, содержит в общую конфигурацию и настройку отправляемых запросов, прокси, кэша, а так же связанные с сетевой работой слото-сигналы.
Одного экземпляра достаточно для всего приложения, вызывается только из своего потока.
Этот объект управляет авторизацией на ресурсе, если она необходима.
Для запросов и ответов есть классы QNetworkRequest и QNetworkReply соответственно.
Класс содержит адреса v4/v6.
Адрес устанавливается и извлекается, можно проверить его тип, поддерживает предопределенные адреса, типа LocalHost, Broadcast, Any.
Сервер на базе QTcpServer, слушает указанный IP и порт.
При приеме входящего подключения отправляет клиенту указанную инфу и закрывает соединение.
Данные передаются в открытом виде
При приеме соединения вызывается сигнал NewConnection(), его обрабатываем.
Внутри создаем объект пользовательского подключения, методом nextPendingConnection(), и работаем с этим сокетом подключения, отправляемые данные просто пишем в него, как в устройство (QIODevice).
Запись делаем методом write(), на сколько я понял разделение на пакеты происходит уже на уровне протокола, в зависимости от размера данных, и неважно сколько раз мы вызываем write().
Пакеты по 60-65 кБ.
В данном примере открываем файл и заносим его содержимое в массив QByteArray, можно сразу целиком, можно частями, с указанным размером (чтение автоматом продолжается с нужного места)
Далее передаем получившийся QByteArray в метод write(), тем самым отправляя его клиенту.
Основан на примере «Fortune Server Example», переделан под файл sserv.h
#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
sserv.cpp
#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(); }
Клиент сам подключается к серверу и сервер сразу же начинает передачу. На клиенте просто обрабатываем сигнал readyRead().
Объект сокета должен быть глобальным, этим сокетом инициируем соединение и с этого сокета затем читаем доступные данные.
Читаем в QByteArray и далее работаем с ними. Если сохраняем в файл (как в примере) то необходимо открывать его в режиме добавления, т.к. вызов этой функции и собсна запись, происходят при получении каждого пакета.
sc.h
#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
sc.cpp
#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); }
В данном примере изменен подход: файл передается от клиента серверу, сервер почти поддерживает множество клиентов (создается объект сокета при каждом новом подключении, этот вопрос еще нужно больше изучить), и главное то что передается служебная инфа, благодаря тому что чтение/запись ведется с помощью потоков данных (QDataStream).
sserv.h
#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> #include <QCheckBox> #include <QLineEdit> #include <QTextEdit> class sserv : public QDialog { Q_OBJECT QTcpServer *tcpServer; QCheckBox *checkRunning; QLabel *labPort; QLineEdit *editPort; QTextEdit *editEventsLog; private slots: void slotSetNewConnection(); void slotReadDataFromClient(); void slotStartStop(int); public: sserv(QWidget *parent = nullptr); }; #endif // SSERV_H
sserv.cpp
#include "sserv.h" sserv::sserv(QWidget *parent): QDialog(parent) { this->setFont(QFont("Arial", 14)); checkRunning= new QCheckBox("Running Server", this); labPort= new QLabel("Port:", this); editPort= new QLineEdit("2233", this); editEventsLog= new QTextEdit(this); editEventsLog->setFont(QFont("Arial", 10)); QGridLayout *layMain= new QGridLayout(this); layMain->setColumnStretch(1, 1); layMain->setColumnStretch(2, 1); layMain->addWidget(checkRunning, 0, 0); layMain->addWidget(labPort, 0, 1, Qt::AlignRight); layMain->addWidget(editPort, 0, 2); layMain->addWidget(editEventsLog, 1, 0, 1, -1); tcpServer= new QTcpServer(this); connect(tcpServer, &QTcpServer::newConnection, this, &sserv::slotSetNewConnection); connect(checkRunning, &QCheckBox::stateChanged, this, &sserv::slotStartStop); } void sserv::slotSetNewConnection() { QTcpSocket *vNewConnect= tcpServer->nextPendingConnection(); connect(vNewConnect, &QTcpSocket::readyRead, this, &sserv::slotReadDataFromClient); connect(vNewConnect, &QTcpSocket::disconnected, vNewConnect, &QTcpSocket::deleteLater); editEventsLog->insertPlainText(tr("\nНовое подключение %1, %2, %3").arg(vNewConnect->peerName()).arg(vNewConnect->peerAddress().toString()).arg(vNewConnect->peerPort())); } void sserv::slotReadDataFromClient() { QTcpSocket *vConnect= static_cast<QTcpSocket*>(sender()); QDataStream vInput(vConnect); vInput.setVersion(QDataStream::Qt_4_0); QByteArray vFirstStr; vInput >> vFirstStr; // Предполагалось тут считывать тоже через поток, но можно и так тоже QByteArray vBuffer; vBuffer= vConnect->readAll(); QFile vFile("D:\\this-text-getting.txt"); vFile.open(QIODevice::WriteOnly); vFile.write(vBuffer); vFile.close(); } void sserv::slotStartStop(int vState) { switch(vState) { case Qt::Checked: editPort->setEnabled(false); if(!tcpServer->listen(QHostAddress::LocalHost, editPort->text().toInt())) editEventsLog->insertPlainText("\nНе удается запустить сервер - "+ tcpServer->errorString()); else editEventsLog->insertPlainText("\nСервер запущен на порту - "+ QString::number(tcpServer->serverPort())); break; case Qt::Unchecked: tcpServer->close(); editPort->setEnabled(true); editEventsLog->insertPlainText("\nСервер остановлен"); break; default: editEventsLog->insertPlainText(tr("\nПередано не корректное значение, текущее состояние- ").arg(tcpServer->isListening() ? "работает":"не работает")); break; } }
sc.h
#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> #include <QTextEdit> class sc : public QDialog { Q_OBJECT QLabel *labPortServ; QLineEdit *editPort; QPushButton *butSendFile, *butConnectToServ; QTextEdit *editEventsLog; QTcpSocket *tcpSocket; private slots: void slotDisplayError(QAbstractSocket::SocketError vSockError); void slotSendFile(); void slotConNocon(); void slotChangeState(QAbstractSocket::SocketState); public: sc(QWidget *parent = nullptr); }; #endif // SC_H
sc.cpp
#include "sc.h" sc::sc(QWidget *parent): QDialog(parent) { this->setFont(QFont("Arial", 14)); QGridLayout *layMain= new QGridLayout(this); layMain->setColumnStretch(0,1); layMain->setColumnStretch(1,1); labPortServ= new QLabel("Укажите порт:", this); editPort= new QLineEdit("2233", this); butConnectToServ= new QPushButton("Connect To Serv", this); butSendFile= new QPushButton("Отправить файл", this); editEventsLog= new QTextEdit(this); editEventsLog->setFont(QFont("Arial", 10)); layMain->addWidget(labPortServ, 0, 0, Qt::AlignRight); layMain->addWidget(editPort, 0, 1); layMain->addWidget(butConnectToServ, 0, 2); layMain->addWidget(butSendFile, 1, 0, 1, 3); layMain->addWidget(editEventsLog, 2, 0, 1, 3); tcpSocket= new QTcpSocket(this); connect(tcpSocket, &QTcpSocket::errorOccurred, this, &sc::slotDisplayError); connect(butConnectToServ, &QPushButton::clicked, this, &sc::slotConNocon); connect(butSendFile, &QPushButton::clicked, this, &sc::slotSendFile); connect(tcpSocket, &QTcpSocket::stateChanged, this, &sc::slotChangeState); } void sc::slotSendFile() { QFile vFile("D:\\this-text_small.txt"); vFile.open(QIODevice::ReadOnly); QByteArray vBuffer; QDataStream vOut(&vBuffer, QIODevice::WriteOnly); vOut.setVersion(QDataStream::Qt_4_0); vOut << "-=This Service String=-"; vOut << vFile.readAll(); tcpSocket->write(vBuffer); editEventsLog->insertPlainText("\nFile is written"); } void sc::slotConNocon() { if(tcpSocket->state() == QAbstractSocket::ConnectedState) { tcpSocket->abort(); tcpSocket->close(); } else if (tcpSocket->state() == QAbstractSocket::UnconnectedState) { tcpSocket->abort(); tcpSocket->connectToHost(QHostAddress::LocalHost, editPort->text().toInt()); } } void sc::slotChangeState(QAbstractSocket::SocketState vSocket) { switch (vSocket) { case QAbstractSocket::UnconnectedState: editEventsLog->insertPlainText("\n0 The socket is not connected"); break; case QAbstractSocket::HostLookupState: editEventsLog->insertPlainText("\n1 The socket is performing a host name lookup"); break; case QAbstractSocket::ConnectingState: editEventsLog->insertPlainText("\n2 The socket has started establishing a connection"); break; case QAbstractSocket::ConnectedState: editEventsLog->insertPlainText("\n3 A connection is established"); break; case QAbstractSocket::BoundState: editEventsLog->insertPlainText("\n4 The socket is bound to an address and port"); break; case QAbstractSocket::ClosingState: editEventsLog->insertPlainText("\n6 The socket is about to close (data may still be waiting to be written)"); break; case QAbstractSocket::ListeningState: editEventsLog->insertPlainText("\n5 For internal use only"); break; } } void sc::slotDisplayError(QAbstractSocket::SocketError vSockError) { editEventsLog->insertPlainText("\n--== Error ==-- "+ tcpSocket->errorString()); }