Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Часть 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