Пример использования 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 .

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

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

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

LP
Nov. 12, 2019, 8:22 a.m.
Lev Parhimovich

C++ - Test 006. Enumerations

  • Result:50points,
  • Rating points-4
LP
Nov. 12, 2019, 7:35 a.m.
Lev Parhimovich

C++ - Test 005. Structures and Classes

  • Result:66points,
  • Rating points-1
LP
Nov. 12, 2019, 7:26 a.m.
Lev Parhimovich

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
Last comments
b
Nov. 9, 2019, 8:28 a.m.
bastonc

спасибо ещё раз. огромное, за уделённое время
b
Nov. 9, 2019, 8:24 a.m.
bastonc

Спасибо Вам большое. Буду изучать.
Nov. 9, 2019, 5:58 a.m.
Evgenij Legotskoj

Добрый день. По первым двум вопросам вы найдёте ответ в этой статье - PyQt5 - Урок 008. Работа с QTableWidget (Обновление урока 006) Что касается последнего вопроса, то я вам…
Nov. 9, 2019, 2:50 a.m.
Evgenij Legotskoj

Как и обещал, вы можете посмотреть новую статью QML - Урок 037. Кастомизация кнопок в QML (Обновление урока 002) . Там же найдёте ссылку на Git репозиторий. Не забудьте поставить звёз…
b
Nov. 8, 2019, 7:40 a.m.
bastonc

Приветствую. Подскажите пожалуйста пару моментов. 1. Как сделать столбец не редактируемый, а остальные ячейки остаются редактируемыми 2. Как оталвливать события двойного клика для реда…
Now discuss on the forum
KZ
Nov. 13, 2019, 10:07 a.m.
Konstantin Znamenskii

Добрый день. Я хочу узнать, как наиболее грамотно обновлять Maintenance Tool у пользователя. Я периодически выкладываю обновления пакетов самой программы, но мне также нужно вносить изменения в …
Nov. 13, 2019, 9:33 a.m.
Pavel.K

Приложение трэкинг задач. Есть вложения. Добавляем вложение и отправляем его на сервер. Для синхронного вызова методов в синхронизации, используем QEventLoop при отправке вложений. В момент син…
Nov. 13, 2019, 6:41 a.m.
Ruslan Polupan

Ну ка кбы уже лет 10 как работает :-) Просто нужна отдельная прога для добавления таких записей, отдать клиентам чтобы мозг не парили....
Nov. 13, 2019, 3:13 a.m.
Evgenij Legotskoj

Добрый день. Думаю, что да. Выбранный стиль можно подгружать при запуске программы. Во всяком случае, есть такой пример на C++ - Controls Gallery . И там есть такой код #incl…
Nov. 12, 2019, 9:35 a.m.
Evgenij Legotskoj

Добрый день. Для корректной вставки ссылок на youtube вставляйте их через пустую строку, таким образом Первая ссылка Вторая ссылкаТретья ссылка
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB