- 1. QLocalServer
- 2. QLocalSocket
В статье описывается использование QLocalServer и QLocalSocket. Пример является переработкой кода из книги Шлее «Qt 5.3. Профессиональное программирование на C++», посвящённого QTcpServer и QTcpSocket соответственно. Не смотря на то, что наименования классов похожи, и используются в одном и том же модуле, пара существенных отличий есть. Их мы рассмотрим по ходу изложения.
QLocalServer и QLocalSocket реализуют механизмы именованных каналов или сокетов домена Unix. Подробнее об этом можно почитать здесь и здесь .
Пример состоит из двух частей. В первой реализуется QLocalServer, во второй – QlocalSocket.
Сервер реализует следующий функционал: на монитор выводится виджет с текстовым полем, в котором будет отображаться информация, поступающая извне, от сокета. Сервер ожидает входящих соединений и, в случае успешного подключения, отправляет сокету сообщение об этом. Кроме того, сервер ретранслирует сокету передаваемую от сокета информацию.
В свою очередь, сокет выводит на монитор виджет с текстовым полем и кнопкой. В текстовом поле отображается информация, поступающая от сервера и служебная информация самого сокета (обнаружение подключения в начале сеанса или ошибка подключения к серверу в случае его недоступности). По нажатию кнопки сокет отправляет информацию серверу.
Рассмотрим подробно код примера.
QLocalServer
- # QLocalServer.pro
- # Кроме модулей core gui подключаем network
- QT += core gui network
- greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- TARGET = QLocalServer
- TEMPLATE = app
- DEFINES += QT_DEPRECATED_WARNINGS
- SOURCES += \
- main.cpp \
- mylocalserver.cpp
- HEADERS += \
- mylocalserver.h
- // main.cpp
- #include "mylocalserver.h"
- #include <QApplication>
- int main(int argc, char* argv[])
- {
- QApplication app(argc, argv);
- // Создаём и показываем объект класса MyLocalServer, определённого далее,
- // для запуска сервера с именем "MyLocalServer"
- MyLocalServer server("MyLocalServer");
- server.show();
- return app.exec();
- }
- // mylocalserver.h
- #ifndef MYLOCALSERVER_H
- #define MYLOCALSERVER_H
- #include <QWidget>
- #include <QLocalServer>
- #include <QLocalSocket>
- #include <QTextEdit>
- class MyLocalServer : public QWidget
- {
- Q_OBJECT
- public:
- MyLocalServer(QString serverName, QWidget* parent = 0);
- ~MyLocalServer();
- private:
- // Указатель на QLocalServer
- QLocalServer* localServer;
- // Указатель на QTextEdit, в котором будет показываться поступающая
- // от клиента информация
- QTextEdit* textEdit;
- // Переменная для хранения размера получаемого от клиента блока
- quint16 nextBlockSize;
- // Метод для отправки клиенту подтверждения о приёме информации
- void sendToClient(QLocalSocket* localSocket, const QString& string);
- public slots:
- // Слот обработки нового клиентского подключения
- virtual void slotNewConnection();
- // Слот чтения информации от клиента
- void slotReadClient();
- };
- #endif // MYLOCALSERVER_H
- // mylocalserver.cpp
- #include "mylocalserver.h"
- #include <QVBoxLayout>
- #include <QMessageBox>
- #include <QLabel>
- #include <QTime>
- MyLocalServer::MyLocalServer(QString serverName, QWidget* parent)
- : QWidget(parent), nextBlockSize(0) // Устанавливаем nextBlockSize равным нулю
- {
- // Создаём и запускаем сервер командой listen.
- // Если сервер не может быть запущен, выдать сообщение об ошибке и завершить работу программы
- localServer = new QLocalServer(this);
- if(!localServer->listen(serverName))
- {
- QMessageBox::critical(0, "Server error",
- "Unable to start server:" + localServer->errorString());
- localServer->close();
- return;
- }
- // Соединяем сигнал сервера о наличии нового подключения с обработчиком нового клиентского подключения
- connect(localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
- // Формируем окно для просмотра текстовых сообщений от клиента
- textEdit = new QTextEdit;
- textEdit->setReadOnly(true);
- QVBoxLayout* layout = new QVBoxLayout;
- layout->addWidget(new QLabel(serverName));
- layout->addWidget(textEdit);
- setLayout(layout);
- }
- MyLocalServer::~MyLocalServer()
- {
- }
- // Слот обработки нового клиентского подключения
- void MyLocalServer::slotNewConnection()
- {
- // Получаем сокет, подключённый к серверу
- QLocalSocket* localSocket = localServer->nextPendingConnection();
- // Соединяем сигнал отключения сокета с обработчиком удаления сокета
- connect(localSocket, SIGNAL(disconnected()), localSocket, SLOT(deleteLater()));
- // Соединяем сигнал сокета о готовности передачи данных с обработчиком данных
- connect(localSocket, SIGNAL(readyRead()), this, SLOT(slotReadClient()));
- // Отправляем информацию клиенту о соединении с сервером
- sendToClient(localSocket, "Server response: Connected!");
- }
- // Слот чтения информации от клиента
- void MyLocalServer::slotReadClient()
- {
- // Получаем QLocalSocket после срабатывания сигнала о готовности передачи данных
- QLocalSocket* localSocket = (QLocalSocket*)sender();
- // Создаём входной поток получения данных на основе сокета
- QDataStream in(localSocket);
- // Устанавливаем версию сериализации данных потока. У клиента и сервера они должны быть одинаковыми
- in.setVersion(QDataStream::Qt_5_3);
- // Бесконечный цикл нужен для приёма блоков данных разных размеров, от двух байт и выше
- for(;;)
- {
- // Если размер блока равен нулю
- if(!nextBlockSize)
- {
- // Если размер передаваемого блока меньше двух байт, выйти из цикла
- if(localSocket->bytesAvailable() < (int)sizeof(quint16))
- break;
- // Извлекаем из потока размер блока данных
- in >> nextBlockSize;
- }
- // Извлекаем из потока время и строку
- QTime time;
- QString string;
- in >> time >> string;
- // Преобразуем полученные данные и показываем их в виджете
- QString message = time.toString() + " " + "Client has sent - " + string;
- textEdit->append(message);
- nextBlockSize = 0;
- // Отправляем ответ клиенту
- sendToClient(localSocket, "Server response: received \"" + string + "\"");
- }
- }
- // Метод для отправки клиенту подтверждения о приёме информации
- void MyLocalServer::sendToClient(QLocalSocket* localSocket, const QString& string)
- {
- // Поскольку заранее размер блока неизвестен (параметр string может быть любой длины),
- // вначале создаём объект array класса QByteArray
- QByteArray array;
- // На его основе создаём выходной поток
- QDataStream out(&array, QIODevice::WriteOnly);
- // Устанавливаем версию сериализации данных потока
- out.setVersion(QDataStream::Qt_5_3);
- // Записываем в поток данные для отправки. На первом месте идёт нулевой размер блока
- out << quint16(0) << QTime::currentTime() << string;
- // Перемещаем указатель на начало блока
- out.device()->seek(0);
- // Записываем двухбайтное значение действительного размера блока без учёта пересылаемого размера блока
- out << quint16(array.size() - sizeof(quint16));
- // Отправляем получившийся блок клиенту
- localSocket->write(array);
- }
QLocalSocket
- # QLocalSocket.pro
- # Кроме модулей core gui подключаем network
- QT += core gui network
- greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- TARGET = QLocalSocket
- TEMPLATE = app
- DEFINES += QT_DEPRECATED_WARNINGS
- SOURCES += \
- main.cpp \
- mylocalsocket.cpp
- HEADERS += \
- mylocalsocket.h
- // main.cpp
- #include "mylocalsocket.h"
- #include <QApplication>
- int main(int argc, char* argv[])
- {
- QApplication app(argc, argv);
- // Создаём и показываем объект класса MyLocalSocket, определённого далее,
- // для запуска клиента, подключаемого к серверу с именем "MyLocalServer"
- MyLocalSocket socket("MyLocalServer");
- socket.show();
- return app.exec();
- }
- // mylocalsocket.h
- #ifndef MYLOCALSOCKET_H
- #define MYLOCALSOCKET_H
- #include <QWidget>
- #include <QLocalSocket>
- #include <QTextEdit>
- #include <QPushButton>
- class MyLocalSocket : public QWidget
- {
- Q_OBJECT
- public:
- MyLocalSocket(QString serverName, QWidget* parent = 0);
- ~MyLocalSocket();
- private:
- // Указатель на QLocalSocket
- QLocalSocket* localSocket;
- // Указатели на элементы интерфейса
- QTextEdit* textEdit;
- QPushButton* sendRevision;
- // Размер принимаемого от сервера блока
- quint16 nextBlockSize;
- // Номер ревизии, отправляемый серверу
- // Увеличивается при каждом нажатии QPushButton
- int revision;
- private slots:
- // Слот чтения информации, получаемой от сервера
- void slotReadyRead();
- // Слот обработки ошибок сокета
- void slotError(QLocalSocket::LocalSocketError error);
- // Слот передачи информации на сервер
- void slotSendToServer();
- // Слот обработки сигнала соединения с сервером
- void slotConnected();
- };
- #endif // MYLOCALSOCKET_H
- // mylocalsocket.cpp
- #include "mylocalsocket.h"
- #include <QVBoxLayout>
- #include <QLabel>
- #include <QTime>
- #include <QMessageBox>
- MyLocalSocket::MyLocalSocket(QString serverName, QWidget* parent)
- : QWidget(parent), nextBlockSize(0), revision(0)
- // Устанавливаем nextBlockSize и revision равными нулю
- {
- // Инициализируем сокет
- localSocket = new QLocalSocket(this);
- // Устанавливаем соединение между сигналом ошибки сокета с обработчиком ошибок
- connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
- this, &MyLocalSocket::slotError);
- // Устанавливаем имя сервера, к которому сокет должен подключаться
- localSocket->setServerName(serverName);
- // Устанавливаем соединение между сигналом подключения сокета к серверу
- // и обработчиком сигнала
- connect(localSocket, SIGNAL(connected()), SLOT(slotConnected()));
- // Соединяем сигнал сокета о готовности приёма данных данных с обработчиком данных
- connect(localSocket, SIGNAL(readyRead()), SLOT(slotReadyRead()));
- // Инициализируем элементы интерфейса
- textEdit = new QTextEdit;
- sendRevision = new QPushButton("Send next revision");
- // Соединяем нажатие кнопки с обработчиком, передающим информацию о ревизии на сервер
- connect(sendRevision, SIGNAL(clicked()), this, SLOT(slotSendToServer()));
- // Настраиваем элементы интерфейса и формируем вид окна клиента
- textEdit->setReadOnly(true);
- QVBoxLayout* layout = new QVBoxLayout;
- layout->addWidget(new QLabel("Sender revisions"));
- layout->addWidget(textEdit);
- layout->addWidget(sendRevision);
- setLayout(layout);
- // Подключаем сокет к серверу
- localSocket->connectToServer();
- }
- MyLocalSocket::~MyLocalSocket()
- {
- }
- // Слот чтения информации, получаемой от сервера
- void MyLocalSocket::slotReadyRead()
- {
- // Всё аналогично приёму информации на стороне сервера
- QDataStream in(localSocket);
- in.setVersion(QDataStream::Qt_5_3);
- for(;;)
- {
- if(!nextBlockSize)
- {
- if(localSocket->bytesAvailable() < (int)sizeof(quint16))
- break;
- }
- in >> nextBlockSize;
- if(localSocket->bytesAvailable() < nextBlockSize)
- break;
- QTime time;
- QString string;
- in >> time >> string;
- textEdit->append(time.toString() + " " + string);
- nextBlockSize = 0;
- }
- }
- // Слот обработки ошибок сокета
- void MyLocalSocket::slotError(QLocalSocket::LocalSocketError error)
- {
- QString strError =
- "Error: " + (error == QLocalSocket::ServerNotFoundError ?
- "The server was not found." :
- error == QLocalSocket::PeerClosedError ?
- "The server is closed." :
- error == QLocalSocket::ConnectionRefusedError ?
- "The connection was refused." :
- QString(localSocket->errorString()));
- textEdit->append(strError);
- }
- // Слот передачи информации на сервер
- void MyLocalSocket::slotSendToServer()
- {
- // Блок для передачи формируется аналогично тому, как это делается на сервере
- QByteArray arrayBlock;
- QDataStream out(&arrayBlock, QIODevice::WriteOnly);
- out.setVersion(QDataStream::Qt_5_3);
- ++revision;
- QString message = "Revision: " + QString("%1").arg(revision);
- out << quint16(0) << QTime::currentTime() << message;
- out.device()->seek(0);
- out << quint16(arrayBlock.size() - sizeof(quint16));
- localSocket->write(arrayBlock);
- }
- // Слот обработки сигнала соединения с сервером
- void MyLocalSocket::slotConnected()
- {
- textEdit->append("Received the connected() signal");
- }
Первая особенность заключается в том, как именно подключается для QLocalSocket сигнал ошибки слота к сигналу обработки ошибок: через QOverload.
- connect(localSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
- this, &MyLocalSocket::slotError);
Вторая особенность связана с тем, что для QLocalSocket это соединение должно быть объявлено до подключения к серверу, тогда как для QTcpSocket соединение сигнала ошибки и слота обработки ошибок может быть объявлено после подключения к серверу. Подробнее об этом см. тему форума: https://evileg.com/ru/forum/topic/965/ .
В итоге на мониторе должна быть такая картина.
Код примеров на GitHub: QLocalServer , QLocalSocket .
Остальные сигналы и слоты соединены между собой в старом стиле, так как фрагмент кода сокета, показанный ниже, компилировался с ошибками.