4.1 管道
管道是由pipe函数创建的,提供一个单向数据流。
头文件 | #include <unistd.h> |
函数原型 | int pipe(int fd[2]); |
返回值 | 成功则为0,出错则为-1 |
函数功能 | 该函数返回两个文件描述符:fd[0]和fd[1]。fd[0]用来读操作,fd[1]用来写操作 |
说明 | 管道只能用于有亲缘关系进程间通讯。要实现非亲缘关系进程间通讯用有名管道FIFO |
4.2 管道实现半双工通讯
实现的步骤:
(1)创建管道(fd[0]和fd[1])
(2)fork
(3)父进程关闭管道的读端(fd[0])
(4)子进程关闭管道的写端(fd[1])
(5)父进程往管道的写端(fd[1])写入数据
(6)子进程从管道的读端(fd[0])读出数据
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_MAX_LEN 10 int main() { pid_t childpid = ; ] = {}; ] = {}; // 创建管道 ) { printf("pipe error"); ; } // 创建进程 childpid = fork(); ) { printf("fork error\n"); ; } // 子进程 ) { close(pipefd[]); read(pipefd[], buf, BUF_MAX_LEN); printf("child:%s\n", buf); exit(); } // 父进程 close(pipefd[]); write(pipefd[], "hello", sizeof("hello")); printf("parent:%s\n", "hello"); waitpid(childpid, NULL, ); ; }
4.3 管道实现全双工通讯
实现的步骤:
(1)创建管道1(fd1[0]和fd1[1])、管道2(fd2[0]和fd2[1])
(2)fork
(3)父进程关闭管道1的读端(fd1[0])、关闭管道2的写端(fd2[1])
(4)子进程关闭管道1的写端(fd1[1])、关闭管道2的读端(fd2[0])
(5)父、子进程间通讯
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_MAX_LEN 10 int main() { pid_t childpid = ; ] = {}; ] = {}; ] = {}; // 创建管道fd1 ) { printf("pipe error"); ; } // 创建管道fd2 ) { printf("pipe error"); ; } // 创建进程 childpid = fork(); ) { printf("fork error\n"); ; } // 子进程 ) { sleep(); close(pipefd1[]); close(pipefd2[]); read(pipefd1[], buf, BUF_MAX_LEN); printf("child read:%s\n", buf); write(pipefd2[], "hi", sizeof("hi")); exit(); } // 父进程 close(pipefd1[]); close(pipefd2[]); write(pipefd1[], "hello", sizeof("hello")); read(pipefd2[], buf, BUF_MAX_LEN); printf("parent read:%s\n", buf); waitpid(childpid, NULL, ); ; }
4.4 popen 和 pclose 函数
头文件 | #include <stdio.h> | |
函数原型 | FILE *popen(const char *command, const char *type); | |
int pclose(FILE *stream); | ||
返回值 | 成功返回文件描述符,失败返回NULL | |
参数 | command | shell命令,这行命令将被传到 bin/sh 并使用-c 标志 |
type | r:读到commond的标准输出 | |
w:写到command的标准输入 | ||
说明 | popen创建的文件描述符,必须由pclose关闭。 |
#include <stdio.h> #include <string.h> #define BUF_MAX_LEN 100 int main() { FILE *fp = NULL; ] = {}; fp = popen("ls", "r"); if (fp == NULL) { printf("popen error\n"); ; } while (fgets(buf, BUF_MAX_LEN, fp) != NULL) { fputs(buf, stdout); } pclose(fp); ; }
4.5 FIFO
FIFO由mkfifo函数创建。
头文件 |
#include <sys/types.h> #include <sys/stat.h> |
|
函数原型 | int mkfifo(const char *pathname, mode_t mode); | |
返回值 | 成功返回0,出错返回-1 | |
参数 | pathname | 一个普通的路径名,它是该FIFO名字 |
mode | 文件的权限 | |
说明 | 1.mkfifo函数已隐含O_CREAT | O_EXCL,只能创建一个新的FIFO | |
2.如要打开存在的FIFO,首先调用mkfifo,然后判断返回值是否为EEXIST,若返回该错误,再调用open或fopen | ||
3.FIFO只能读或者写,不能即读又写的,因为它是半双工的 | ||
4.对FIFO写总是追加到末尾,对FIFO读总是从头开始,对FIFO调用lseek,则返回ESPIPE错误 | ||
5.open打开FIFO文件时,如果以只写方式没有打开,则只读方式的open会一直阻塞 |
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #define FIFO1 "/tmp/fifo1" #define FIFO2 "/tmp/fifo2" #define BUF_MAX_LEN 10 int main() { ; ; pid_t childpid = ; ] = {}; // 打开2个FIFO ) < ) && (errno != EEXIST)) { printf("mkfifo error:cannot open %s\n", FIFO1); ; } ) < ) && (errno != EEXIST)) { unlink(FIFO1); printf("mkfifo error:cannot open %s\n", FIFO2); ; } // fork子进程 childpid = fork(); ) { unlink(FIFO1); unlink(FIFO2); printf("fork error\n"); ; } // child ) { readfd = open(FIFO1, O_RDONLY, ); writefd = open(FIFO2, O_WRONLY, ); write(writefd, "hi", sizeof("hi")); read(readfd, buf, BUF_MAX_LEN); printf("child recv:%s\n", buf); close(readfd); close(writefd); exit(); } // parent writefd = open(FIFO1, O_WRONLY, ); readfd = open(FIFO2, O_RDONLY, ); write(writefd, "hello", sizeof("hello")); read(readfd, buf, BUF_MAX_LEN); printf("parent recv:%s\n", buf); waitpid(childpid, NULL, ); close(readfd); close(writefd); unlink(FIFO1); unlink(FIFO2); ; }
4.6 管道和FIFO的区别
(1)创建并打开一个管道只需调用pipe。创建并打开一个FIFO则需要在调用mkfifo后再调用open。
(2)管道在所有进程都关闭后自动关闭。FIFO的名字则只能通过调用unlink才从文件系统中删除。
(3)FIFO有一个名字,可以供无亲缘关系的进程间通讯,管道不可以。
4.7 管道和FIFO的额外属性
(1)将描述符设置成非阻塞。
(2)write写入的字节数小于或等于PIPE_BUF(Posix限制),那么write操作是原子性的;如果写入数据大于PIPE_BUF,那么write操作不保证原子性。O_NOBLOCK标志对write的原子性没有影响
(3)如果向一个没有读打开的管道或FIFO写入,那么内核将产生一个SIGPIPE信号。