Часть 2
Исходники для этой страницы лежат тут
Глава 4 : именованные и неименованные каналы
Неименованный канал - их еще называют программными - pipe -
предоставляет возможность однонаправленной передачи данных .
int pipe(int fd[2]);
Функция возвращает 0 в случае успеха , а также 2 дескриптора ,
первый открыт для чтения , второй для записи.
Такой канал обычно используется для связи между 2-мя процессами - родительским и дочерним .
Сначала процесс создает канал , потом форкает дочерний процесс ,
затем родительский процесс должен закрыть открытый для чтения конец канала ,
а дочерний - открытый на запись конец канала .
Например команда
who | sort | lp
работает по схеме :
Напишем программу , которая будет создавать 2 канала :
Родительский процесс будет клиентом , а дочерний - сервером .
Первый канал будет использоваться для передачи имени файла ,
второй - для передачи содержимого файла в обратном направлении .
Каждый канал проходит через ядро .
//pipe/mainpipe.c
int main(int argc, char **argv)
{
int pipe1[2], pipe2[2];
pid_t childpid;
Pipe(pipe1); /* create two pipes */
Pipe(pipe2);
if ( (childpid = Fork()) == 0) { /* child */
Close(pipe1[1]);
Close(pipe2[0]);
server(pipe1[0], pipe2[1]);
exit(0);
}
/* 4parent */
Close(pipe1[0]);
Close(pipe2[1]);
client(pipe2[0], pipe1[1]);
Waitpid(childpid, NULL, 0); /* wait for child to terminate */
exit(0);
}
//pipe/client.c
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
/* 4read pathname */
Fgets(buff, MAXLINE, stdin);
len = strlen(buff); /* fgets() guarantees null byte at end */
if (buff[len-1] == '\n')
len--; /* delete newline from fgets() */
/* 4write pathname to IPC channel */
Write(writefd, buff, len);
/* 4read from IPC, write to standard output */
while ( (n = Read(readfd, buff, MAXLINE)) > 0)
Write(STDOUT_FILENO, buff, n);
}
//pipe/server.c
void server(int readfd, int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE+1];
/* 4read pathname from IPC channel */
if ( (n = Read(readfd, buff, MAXLINE)) == 0)
err_quit("end-of-file while reading pathname");
buff[n] = '\0'; /* null terminate pathname */
if ( (fd = open(buff, O_RDONLY)) < 0) {
/* 4error: must tell client */
printf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
strerror(errno));
n = strlen(buff);
Write(writefd, buff, n);
} else {
/* 4open succeeded: copy file to IPC channel */
while ( (n = Read(fd, buff, MAXLINE)) > 0)
Write(writefd, buff, n);
Close(fd);
}
}
Программа работает следующим образом :
после запуска она ожидает ввода имени файла вручную ,
после его ввода имя файла из stdin записывается в канал ,
слиент считывает из канала имя и записывает его в stdout ,
т.е. содержимое файла выводится на экран .
Данная программа работает по схеме , в которой открыты два односторонних канала :
В следующем примере мы попробуем использовать один двухсторонний канал для двухсторонней передачи данных:
Логика программы такова , что в начале по идее родитель должен записать в канал
символ 'p', а потом прочитать символ 'c'.
//pipe/fduplex.c
int main(int argc, char **argv)
{
int fd[2], n;
char c;
pid_t childpid;
Pipe(fd); /* assumes a full-duplex pipe (e.g., SVR4) */
if ( (childpid = Fork()) == 0) { /* child */
sleep(3);
if ( (n = Read(fd[0], &c, 1)) != 1)
err_quit("child: read returned %d", n);
printf("child read %c\n", c);
Write(fd[0], "c", 1);
exit(0);
}
/* 4parent */
Write(fd[1], "p", 1);
if ( (n = Read(fd[1], &c, 1)) != 1)
err_quit("parent: read returned %d", n);
printf("parent read %c\n", c);
exit(0);
}
При запуске программы будет выдана ошибка
read error: Bad file descriptor
Все правильно : родительский процесс не может прочитать из канала , который на запись , а не на чтение,
а дочерний записать в канал , который на чтение.
Функции popen и pclose
Функция popen создает канал и запускает другой процесс , который пишет или читает данный в этот канал
FILE * popen(const char * command , const char * type)
int pclose (FILE *)
command - это внешняя команда интерпретатора.
Если type="r" , то запускаемая команда читает данные из канала ,
если "w" , то пишет в канал.
В следующем примере будет использована команда popen и утилита cat :
//pipe/mainpopen.c
int main(int argc, char **argv)
{
size_t n;
char buff[MAXLINE], command[MAXLINE];
FILE *fp;
/* 4read pathname */
Fgets(buff, MAXLINE, stdin);
n = strlen(buff); /* fgets() guarantees null byte at end */
if (buff[n-1] == '\n')
n--; /* delete newline from fgets() */
snprintf(command, sizeof(command), "cat %s", buff);
fp = Popen(command, "r");
/* 4copy from pipe to standard output */
while (Fgets(buff, MAXLINE, fp) != NULL)
Fputs(buff, stdout);
Pclose(fp);
exit(0);
}
Полный путь к файлу считывается из stdin , командная строка передается в popen ,
вывод команды cat передается в stdout .
Именованные каналы FIFO
FIFO - first in , first out . Фифо работают как очереди .
Сходство фифо с пайпами в том , что канал работает только в одну сторону .
Отличие в том , что обращаться к фифо могут разные неродственные процессы .
Фифо создаются функцией mkfifo :
int mkfifo(const char * pathname , mode_t mode)
Возвращает 0 при успехе . Pathname - путь к файлу . mode указывает разрешение надоступ.
Фифо работает только на локальном узле , в сетевом варианте он не работает.
Для фифо есть 2 ограничения : количество открытых каналов одним процессом - OPEN_MAX -
и максимальная величина буфера - PIPE_buf.
mkfifo действует как функция open , но с аргументом O_CREAT . Если фифо уже есть ,
возвращается ошибка , тогда после этого надо вызвать open .
После создания фифо его можно открыть либо с помощью open , либо fopen.
Фифо может быть открыт либо только на чтение , либо на запись.
Читать-писать можно с помощью read-write.
Напишем программу , в которой создадим 2 канала фифо .
В первый мы записываем путь к файлу , во второй мы пишем содержимое этого файла.
Сначала пользователь должен набрать полный путь к файлу , а затем
мы выводим его на экран .
//pipe/mainfifo.c
int main(int argc, char **argv)
{
int readfd, writefd;
pid_t childpid;
/* 4create two FIFOs; OK if they already exist */
if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", FIFO1);
if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST)) {
unlink(FIFO1);
err_sys("can't create %s", FIFO2);
}
if ( (childpid = Fork()) == 0) { /* child */
readfd = Open(FIFO1, O_RDONLY, 0);
writefd = Open(FIFO2, O_WRONLY, 0);
server(readfd, writefd);
exit(0);
}
/* 4parent */
writefd = Open(FIFO1, O_WRONLY, 0);
readfd = Open(FIFO2, O_RDONLY, 0);
client(readfd, writefd);
Waitpid(childpid, NULL, 0); /* wait for child to terminate */
Close(readfd);
Close(writefd);
Unlink(FIFO1);
Unlink(FIFO2);
exit(0);
}
В каталоге /tmp создаютсяя 2 файла фифо .
Родительский процесс открывает первый канал на запись , второй на чтение ,
а дочерний процесс наоборот .
В этом примере и сервер , и клиент являются родственными программами , т.е.
находятся в связи родитель-потомок . Напишем другой вариант этой программы ,
где и сервер , и клиент будут не родственными , а совершенно отдельными программами .
//pipe/server_main.c
int main(int argc, char **argv)
{
int readfd, writefd;
/* 4create two FIFOs; OK if they already exist */
if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", FIFO1);
if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST)) {
unlink(FIFO1);
err_sys("can't create %s", FIFO2);
}
readfd = Open(FIFO1, O_RDONLY, 0);
writefd = Open(FIFO2, O_WRONLY, 0);
server(readfd, writefd);
exit(0);
}
//pipe/client_main.c
int main(int argc, char **argv)
{
int readfd, writefd;
writefd = Open(FIFO1, O_WRONLY | O_NONBLOCK, 0);
readfd = Open(FIFO2, O_RDONLY, 0);
client(readfd, writefd);
Close(readfd);
Close(writefd);
Unlink(FIFO1);
Unlink(FIFO2);
exit(0);
}
Эти 2 программы нужно запустить в отдельных терминалах - сначала сервер , потом клиент .
В клиенте набираем путь к файлу и получаем от сервера его содержимое.
После получения ответа и сервер , и клиент завершают свою работу.
Напишем программу-демон , которая будет обслуживать множество запросов от разных клиентов .
Демон открывает канал фифо на чтение , а клиенты пишут в него запросы.
Данные здесь будут посылаться как от клиента демону , так и обратно .
Сервер-демон :
//fifocliserv/mainserver.c
int main(int argc, char **argv)
{
int readfifo, writefifo, dummyfd, fd;
char *ptr, buff[MAXLINE], fifoname[MAXLINE];
pid_t pid;
ssize_t n;
/* 4create server's well-known FIFO; OK if already exists */
if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", SERV_FIFO);
/* 4open server's well-known FIFO for reading and writing */
readfifo = Open(SERV_FIFO, O_RDONLY, 0);
dummyfd = Open(SERV_FIFO, O_WRONLY, 0); /* never used */
while ( (n = Readline(readfifo, buff, MAXLINE)) > 0) {
if (buff[n-1] == '\n')
n--; /* delete newline from readline() */
buff[n] = '\0'; /* null terminate pathname */
if ( (ptr = strchr(buff, ' ')) == NULL) {
err_msg("bogus request: %s", buff);
continue;
}
*ptr++ = 0; /* null terminate PID, ptr = pathname */
pid = atol(buff);
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long) pid);
if ( (writefifo = open(fifoname, O_WRONLY, 0)) < 0) {
err_msg("cannot open: %s", fifoname);
continue;
}
if ( (fd = open(ptr, O_RDONLY)) < 0) {
/* 4error: must tell client */
snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
strerror(errno));
n = strlen(ptr);
Write(writefifo, ptr, n);
Close(writefifo);
} else {
/* 4open succeeded: copy file to FIFO */
while ( (n = Read(fd, buff, MAXLINE)) > 0)
Write(writefifo, buff, n);
Close(fd);
Close(writefifo);
}
}
}
Сервер открывает свой канал дважды : сначала для чтения , потом для записи.
При запуске сервера первый вызов open с флагом O_RDONLY его блокирует до обращения клиента.
Второй вызов open с флагом O_WRONLY его не блокирует , поскольку он уже открыт для записи.
Каждый запрос от клиента представляет одну строку , в которой есть его идентификатор , пробел и
полный путь к файлу. Строка считывается функцией readline.
Затем файл открывается и его содержимое копируется в канал клиента.
Дескриптор канала клиента закрывается с помощью close для того , чтобы функция read
вернула клиенту ноль . Канал удаляется клиентом .
//fifocliserv/mainclient.c
int main(int argc, char **argv)
{
int readfifo, writefifo;
size_t len;
ssize_t n;
char *ptr, fifoname[MAXLINE], buff[MAXLINE];
pid_t pid;
/* 4create FIFO with our PID as part of name */
pid = getpid();
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long) pid);
if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", fifoname);
/* 4start buffer with pid and a blank */
snprintf(buff, sizeof(buff), "%ld ", (long) pid);
len = strlen(buff);
ptr = buff + len;
/* 4read pathname */
Fgets(ptr, MAXLINE - len, stdin);
len = strlen(buff); /* fgets() guarantees null byte at end */
/* 4open FIFO to server and write PID and pathname to FIFO */
writefifo = Open(SERV_FIFO, O_WRONLY, 0);
Write(writefifo, buff, len);
/* 4now open our FIFO; blocks until server opens for writing */
readfifo = Open(fifoname, O_RDONLY, 0);
/* 4read from IPC, write to standard output */
while ( (n = Read(readfifo, buff, MAXLINE)) > 0)
Write(STDOUT_FILENO, buff, n);
Close(readfifo);
Unlink(fifoname);
exit(0);
}
Особенность данной версии в том , что клиентов может быть много , и они могут параллельно
посылать в фифо запросы , при этом они не будут смешиваться.
Данный сервер является последовательным сервером - iterative server - т.е. в данный момент
он может обслуживать только одного клиента.
Параллельный сервер - concurrent server - это т.н. one-child-per-client -
одному клиенту - один дочерний процесс .
Сервер вызывает fork каждый раз для каждого клиентского запроса .
Вместо простой посылаемой строки мы можем сформировать боле сложную запись.
В следующем примере задаем структуру посылаемой записи
// mesg.h
#include "unpipc.h"
/* Our own "messages" to use with pipes, FIFOs, and message queues. */
/* 4want sizeof(struct mymesg) <= PIPE_BUF */
#define MAXMESGDATA (PIPE_BUF - 2*sizeof(long))
/* 4length of mesg_len and mesg_type */
#define MESGHDRSIZE (sizeof(struct mymesg) - MAXMESGDATA)
struct mymesg {
long mesg_len; /* #bytes in mesg_data, can be 0 */
long mesg_type; /* message type, must be > 0 */
char mesg_data[MAXMESGDATA];
};
ssize_t mesg_send(int, struct mymesg *);
void Mesg_send(int, struct mymesg *);
ssize_t mesg_recv(int, struct mymesg *);
ssize_t Mesg_recv(int, struct mymesg *);
Добавим 2 функции для записи и чтения таких записей :
//pipemesg/mesg_send.c
ssize_t mesg_send(int fd, struct mymesg *mptr)
{
return(write(fd, mptr, MESGHDRSIZE + mptr->mesg_len));
}
/* end mesg_send */
void Mesg_send(int fd, struct mymesg *mptr)
{
ssize_t n;
if ( (n = mesg_send(fd, mptr)) != mptr->mesg_len)
err_quit("mesg_send error");
}
Изменим функции client() и server()
//pipemesg/client_mesg.c
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
struct mymesg mesg;
/* 4read pathname */
Fgets(mesg.mesg_data, MAXMESGDATA, stdin);
len = strlen(mesg.mesg_data);
if (mesg.mesg_data[len-1] == '\n')
len--; /* delete newline from fgets() */
mesg.mesg_len = len;
mesg.mesg_type = 1;
/* 4write pathname to IPC channel */
Mesg_send(writefd, &mesg);
/* 4read from IPC, write to standard output */
while ( (n = Mesg_recv(readfd, &mesg)) > 0)
Write(STDOUT_FILENO, mesg.mesg_data, n);
}
//server.c
void server(int readfd, int writefd)
{
FILE *fp;
ssize_t n;
struct mymesg mesg;
/* 4read pathname from IPC channel */
mesg.mesg_type = 1;
if ( (n = Mesg_recv(readfd, &mesg)) == 0)
err_quit("pathname missing");
mesg.mesg_data[n] = '\0'; /* null terminate pathname */
if ( (fp = fopen(mesg.mesg_data, "r")) == NULL) {
/* 4error: must tell client */
snprintf(mesg.mesg_data + n, sizeof(mesg.mesg_data) - n,
": can't open, %s\n", strerror(errno));
mesg.mesg_len = strlen(mesg.mesg_data);
Mesg_send(writefd, &mesg);
} else {
/* 4fopen succeeded: copy file to IPC channel */
while (Fgets(mesg.mesg_data, MAXMESGDATA, fp) != NULL) {
mesg.mesg_len = strlen(mesg.mesg_data);
Mesg_send(writefd, &mesg);
}
Fclose(fp);
}
/* 4send a 0-length message to signify the end */
mesg.mesg_len = 0;
Mesg_send(writefd, &mesg);
}
Т.о. , подводя итог по фифо , можно сказать , что данные в них передаются в виде потока байтов ,
аналогично соединению TCP.
|
Владимир | Спасибо за статью. 2016-04-28 14:22:51 | oleshii | По поводу fduplex исполнение давно уже идёт не так.
.affect
parent: read returned -1
child read p
Дальше hang
2021-03-11 15:00:31 | |
|