Использование механизма SRR в приложениях разработанных на Qt для QNX

в 11:23, , рубрики: QNX, qt, Программирование

Использование механизма SRR в приложениях разработанных на Qt для QNX - 1 Фреймворк Qt один из самых популярных и применяемых при разработке кроссплатформенных настольных и мобильных приложений. Эта популярность не могла рано или поздно не привести к использованию Qt в системах специального и ответственного назначения. Достаточно давно существует возможность разработки на Qt для QNX Neutrino. Библиотека Qt поддерживает платформу QNX, а среда разработки Qt Creator обеспечивает взаимодействие с системами на QNX. Однако QNX, как система в том числе и для встраиваемых решений, имеет в своём составе технологии, которые не требуются, а потому и отсутствуют в системах общего назначения. Ключевая для ОСРВ QNX функциональность, на которой построена сама система и на которую нередко опираются пользовательские задачи это передача сообщений. Об особенностях применения механизма SRR (Send/Receive/Replay), как ещё называют передачу сообщений в QNX, и о разработке двух примеров Qt-приложений — клиента и сервера — я и хотел бы рассказать сегодня.

В заметке не делается каких-то открытий, предлагается в общем-то известная информация. Тем не менее Qt это относительно новый фреймворк для разработчиков систем специального назначения, где исторически наблюдается некоторая инертность при внедрении новых технологий. Разработчики систем на QNX могут быть недостаточно знакомы с тонкостями Qt, а разработчики Qt-приложений зачастую могут не знать специфику QNX. Решение задачи использования в одном проекте и графических возможностей библиотеки Qt, и специфичных для QNX технологий может потребовать затраты усилий, особенно на первых этапах. Именно поэтому и появилась данная заметка, цель которой в одном месте собрать информацию, которая может понадобиться разработчикам при использовании Qt в QNX.

Типовой пример использования SRR в QNX

Поскольку о сообщениях QNX я уже писал раньше на Хабре, в том числе и о составных сообщениях, то будем считать, что теория в каком-то виде уже известна и можно переходить к практике. Поэтому привожу ниже исходный код приложения клиента:

qnx_client.c

////////////////////////////////////////////////////////////////////////////////
// qnx_client.c
//
// demonstrates using input/output vector (IOV) messaging
//
////////////////////////////////////////////////////////////////////////////////

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include "../iov_server.h"

int main(int argc, char* argv[])
{
    int coid; // Connection ID to server
    cksum_header_t hdr; // msg header will specify how many bytes of data will follow
    int incoming_checksum; // space for server's reply
    int status; // status return value
    iov_t siov[2]; // create a 2 part iov

    if ( 2 != argc )
    {
        printf("ERROR: This program must be started with a command-line arg, for example:nn");
        printf("   iov_client abcdefjhi    nn");
        printf(" where 1st arg(abcdefghi) is the text to be sent to the server to be checksum'dn");
        exit(EXIT_FAILURE);
    }

    // locate the server
    coid = name_open(CKSUM_SERVER_NAME, 0);
    if ( -1 == coid ) // was there an error attaching to server?
    {
        perror("name_open"); // look up error code and print
        exit(EXIT_FAILURE);
    }

    printf("Sending the following text to checksum server: %sn", argv[1]);

    // build the header
    hdr.msg_type = CKSUM_MSG_TYPE;
    hdr.data_size = strlen(argv[1]) + 1;

    // setup the message as a two part iov, first the header then the data
    SETIOV(&siov[0], &hdr, sizeof hdr);
    SETIOV(&siov[1], argv[1], hdr.data_size);

    // and send the message off to the server
    status = MsgSendvs(coid, siov, 2, &incoming_checksum, sizeof incoming_checksum);
    if ( -1 == status ) // was there an error sending to server?
    {
        perror("MsgSend");
        exit(EXIT_FAILURE);
    }

    printf("received checksum=%d from servern", incoming_checksum);
    printf("MsgSend return status: %dn", status);

    return EXIT_SUCCESS;
}

Программа довольно тривиальная, я просто взял пример из курсов по QNX и немного его «причесал». Это консольное приложение, которое принимает на вход строку, пересылает её на сервер и выводит на экран ответ сервера — контрольную сумму переданной ранее строки. Обратите внимание, что в примере используются составные сообщения — макрос SETIOV() и функция MsgSendvs() вместо MsgSend() — что позволяет избежать лишнего копирования. Самое интересное здесь это использование функции name_open() для поиска сервера и установления соединения с ним.

Теперь самое время посмотреть исходный код сервера:

qnx_server.c

////////////////////////////////////////////////////////////////////////////////
// qnx_server.c
//
// demonstrates using input/output vector (IOV) messaging
//
////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>

#include "../iov_server.h"

typedef union
{
    uint16_t msg_type;
    struct _pulse pulse;
    cksum_header_t cksum_hdr;
}
msg_buf_t;

int calculate_checksum(char *text)
{
    char *c;
    int cksum = 0;

    for ( c = text; *c; c++ )
        cksum += *c;

    sleep(10); // emulate calculation delay

    return cksum;
}

int main(void)
{
    int rcvid;
    name_attach_t* attach;
    msg_buf_t msg;
    int status;
    int checksum;
    char* data;

    attach = name_attach(NULL, CKSUM_SERVER_NAME, 0);
    if ( NULL == attach )
    {
        perror("name_attach"); // look up the errno code and print
        exit(EXIT_FAILURE);
    }

    while ( 1 )
    {
        printf("Waiting for a message...n");
        rcvid = MsgReceive(attach->chid, &msg, sizeof(msg), NULL);

        if ( -1 == rcvid ) // Was there an error receiving msg?
        {
            perror("MsgReceive"); // look up errno code and print
            break;
        }
        else if ( rcvid > 0 ) // Process received message
        {
            switch ( msg.msg_type )
            {
            case _IO_CONNECT: // name_open() within the client may send this
                printf("Received an _IO_CONNECT msgn");
                MsgReply(rcvid, EOK, NULL, 0);
                break;

            case CKSUM_MSG_TYPE:
                printf("Received a checksum request msg, header says the data is %d bytesn",
                        msg.cksum_hdr.data_size);
                data = malloc(msg.cksum_hdr.data_size);
                if ( NULL == data )
                {
                    MsgError(rcvid, ENOMEM );
                }
                else
                {
                    status = MsgRead(rcvid, data, msg.cksum_hdr.data_size, sizeof(cksum_header_t));
                    printf("Received the following text from client: %sn", data);
                    checksum = calculate_checksum(data);
                    free(data);
                    status = MsgReply(rcvid, EOK, &checksum, sizeof(checksum));
                    if (-1 == status)
                    {
                        perror("MsgReply");
                    }
                }
                break;

            default:
                MsgError(rcvid, ENOSYS);
                break;
            }
        }
        else if ( 0 == rcvid ) // Process received pulse
        {
            switch ( msg.pulse.code )
            {
            case _PULSE_CODE_DISCONNECT:
                printf("Received disconnect pulsen");
                ConnectDetach(msg.pulse.scoid);
                break;

            case _PULSE_CODE_UNBLOCK:
                printf("Received unblock pulsen");
                break;

            default:
                printf("unknown pulse received, code = %dn", msg.pulse.code);
            }
        }
        else
        {
            printf("Receive returned an unexpected value: %dn", rcvid);
        }
    }

    return 0;
}

Код сервера немного поинтереснее. Сервер принимает и обрабатывает сообщения от клиента. На самом деле в этом примере реализовано только одно сообщение — CKSUM_MSG_TYPE — подсчёт контрольной суммы переданных данных. Другое сообщение — _IO_CONNECT — посылается серверу, когда клиент вызывает функцию name_open(). Помимо сообщений сервер умеет обрабатывать служебные импульсы _PULSE_CODE_DISCONNECT и _PULSE_CODE_UNBLOCK. В этом простом примере обработка служебных сообщений в принципе не требуется.

Алгоритм работы сервера достаточно прост. Сначала выполняется инициализация, в данном случае это объявление имени при помощи функции name_attach(), после чего клиенты могут найти сервер. Дальнейшая работа сервера представляет собой «вечный цикл». В самом начале цикла сервер блокируется на вызове MsgReceive() ожидая сообщений от клиента. По приходу сообщения или пульса ядро QNX разблокирует сервер, который начнёт обработку принятого сообщения. В примере используется объединение (union) msg_buf_t для получения сообщения. Это обычная практика для QNX, когда возможные типы сообщений (а сообщение обычно описываются структурой языка Си) объединяются в union. Наше полезное сообщение CKSUM_MSG_TYPE мы получаем при помощи MsgReceive() не целиком, принимается только заголовок, в котором указан размер данных. Данные дочитываются при помощи функции MsgRead(). Ответ клиенту отправляется при помощи функции MsgReply(), а в случае ошибки — MsgError(). Импульсы не требуют ответа.

Для полноты картины привожу текст заголовочного файла. Этот заголовочный файл используется и сервером, и клиентом, и, как мы увидим дальше, Qt версии нашего сервера и клиента тоже используют этот файл. Он предназначен для подключения необходимых заголовочных файлов и объявления структуры заголовка сообщения CKSUM_MSG_TYPE.

iov_server.h

#ifndef _IOV_SERVER_H_
#define _IOV_SERVER_H_

#include <sys/dispatch.h>
#include <sys/neutrino.h>
#include <sys/iomsg.h>
#include <errno.h>

#define CKSUM_SERVER_NAME "cksum"
#define CKSUM_MSG_TYPE (_IO_MAX + 2)

typedef struct
{
    uint16_t msg_type;
    unsigned data_size;
}
cksum_header_t;

// checksum reply is an int

#endif //_IOV_SERVER_H_

На скриншоте ниже представлен пример работы консольных версий сервера и клиента:

image

Сначала запускается сервер, который ожидает сообщений от клиента. Потом запускается клиент, в качестве аргумента ему указывается строка «Hello, QNX!» Во время работы клиент и сервер выводят диагностические сообщения в консоль, по которым можно судить о работе программ. Программы работают как ожидалось, можно приступать к написанию графических вариантов на Qt. Сначала адаптируем клиентское приложение.

Пример клиента на Qt

Разрабатывать Qt приложения будем в Qt Creator. В этом случае сам процесс разработки приложений для QNX в общем не отличается от разработки приложений для других ОС. Ведь Qt это кроссплатформенный фреймворк. Требуется только создать комплект (Kit) для QNX в Qt Creator.

Создаём новый проект приложения типа Qt Widgets Application. При этом Qt Creator подготовит все необходимые файлы, в том числе и форму для окна. Для клиента форму окна приводим к следующему виду:

image

На форме размещены поле для ввода текста (text), который передаётся серверу, кнопки подключения (connect) и отключения (disconnect) от сервера, кнопка (calc) отправки сообщения серверу, поле ввода (cksum), которое используется для вывода контрольной суммы полученной от сервера, и область вывода диагностических сообщений (status).

Осталось только написать код для работы с сервером и обработки логики графической формы. В результате получаем следующий класс MainWindow:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "../iov_server.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void log(QString msg);
    void showCrc(QString crc);

private slots:
    void qnxConnect();
    void qnxDisconnect();
    void calculate();

private:
    Ui::MainWindow *ui;
    int coid;       // Connection ID to server
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDateTime>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    coid = -1;

    connect(ui->connect, SIGNAL(clicked()), this, SLOT(qnxConnect()));
    connect(ui->disconnect, SIGNAL(clicked()), this, SLOT(qnxDisconnect()));
    connect(ui->calc, SIGNAL(clicked()), this, SLOT(calculate()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::qnxConnect()
{
    // check if we already connected
    if ( coid >= 0 )
        return;

    // connect to the server
    coid = name_open(CKSUM_SERVER_NAME, 0);
    if ( coid < 0 )
    {
        log(QString(tr("Can't connect to server: ")).append(strerror(errno)));
        return;
    }

    log(tr("Connected to server"));

    ui->connect->setEnabled(false);
    ui->disconnect->setEnabled(true);
    ui->calc->setEnabled(true);
}

void MainWindow::qnxDisconnect()
{
    // check if we already disconnected
    if ( coid < 0 )
        return;

    // disconnect from the server
    int status = name_close(coid);
    if ( status < 0 )
    {
        log(QString(tr("Can't disconnect from server: ")).append(strerror(errno)));
        return;
    }

    log(tr("Disconnected from server"));

    coid = -1;
    ui->calc->setEnabled(false);
    ui->disconnect->setEnabled(false);
    ui->connect->setEnabled(true);
}

void MainWindow::calculate()
{
    ui->disconnect->setEnabled(false);
    ui->calc->setEnabled(false);

    // get the data
    QString data = ui->text->toPlainText();

    log(QString(tr("Sending the following text to checksum server: %1")).arg(data));

    // build the header
    cksum_header_t hdr; // msg header will specify how many bytes of data will follow
    hdr.msg_type = CKSUM_MSG_TYPE;
    hdr.data_size = data.length() + 1;

    // setup the message as a two part iov, first the header then the data
    iov_t siov[2]; // create a 2 part iov
    SETIOV(&siov[0], &hdr, sizeof(hdr));
    SETIOV(&siov[1], data.toAscii().data(), hdr.data_size);

    // and send the message off to the server
    int incoming_checksum; // space for server's reply
    int status = MsgSendvs(coid, siov, 2, &incoming_checksum, sizeof(incoming_checksum));
    if ( status < 0 )
    {
        log(QString(tr("Can't send message to server: ")).append(strerror(errno)));
        return;
    }

    log(QString(tr("MsgSend return status: %1")).arg(status));

    showCrc(QString::number(incoming_checksum));
}

void MainWindow::showCrc(QString crc)
{
    ui->cksum->setText(crc);
    ui->disconnect->setEnabled(true);
    ui->calc->setEnabled(true);
}

void MainWindow::log(QString msg)
{
    ui->status->append(msg.prepend(QDateTime::currentDateTime().toString("hh:mm:ss ")));
}

Файл main.cpp остался таким, каким его создал Qt Creator, поэтому приводить его содержимое не стану.

Итак, посмотрим, что же мы тут понаделали. Сначала, как и в прошлом примере, запускаем сервер. Затем запускаем Qt версию клиента. Нажимаем кнопку Connect, обращаем внимание, что сервер получает уведомление о подключении клиента в виде сообщения _IO_CONNECT. Затем пишем текст «Hello, QNX!» и нажимаем кнопку Calc, что приводит к отправке сообщения на на сервер. Событие отправки также отображается на экране. Полученная от сервера контрольная сумма отображается в окне клиента.

image

Пример работает, сообщения отправляются и принимаются, проблем не замечено. Но… Но я то знаю, что всё не должно так замечательно работать. Дело в том, что после вызова MsgSendvs() клиент блокируется как минимум до вызова сервером функции MsgReceive() (моет быть больше, если в системе есть более высокоприоритетные процессы). Для иллюстрации этой особенности в коде функции calculate_checksum() сервера добавлена задержка в виде вызова sleep(10). С такой задержкой в сервере клиент блокируется на 10 секунд, что приводит к заметному «замерзанию» графического окна сервера.

В некоторых случаях, особенно когда сервер сразу отвечает клиенту (т.е. информация всегда доступна серверу, а не приходит извне), блокировка не является проблемой. В остальных случаях, пользователь может начать нервничать, когда у него «замерзает» графический интерфейс. Я бы не стал рисковать и выпускать программы, которые могут нервировать заказчиков. С «замёрзшим» интерфейсом клиент не сможет продолжить работу с приложением после отправки сообщения до получения ответа от сервера, а ведь в реальной жизни приложение может взаимодействовать с несколькими серверами и предоставлять другие функции управления. Нет, текущий вариант клиентского приложения нас не может устроить. Поэтому давайте посмотрим на правильную реализацию клиента.

Правильный пример клиента на Qt

Как же можно решить проблему с блокировкой клиента? Клиент не может не блокироваться на MsgSendvs(). Однако вполне допустимо выделить работу с сообщениями в отдельный поток. В этом случае один поток обслуживает графический интерфейс, другой — реализует механизм SRR. Для работы с потоками в Qt будем использовать класс QThread. Реализацию SRR вынесем в отдельный класс Sender. Связь между классами Sender (работа с сообщениями) и MainWindow (графический интерфейс) организуем через сигналы и слоты Qt.

Посмотрим, как изменился класс MainWindow с учётом вышесказанного. Для наглядности старый код также оставлен, и добавлен макрос SENDER_THREAD, при объявлении которого работа с сообщениями выполняется в отдельном потоке Qt.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "../iov_server.h"

#define SENDER_THREAD

#ifdef SENDER_THREAD
#include <QThread>
#endif

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

#ifdef SENDER_THREAD
signals:
    void calcCrc(int coid, QString data);
#endif

public slots:
    void log(QString msg);
    void showCrc(QString crc);

private slots:
    void qnxConnect();
    void qnxDisconnect();
    void calculate();

private:
    Ui::MainWindow *ui;
    int coid;       // Connection ID to server
#ifdef SENDER_THREAD
    QThread senderThread;
#endif
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#ifdef SENDER_THREAD
#include "sender.h"
#endif

#include <QDateTime>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    coid = -1;

    connect(ui->connect, SIGNAL(clicked()), this, SLOT(qnxConnect()));
    connect(ui->disconnect, SIGNAL(clicked()), this, SLOT(qnxDisconnect()));
    connect(ui->calc, SIGNAL(clicked()), this, SLOT(calculate()));

#ifdef SENDER_THREAD
    Sender *sender = new Sender;
    sender->moveToThread(&senderThread);
    connect(&senderThread, SIGNAL(finished()), sender, SLOT(deleteLater()));
    connect(this, SIGNAL(calcCrc(int, QString)), sender, SLOT(send(int, QString)));
    connect(sender, SIGNAL(result(QString)), this, SLOT(showCrc(QString)));
    connect(sender, SIGNAL(log(QString)), this, SLOT(log(QString)));
    senderThread.start();
#endif
}

MainWindow::~MainWindow()
{
#ifdef SENDER_THREAD
    senderThread.quit();
    senderThread.wait();
#endif

    delete ui;
}

void MainWindow::qnxConnect()
{
    // check if we already connected
    if ( coid >= 0 )
        return;

    // connect to the server
    coid = name_open(CKSUM_SERVER_NAME, 0);
    if ( coid < 0 )
    {
        log(QString(tr("Can't connect to server: ")).append(strerror(errno)));
        return;
    }

    log(tr("Connected to server"));

    ui->connect->setEnabled(false);
    ui->disconnect->setEnabled(true);
    ui->calc->setEnabled(true);
}

void MainWindow::qnxDisconnect()
{
    // check if we already disconnected
    if ( coid < 0 )
        return;

    // disconnect from the server
    int status = name_close(coid);
    if ( status < 0 )
    {
        log(QString(tr("Can't disconnect from server: ")).append(strerror(errno)));
        return;
    }

    log(tr("Disconnected from server"));

    coid = -1;
    ui->calc->setEnabled(false);
    ui->disconnect->setEnabled(false);
    ui->connect->setEnabled(true);
}

void MainWindow::calculate()
{
    ui->disconnect->setEnabled(false);
    ui->calc->setEnabled(false);

    // get the data
    QString data = ui->text->toPlainText();

#ifdef SENDER_THREAD
    emit calcCrc(coid, data);
#else
    log(QString(tr("Sending the following text to checksum server: %1")).arg(data));

    // build the header
    cksum_header_t hdr; // msg header will specify how many bytes of data will follow
    hdr.msg_type = CKSUM_MSG_TYPE;
    hdr.data_size = data.length() + 1;

    // setup the message as a two part iov, first the header then the data
    iov_t siov[2]; // create a 2 part iov
    SETIOV(&siov[0], &hdr, sizeof(hdr));
    SETIOV(&siov[1], data.toAscii().data(), hdr.data_size);

    // and send the message off to the server
    int incoming_checksum; // space for server's reply
    int status = MsgSendvs(coid, siov, 2, &incoming_checksum, sizeof(incoming_checksum));
    if ( status < 0 )
    {
        log(QString(tr("Can't send message to server: ")).append(strerror(errno)));
        return;
    }

    log(QString(tr("MsgSend return status: %1")).arg(status));

    showCrc(QString::number(incoming_checksum));
#endif
}

void MainWindow::showCrc(QString crc)
{
    ui->cksum->setText(crc);
    ui->disconnect->setEnabled(true);
    ui->calc->setEnabled(true);
}

void MainWindow::log(QString msg)
{
    ui->status->append(msg.prepend(QDateTime::currentDateTime().toString("hh:mm:ss ")));
}

В объявлении класса MainWindow появился сигнал calcCrc(), при помощи которого сообщаемся экземпляру класса Sender кому и какое сообщение требуется отправить.

Большие изменения претерпела реализация класса MainWindow. В конструкторе появился блок кода, в котором создаётся экземпляр класса Sender и при помощи метода moveToThread() выделяется в отдельный поток. В деструкторе ожидаем завершение потока (методы quit() и wait() класса QThread). Весь код метода calculate() перенесён в класс Sender и заменён на генерацию сигнала calcCrc().

После доработки MainWindow, можно перейти к классу Sender.

sender.h

#ifndef SENDER_H
#define SENDER_H

#include <QObject>

#include "../iov_server.h"

class Sender : public QObject
{
    Q_OBJECT
public:
    Sender() {}
    virtual ~Sender() {}

signals:
    void result(QString data);
    void log(QString err);

public slots:
    void send(int coid, QString data);
};

#endif // SENDER_H

sender.cpp

#include "sender.h"

void Sender::send(int coid, QString data)
{
    emit log(QString(tr("Sending the following text to checksum server: %1")).arg(data));

    // build the header
    cksum_header_t hdr; // msg header will specify how many bytes of data will follow
    hdr.msg_type = CKSUM_MSG_TYPE;
    hdr.data_size = data.length() + 1;

    // setup the message as a two part iov, first the header then the data
    iov_t siov[2]; // create a 2 part iov
    SETIOV(&siov[0], &hdr, sizeof(hdr));
    SETIOV(&siov[1], data.toAscii().data(), hdr.data_size);

    // and send the message off to the server
    int incoming_checksum; // space for server's reply
    int status = MsgSendvs(coid, siov, 2, &incoming_checksum, sizeof(incoming_checksum));
    if ( status < 0 )
    {
        emit log(QString(tr("Can't send message to server: ")).append(strerror(errno)));
        return;
    }

    emit log(QString(tr("MsgSend return status: %1")).arg(status));
    emit result(QString::number(incoming_checksum));
}

По сути это код, который был раньше в методе calculate() класса MainWindow. Вывод ошибок и результата в графическое окно приложения клиента реализован при помощи сигналов log() и result().

С такими доработками графический интерфейс клиента не «замерзает», т.е. пока экземпляр класса Sender блокируется на 10 секунд в отдельном потоке, мы можем управлять графическим окном. Правда в представленном примере управлять особо нечем, но возможность то есть.

Пример сервера на Qt

Поэксперементировав с клиентом будем сразу разрабатывать сервер правильно. Поскольку вызов MsgReceive() приводит к блокировке, то вынесем функциональность сервера в класс Server, который будет работать в отдельном потоке. Принципы те же, что и в клиенте. Форму главного окна по-честному «скомуниздим» у клиента — скопируем mainwindow.ui, откроем в редакторе, удалим ненужные кнопки и преобразуем класс QPlainTextEdit (объект text) в QTextBrowser (редактор это позволяет).

image

Объявление и реализация класса MainWindow сервера приведены ниже:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

#include "../iov_server.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
    void startServer(name_attach_t* attach);

public slots:
    void log(QString msg);
    void showCrc(QString crc);
    void showText(QString txt);
    void stopServer(void);

private:
    Ui::MainWindow *ui;
    name_attach_t* attach;
    QThread serverThread;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "server.h"

#include <QDateTime>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    Server *server = new Server;
    server->moveToThread(&serverThread);
    connect(&serverThread, SIGNAL(finished()), server, SLOT(deleteLater()));
    connect(this, SIGNAL(startServer(name_attach_t*)), server, SLOT(process(name_attach_t*)));
    connect(server, SIGNAL(result(QString)), this, SLOT(showCrc(QString)));
    connect(server, SIGNAL(text(QString)), this, SLOT(showText(QString)));
    connect(server, SIGNAL(log(QString)), this, SLOT(log(QString)));

    attach = name_attach(NULL, CKSUM_SERVER_NAME, 0);
    if ( NULL == attach )
    {
        log(QString(tr("Can't attach name: %1")).arg(strerror(errno)));
    }
    else
    {
        serverThread.start();
        emit startServer(attach);
    }
}

MainWindow::~MainWindow()
{
    stopServer();
    serverThread.quit();
    serverThread.wait();

    delete ui;
}

void MainWindow::showText(QString txt)
{
    ui->text->setText(txt);
}

void MainWindow::showCrc(QString crc)
{
    ui->cksum->setText(crc);
}

void MainWindow::log(QString msg)
{
    ui->status->append(msg.prepend(QDateTime::currentDateTime().toString("hh:mm:ss ")));
}

void MainWindow::stopServer()
{
    if ( NULL != attach )
    {
        name_detach(attach, 0);
    }
}

Для работы сервера создаём имя в MainWindow используя функцию name_attach(). При помощи сигнала передаём структуру attach в поток сервера, тем самым запуская его. Для остановки сервера удаляем имя — функция name_detach(). В остальном очень похоже на то, что было сделано в клиенте. Посмотрим на код:

server.h

#ifndef SERVER_H
#define SERVER_H

#include <QObject>

#include "../iov_server.h"

typedef union
{
    uint16_t msg_type;
    struct _pulse pulse;
    cksum_header_t cksum_hdr;
}
msg_buf_t;

class Server : public QObject
{
    Q_OBJECT
public:
    Server() {}
    virtual ~Server() {}

signals:
    void result(QString data);
    void text(QString text);
    void log(QString err);

public slots:
    void process(name_attach_t* attach);

private:
    int calculate_checksum(char *text);
};

#endif // SERVER_H

server.cpp

#include "server.h"

int Server::calculate_checksum(char *text)
{
    int cksum = 0;

    for ( char *c = text; *c; c++ )
        cksum += *c;

    sleep(10); // emulate calculation delay

    return cksum;
}

void Server::process(name_attach_t* attach)
{
    if ( NULL == attach )
    {
        return;
    }

    int rcvid;
    msg_buf_t msg;
    char *data;

    while ( 1 )
    {
        emit log(tr("Waiting for a message..."));
        rcvid = MsgReceive(attach->chid, &msg, sizeof(msg), NULL);

        if ( -1 == rcvid ) // Was there an error receiving msg?
        {
            emit log(QString(tr("MsgReceive: %1")).arg(strerror(errno))); // look up errno code and print
            break;
        }
        else if ( rcvid > 0 ) // Process received message
        {
            switch ( msg.msg_type )
            {
            case _IO_CONNECT: // name_open() within the client may send this
                emit log(tr("Received an _IO_CONNECT msg"));
                MsgReply(rcvid, EOK, NULL, 0);
                break;

            case CKSUM_MSG_TYPE:
                emit log(QString(tr("Received a checksum request msg, header says the data is %1 bytes")).arg(msg.cksum_hdr.data_size));
                data = (char *)malloc(msg.cksum_hdr.data_size);
                if ( NULL == data )
                {
                    MsgError(rcvid, ENOMEM );
                }
                else
                {
                    int status = MsgRead(rcvid, data, msg.cksum_hdr.data_size, sizeof(cksum_header_t));
                    emit text(data);
                    int checksum = calculate_checksum(data);
                    emit result(QString::number(checksum));
                    free(data);
                    status = MsgReply(rcvid, EOK, &checksum, sizeof(checksum));
                    if (-1 == status)
                    {
                        emit log(tr("MsgReply"));
                    }
                }
                break;

            default:
                MsgError(rcvid, ENOSYS);
                break;
            }
        }
        else if ( 0 == rcvid ) // Process received pulse
        {
            switch ( msg.pulse.code )
            {
            case _PULSE_CODE_DISCONNECT:
                emit log(tr("Received disconnect pulse"));
                ConnectDetach(msg.pulse.scoid);
                break;

            case _PULSE_CODE_UNBLOCK:
                emit log(tr("Received unblock pulse"));
                break;

            default:
                emit log(QString(tr("unknown pulse received, code = %1")).arg(msg.pulse.code));

            }
        }
        else
        {
            emit log(QString(tr("Receive returned an unexpected value: %1")).arg(rcvid));
        }
    }
}

Класс Server реализует две функции консольного сервера (qnx_server), изменился только вывод сообщений (при помощи сигналов/слотов Qt) и регистрация имени выполняется в классе MainWindow. Работа графических вариантов клиента и сервера представлена на следующем скриншоте:

image

Сервер получился без элементов управления. Нет ни кнопок, ни полей ввода. Графическое окно сервера служит только для контроля за его работой.

Заключение

Вот и подошла к концу эта заметка. Был рассмотрен код нескольких примеров, стало понятно, как правильно использовать механизм сообщений QNX в приложениях на Qt. Для тех же, кто захочет воспроизвести примеры я опубликовал их на Bitbucket. Предвидя возможные замечания по коду, прошу учесть, что это только примеры, которые иллюстрируют работу SRR в Qt. Кое-что в рабочей системе я бы сделал иначе, но чтобы не перегружать примеры, их код был упрощён, и на некоторые моменты я закрыл глаза. Тем не менее, если у кого-то из читателей появятся конкретные предложения по улучшению кода примеров или исправлению ошибок, то я их по возможности учту. Прошу по этим вопросам обращаться в личные сообщения.

Автор: ob1

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js