Пример использования QLocalServer и QLocalSocket

Qt

Содержание

В статье описывается использование 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 .

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Остальные сигналы и слоты соединены между собой в старом стиле, так как фрагмент кода сокета, показанный ниже, компилировался с ошибками.

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!");
}

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Как стать автором?

Внесите вклад в развитие сообщества EVILEG.

Узнайте, как стать автором сайта.

Изучить
Donate

Добрый день, Дорогие Пользователи !!!

Я Евгений Легоцкой, разработчик EVILEG. И это мой хобби-проект, который помогает учиться программированию другим программистам и разработчикам

Если сайт помог вам, и вы хотите также поддержать развитие сайта, то вы можете сделать пожертвование следующими способами

PayPalYandex.Money
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг Timeweb
АС
26 мая 2020 г. 12:29
Артём Сун-Дун-Чан

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
МН
25 мая 2020 г. 12:33
Митя Нагибин

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
f
25 мая 2020 г. 6:05
falcon

C++ - Тест 001. Первая программа и типы данных

  • Результат:66баллов,
  • Очки рейтинга-1
Последние комментарии
28 мая 2020 г. 16:14
Евгений Легоцкой

Qt/C++ - Урок 039. Как закрасить строку в QSqlTableModel по значению в столбце

Ну в моём примере, который в статье сработало так model->setData(model->index(1, 1), 7); Поскольку model->index(1, 0) - это индекс колонки id, которая скрыта, поэтому…
МА
28 мая 2020 г. 16:08
Михаил А

Qt/C++ - Урок 039. Как закрасить строку в QSqlTableModel по значению в столбце

Спасибо, завтра првоерю. А model->setData(model->index(1, 0), 7); Тоже заработало?
28 мая 2020 г. 16:06
Евгений Легоцкой

Qt/C++ - Урок 039. Как закрасить строку в QSqlTableModel по значению в столбце

Да, метод data всё-таки влиял, я переписал его так и заработало удаление QVariant TableModel::data(const QModelIndex &idx, int role) const{ if (role == Qt::BackgroundColorRole) {…
28 мая 2020 г. 15:49
Евгений Легоцкой

Django - Урок 011. Добавление комментариев на сайт с Django

Он более функциональный и его функционал объективно лучше поддерживается Django. Из первого, что приходит на ум: Это наличие полей типа Array Поддержка полей для JSON …
28 мая 2020 г. 15:42
progammist

Django - Урок 011. Добавление комментариев на сайт с Django

а в чем явное преимущество postgresql над mysql?)
Сейчас обсуждают на форуме
RG
28 мая 2020 г. 19:21
Rovshan Gurbanov

Сборка под старые версии Android

У меня SDK почти все версии есть, NDK есть версии 10, 17, 21. Но собирается приложение только с NDK v21 под Android версии 7.0 и выше Версия Qt у меня 5.14.2
28 мая 2020 г. 8:58
Евгений Легоцкой

Освобождение памяти QMainWindow::setCentralWidget

Да, соглашусь. Просто удаление происходит позже, а не сразу.
28 мая 2020 г. 6:43
Михаиллл

При подключении к git как указать пароль?

Нужно сделать ssh-keygen и потом полученый из файла код скопировать в ssh ключ в бикбакете
F
28 мая 2020 г. 2:42
Fidan

QML

Да, проблема ушла, спасибо.
1 мая 2020 г. 9:19
Михаиллл

Как в Qt в qmenu добавить scrollarea

Вот это наследованный класс меню. Но посути это обычное меню. #pragma once#include <QtWidgets>class TransMenu : public QMenu { Q_OBJECTpublic: TransMenu(QWidget* parent = …
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB