Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Books
  Краткое описание
 Linux
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 A.Rubini-J.Corbet 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 K.A.Robbins 
 С Бекман 
 Р Стивенс 
 Ethereal 
 Cluster 
 Languages
 C
 Perl
 M.Pilgrim 
 А.Фролов 
 Mendel Cooper 
 М Перри 
 Kernel
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 Максвелл 
 G. Kroah-Hartman 
 B. Hansen 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...5169 
 Trees...938 
 Максвелл 3...870 
 Go Web ...821 
 William Gropp...802 
 Ethreal 3...786 
 Gary V.Vaughan-> Libtool...772 
 Ethreal 4...770 
 Rodriguez 6...763 
 Ext4 FS...754 
 Steve Pate 1...754 
 Clickhouse...753 
 Ethreal 1...741 
 Secure Programming for Li...731 
 C++ Patterns 3...716 
 Ulrich Drepper...696 
 Assembler...694 
 DevFS...660 
 Стивенс 9...649 
 MySQL & PosgreSQL...630 
 
  01.01.2024 : 3621733 посещений 

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