Використання механізму SRR в додатках розроблених на Qt для QNX

Фреймворк 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 part 2 iov

if ( 2 != argc )
{
printf("ERROR: This program must be started with a command-line arg, for example:\n\n");
printf(" iov_client abcdefjhi \n\n");
printf(" where 1st arg(abcdefghi) is the text to be sent to the server to be checksum'd\n");
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: %s\n", argv[1]);

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

// setup the message as a part two 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 server\n", incoming_checksum);
printf("MsgSend return status: %d\n", 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 message received
{
switch ( msg.msg_type )
{
case _IO_CONNECT: // name_open() within the client may send this
printf("Received an _IO_CONNECT msg\n");
MsgReply(rcvid, EOK, NULL, 0);
break;

case CKSUM_MSG_TYPE:
printf("Received a checksum request msg, header says the data is %d bytes\n",
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: %s\n", 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 pulse\n");
ConnectDetach(msg.pulse.scoid);
break;

case _PULSE_CODE_UNBLOCK:
printf("Received unblock pulse\n");
break;

default:
printf("unknown pulse received, code = %d\n", msg.pulse.code);
}
}
else
{
printf("Receive returned an unexpected value: %d\n", 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 part two iov, first the header then the data
iov_t siov[2]; // create a part 2 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 part two iov, first the header then the data
iov_t siov[2]; // create a part 2 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 part two iov, first the header then the data
iov_t siov[2]; // create a part 2 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 message received
{
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. Дещо в робочій системі я б зробив інакше, але щоб не перевантажувати приклади, їх код був спрощений, і на деякі моменти я закрив очі. Тим не менш, якщо у когось із читачів з'являться конкретні пропозиції щодо поліпшення коду прикладів або виправлення помилок, то я їх по можливості врахую. Прошу з цим питанням звертатися в особисті повідомлення.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.