Именованные каналы в Unix

Именованные каналы в Unix называются также каналами FIFO (First Input - First Output). Для их создания предназначена системная функция mkfifo, имеющая прототип

int mkfifo(const char *pathname, mode_t mode),

где аргумент pathname задает символьное обозначение именованного канала, а аргумент mode – режимы доступа к этому каналу, аналогичные правам доступа для файла. Использование этого вызова требует указания заголовочных файлов sys/types.h и sys/stat.h .

Функция mkfifo возвращает значение хэндла для созданного именованного канала или значение -1 при невозможности его создать.

После создания именованного канала его необходимо открыть. В отличие от других ОС здесь открытие именованного канала требуется и для того процесса, который создал этот канал.

Особенностью открытия именованного канала для записи путем использования режима открытия O_WRONLY является блокировка процесса на вызове открытия до тех пор, пока какой-то другой процесс не откроет этот же канал для чтения. (Если на момент выполнения такой функции открытия по записи другой процесс уже открыл его для чтения, то никакой блокировки не возникает.)

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

open(имя_канала , O_WRONLY | O_NONBLOCK).

Вызов последней функции вернет значение -1, если указанный канал не открыт еще для чтения.

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

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

Следующий пример демонстрирует рассмотренные средства. Программа для серверной стороны этого примера приведена в листинге 11.5.1.

 

#include <stdio.h>

#include <sys/fcntl.h>

#include <errno.h>

 

int main()

{int hpipeto, hpipefrom;

char buffer[512];

int actread, actwrite, rc;

 

printf("Begin\n"); sleep(2);

rc=mkfifo("/tmp/PipeToClient",0646);

if (rc= = -1 && errno!=EEXIST)

{perror("ErrorCreatePipe:"); exit(1);}

rc=mkfifo("/tmp/PipeFromClient",0646);

if (rc= = -1 && errno!=EEXIST)

{perror("ErrorCreatePipe:"); exit(1);}

printf("Waiting for connect...\n");

hpipeto=open("/tmp/PipeToClient",O_WRONLY);

if (hpipeto= =-1) {perror("Error PipeToClient"); exit(1);}

hpipefrom=open("/tmp/PipeFromClient",O_RDONLY);

if (hpipefrom= =-1) {perror("Error PipeFromClient"); exit(1);}

printf("Connected! Waiting for data...\n");

while (1)

{actread=read(hpipefrom,buffer, 512);

if (actread)

{if(!write(hpipeto, buffer, strlen(buffer)+1)) break;

printf("Received data: [%s]\n",buffer);

if (!strcmp(buffer,"exit")) break;

}

else {perror("Error ReadPipe:"); getchar(); break;}

}

close(hpipeto);

close(hpipefrom);

return 0;

}

Листинг 11.5.1. Программа для серверной стороны именованного канала

 

В этой программе системными вызовами создаются два именованных канала. Оба они создаются в каталоге Linux, предназначенном для временных файлов и имеющем наименование /tmp. В качестве собственных имен выбраны названия PipeToClient и PipeFromClient. Эти каналы создаются с правами как на чтение, так и на запись для всевозможных программ, не принадлежащих пользователю, создавшему указанные каналы.

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

Затем канал PipeToClient открывается для записи, а канал PipeFromClient – для чтения. При удачных открытиях запускается цикл, в котором из канала PipeFromClient читается сообщение размером до 512 байтов. Если удается прочитать сообщение (а не признак конца передачи – конца файла, определяемый нулевой длиной полученного сообщения), то оно пересылается обратно клиенту через канал PipeToClient, причем при невозможности переслать через канал (возвращаемого числа пересланных байтов, равное нулю), цикл завершается оператором break. Кроме обратной пересылки, полученное сервером сообщение выводится на экран оператором printf. При обнаружении, что полученное клиентом сообщение есть команда, задаваемая текстом exit, цикл разрывается, выполняется закрытие серверных сторон обоих используемых каналов, и программа завершается.

Программа для клиентской стороны этого примера приведена в листинге 11.5.2.

 

#include <stdio.h>

#include <string.h>

#include <sys/fcntl.h>

int main()

{int hpipein,hpipeout;

char buffer[512],buffer2[512];

int actread, actwrite,rc, action;

hpipein=open("/tmp/PipeToClient",O_RDONLY);

if (hpipein= =-1) {perror("ErrorOpenPipe:"); exit(1);}

hpipeout=open("/tmp/PipeFromClient",O_WRONLY);

if (hpipeout= =-1) {perror("ErrorOpenPipe:"); exit(1);}

printf("Connect with Server!. Type 'exit' for terminate.\n");

while (1)

{printf("Input data, please...>\n");

gets(buffer);

if (!write(hpipeout,buffer, strlen(buffer)+1)) break;

if((actread=read(hpipein, buffer2, 512)))

printf("Received back data: [%s]\n",buffer2);

else {perror("Error ReadPipe:"); break;}

if (!strcmp(buffer,"exit")) break;

}

close(hpipein); close(hpipeout);

return 0;

}

Листинг 11.5.2. Программа для клиентской стороны именованного канала

 

В этой программе открываются именованные каналы, имеющие имена PipeToClient и PipeFromClient в каталоге временных файлов /tmp. Причем открываются соответственно для чтения и записи. После удачного открытия и выдачи информативного сообщения пользователю о подсоединении к серверу запускается цикл посылки и получения сообщений. В цикле от пользователя запрашивается ввод данных и полученные от функции gets() данные посылаются через канал с именем PipeFromClient (используя полученный при открытии хэндл hpipeout). При невозможности переслать данные (возвращаемое нулевое число пересланных байтов), цикл разрывается оператором break. Иначе в дополнительный буфер buffer2 из канала с именем PipeToClient (с помощью хэндла hpipein) читаются данные от сервера. Они выдаются на экран с примечанием, что получены обратно. Текстовая команда exit, введенная в качестве запрашиваемых данных разрывает цикл, после которого оба канала со стороны клиента закрываются.