Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Комплекс работ по QNX.doc
Скачиваний:
204
Добавлен:
30.04.2015
Размер:
382.46 Кб
Скачать

2.2.2 Текст программы

#include <stdio.h>

#include <pthread.h>

#include <sys/neutrino.h>

pthread_t thread_id1;

pthread_t thread_id2;

void * long_thread1(void *notused)

{

int n;

for(n=0;n<5;n++)

{

printf("Eto pervii potok , TID %d - N povtora %d \n", thread_id1, n );

sleep(2);

}

}

void * long_thread2(void *notused)

{

int m;

for(m=0; m<5; m++)

{

printf("Eto vtoroi potok , TID %d - N povtora %d \n", thread_id2 , m );

sleep(1);

}

}

int main(void)

{

printf("Prog threads PID %d \n",getpid());

pthread_create(&thread_id1, NULL, long_thread1, NULL);

pthread_create(&thread_id2, NULL, long_thread2, NULL);

sleep(40);

return(1);

}

2.2.3 Последовательность действий

В программе создаются и запускаются на исполнение два потока. Когда один поток приостанавливается, сразу начинает работу другой. Приостановка реализована функцией sleep(n), которая останавливает процесс на n секунд. На экране можно наблюдать, как по очереди работают два процесса.

2.2.4 Результаты

# gcc pthread.c

# `pwd`a.out

Prog threads PID 852000

Eto pervii potok , TID 0 - N povtora 0

Eto vtoroi potok , TID 0 - N povtora 0

Eto vtoroi potok , TID 3 - N povtora 1

Eto pervii potok , TID 2 - N povtora 1

Eto vtoroi potok , TID 3 - N povtora 2

Eto vtoroi potok , TID 3 - N povtora 3

Eto pervii potok , TID 2 - N povtora 2

Eto vtoroi potok , TID 3 - N povtora 4

Eto pervii potok , TID 2 - N povtora 3

Eto pervii potok , TID 2 - N povtora 4

#

2.3 Лабораторная работа №3 «Обмен сообщениями»

2.3.1 Теоретические сведения

Архитектура и структура обмена сообщениями

Три ключевые выражения:

  • «Клиент посылает (sends) сообщение серверу»;

  • «Сервер принимает (receives) сообщение от клиента»;

  • «Сервер отвечает (replies) клиенту».

Эти выражения в точности соответствуют действительным именам функций, которые используются для передачи сообщений в QNX/Neutrino.

Минимальный полезный набор функций включает в себя функции ChannelCreate(), ConnectAttach(), MsgReply(), MsgSend() и MsgRecieve().

Разобьем обсуждение на две части: отдельно обсудим функции, которые применяются на стороне клиента, и отдельно — те, что применяются на стороне сервера.

Клиент

Клиент, который желает послать запрос серверу, блокируется до тех пор, пока сервер не завершит обработку запроса. Затем, после завершения сервером обработки, запроса клиент разблокируется, чтобы принять «ответ».

Это подразумевает обеспечение двух условий: клиент должен «уметь» сначала установить соединение с сервером, а потом обмениваться с ним данными с помощью сообщений — как в одну сторону (запрос — «send»), так и в другую (ответ — «reply»).

Установление соединения

Первое, что должны сделать клиент — это установить соединение с помощью функции ConnectAttach(), описанной следующим образом:

#include <sys/neutrino.h>

int ConnectAttach

(int nd,pid_t pid, int chid, unsigned index, int flags);

Функции ConnectAttach() передаются три идентификатора;

  • nd— дескриптор узла (Node Descriptor),

  • pid— идентификатор процесса (process ID)

  • chid — идентификатор канала (channel ID).

Вместе эти три идентификатора, которые обычно записываются в виде «ND/PID/CHID», однозначно идентифицируют сервер, с которым клиент желает соединиться. Аргументы index и flags мы здесь просто проигнорируем (установим их в ноль).

Итак, предположим, что мы хотим подсоединиться к процессу, находящемуся на нашем узле и имеющему идентификатор 77, по каналу с идентификатором 1. Ниже приведен пример программы для выполнения этого:

int coid;

coid = ConnectAttach (0, 77, 1, 0, 0);

Можно видеть, что присвоением идентификатору узла (nd) нулевого значения мы сообщаем ядру о том, что мы желаем установить соединение на локальном узле.

Соединиться надо с процессом 77 и по каналу 1.

С этого момента есть идентификатор соединения — небольшое целое число, которое однозначно идентифицирует соединение моего клиента с конкретным сервером по заданному каналу.

Теперь можно применять этот идентификатор для отправки запросов серверу сколько угодно раз. Выполнив все, для чего предназначалось соединение, его можно уничтожить с помощью функции:

ConnectDetach (coid);

Передача сообщений (sending)

Передача сообщения со стороны клиента осуществляется применением функции MsgSend(). Мы рассмотрим это на примере:

#include <sys/neutrino.h>

int MsgSend (int coid,

const void *smsg, int sbytes, void *rmsg, int rbytes);

Аргументами функции MsgSend() являются:

  • идентификатор соединения с целевым сервером (coid);

  • указатель на передаваемое сообщение (smsg);

  • размер передаваемого сообщения (sbytes);

  • указатель на буфер для ответного сообщения (rmsg);

  • размер ответного сообщения (rbytes).

Передадим сообщение процессу с идентификатором 77 по каналу 1:

#include <sys/neutrino.h>

char *smsg = «Это буфер вывода»; char rmsg [200]; int coid;

// Установить соединение

coid = ConnectAttach (0, 77, 1, 0, 0);

if (coid == -1) {

fprintf (stderr, «Ошибка ConnectAttach к 0/77/1!\n»);

perror (NULL);

exit (EXIT_FAILURE);

// Послать сообщение

if(MsgSend(coid, smsg,strlen (smsg) + 1,rmsg,sizeof(rmsg)) == -1)

{ fprintf (stderr, «Ошибка MsgSendXn») ;

perror (NULL) ;

exit (EXIT_FAILURE);

if (strlen (rmsg) > 0)

{

printf («Процесс с ID 77 возвратил \«%s\»\n», rmsg);

}

Предположим, что процесс с идентификатором 77 был действительно активным сервером, ожидающим сообщение именно такого формата по каналу с идентификатором 1. После приема сообщения сервер обрабатывает его и в некоторый момент времени выдает ответ с результатами обработки. В этот момент функция MsgSend() должна возвратить ноль (0), указывая этим, что все прошло успешно. Если бы сервер послал нам в ответ какие-то данные, мы смогли бы вывести их на экран с помощью последней строки в программе.

Сервер. Создание канала

Сервер должен создать канал — то, к чему присоединялся клиент, когда вызывал функцию ConnectAttach(). Обычно сервер, однажды создав канал, приберегает его «впрок».

Канал создается с помощью функции ChannelCreate() и уничтожается с помощью функции ChannelDestroy():

#include <sys/neutrino.h>

int ChannelCreate (unsigned flags);

int ChannelDestroy (int chid);

Таким образом, для создания канала сервер должен сделать так:

int chid;

chid = ChannelCreate (0);

Теперь у нас есть канал. В этом пункте клиенты могут подсоединиться (с помощью функции ConnectAttach()) к этому каналу и начать передачу сообщений:

Рис. 3 Связь между каналом сервера и клиентским соединением

Обработка сообщений

В терминах обмена сообщениями, сервер отрабатывает схему обмена в два этапа:

  • этап «приема» (receive);

  • этап «ответа» (reply).

Рис.4 Взаимосвязь функций клиента и сервера при обмене сообщениями

Обсудим два простейших варианта соответствующих функций, MsgReceive() и MsgReply().

#include <sys/neutrino.h>

int MsgReceive (int chid,void *rmsg, int rbytes, struct _msg_info *info);

int MsgReply (int rcvid, int status, const void *msg, int nbytes);

Четыре основных элемента:

1 Клиент вызывает функцию MsgSend() и указывает ей на буфер передачи (указателем smsg и длиной sbytes). Данные передаются в буфер функции MsgReceive() на стороне сервера, по адресу rmsg и длиной rbytes. Клиент блокируется.

2 Функция MsgReceive() сервера разблокируется и возвращает идентификатор отправителя rcvid, который будет впоследствии использован для ответа. Теперь сервер может использовать полученные от клиента данные.

3 Сервер завершил обработку сообщения и теперь использует идентификатор отправителя rcvid, полученный от функции MsgReceive(), передавая его функции MsgReply(). Заметьте, что местоположение данных для передачи функции MsgReply() задается как указатель на буфер (smsg) определенного размера (sbytes). Ядро передает данные клиенту.

4 Наконец, ядро передает параметр sts, который используется функцией MsgSend() клиента как возвращаемое значение. После этого клиент разблокируется.

Для каждой буферной передачи указываются два размера (в случае запроса от клиента это sbytes на стороне клиента и rbytes на стороне сервера; в случае ответа сервера это sbytes на стороне сервера и rbytes на стороне клиента). Это сделано для того, чтобы разработчики каждого компонента смогли определить размеры своих буферов — из соображений дополнительной безопасности.