管道的概念:
管道是一种最基本的IPC机制,由pipe函数创建:
#include<unistd.h>
int pipe(int fileds[2]);
调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端。
进程在管道间通信:
1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2.父进程调用fork创建子进程,子进程有两个文件描述符指向同一管道。
3.父进程关闭管道读端,子进程关闭管道斜段。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入,从读端流出,这样就实现了进程间通信。
代码实现如下:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<errno.h> 4 #include<string.h> 5 6 int main() 7 { 8 int _pipe[2]; 9 int ret = pipe(_pipe); 10 if(ret == -1){ 11 printf("create pipe erro!errno code is:%d\n",errno); 12 return 1; 13 } 14 pid_t id=fork(); 15 if(id<0){ 16 printf("fork error!"); 17 return 2; 18 }else if(id == 0){ 19 close(_pipe[0]); 20 int i=0; 21 char* _mesg_c=NULL; 22 while(i<100){ 23 _mesg_c="I am a child!"; 24 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1); 25 sleep(1); 26 i++; 27 } 28 }else{//father 29 close(_pipe[1]); 30 char _mesg[100]; 31 int j=0; 32 while(j<100) { 33 memset(_mesg,‘\0‘,sizeof(_mesg)); 34 read(_pipe[0],_mesg,sizeof(_mesg)); 35 printf("%s\n",_mesg); 36 j++; 37 } 38 } 39 return 0; 40 41 42 }
程序运行结果如下:
管道的四个特殊情况:
1.所有指向管道写端的文件描述符都关闭了,仍然有进程从管道的读端读取数据,当管道中剩余的数据都被读取后,再次读取会返回0,就像读到文件末尾一样。
代码实现:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<sys/wait.h> 6 int main() 7 { 8 int _pipe[2]; 9 int ret = pipe(_pipe); 10 if(ret == -1){ 11 printf("create pipe erro!errno code is:%d\n",errno); 12 return 1; 13 } 14 pid_t id=fork(); 15 if(id<0){ 16 printf("fork error!"); 17 return 2; 18 }else if(id == 0){//child 19 close(_pipe[0]); 20 int i=0; 21 char* _mesg_c=NULL; 22 while(i<10){ 23 _mesg_c="I am a child!"; 24 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1); 25 sleep(1); 26 i++; 27 } 28 close(_pipe[1]); 29 }else{//father 30 close(_pipe[1]); 31 char _mesg[100]; 32 int j=0; 33 while(j<100) { 34 memset(_mesg,‘\0‘,sizeof(_mesg)); 35 int ret = read(_pipe[0],_mesg,sizeof(_mesg)); 36 printf("%s:code is:%d\n",_mesg,ret); 37 j++; 38 } 39 if(waitpid(id,NULL,0)<0) 40 { 41 return 3; 42 } 43 } 44 return 0; 45 }
运行结果如下:
2.指向管道写端的文件描述符没关闭,持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读取数据,当管道中剩余的数据都被读取后,再次读取会阻塞,直到管道中有数据可读了才读取数据并返回。
代码实现:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<sys/wait.h> 6 7 int main() 8 { 9 int _pipe[2]; 10 int ret = pipe(_pipe); 11 if(ret == -1){ 12 printf("create pipe erro!errno code is:%d\n",errno); 13 return 1; 14 } 15 pid_t id=fork(); 16 if(id<0){ 17 printf("fork error!"); 18 return 2; 19 }else if(id == 0){//child 20 close(_pipe[0]); 21 int i=0; 22 char* _mesg_c=NULL; 23 while(i<20){ 24 if(i<10){ 25 _mesg_c="I am a child!"; 26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1); 27 } 28 sleep(1); 29 i++; 30 } 31 close(_pipe[1]); 32 }else{//father 33 close(_pipe[1]); 34 char _mesg[100]; 35 int j=0; 36 while(j<20) { 37 memset(_mesg,‘\0‘,sizeof(_mesg)); 38 int ret = read(_pipe[0],_mesg,sizeof(_mesg)); 39 printf("%s:code is:%d\n",_mesg,ret); 40 j++; 41 } 42 if(waitpid(id,NULL,0)<0) 43 { 44 return 3; 45 } 46 } 47 return 0; 48 }
运行结果:
3.如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端write,该进程会收到信号SIGPIPE,会导致进程异常终止。
代码实现:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<sys/wait.h> 6 7 int main() 8 { 9 int _pipe[2]; 10 int ret = pipe(_pipe); 11 if(ret == -1){ 12 printf("create pipe erro!errno code is:%d\n",errno); 13 return 1; 14 } 15 pid_t id=fork(); 16 if(id<0){ 17 printf("fork error!"); 18 return 2; 19 }else if(id == 0){//child 20 close(_pipe[0]); 21 int i=0; 22 char* _mesg_c=NULL; 23 while(i<20){ 24 if(i<10){ 25 _mesg_c="I am a child!"; 26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1); 27 } 28 sleep(1); 29 i++; 30 } 31 }else{//father 32 close(_pipe[1]); 33 char _mesg[100]; 34 int j=0; 35 while(j<3) { 36 memset(_mesg,‘\0‘,sizeof(_mesg)); 37 int ret = read(_pipe[0],_mesg,sizeof(_mesg)); 38 printf("%s:code is:%d\n",_mesg,ret); 39 j++; 40 } 41 close(_pipe[0]); 42 sleep(10); 43 if(waitpid(id,NULL,0)<0) 44 { 45 return 3; 46 } 47 } 48 return 0; 49 }
运行结果:
4.如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
代码实现:
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<sys/wait.h> 6 7 int main() 8 { 9 int _pipe[2]; 10 int ret = pipe(_pipe); 11 if(ret == -1){ 12 printf("create pipe erro!errno code is:%d\n",errno); 13 return 1; 14 } 15 pid_t id=fork(); 16 if(id<0){ 17 printf("fork error!"); 18 return 2; 19 }else if(id == 0){//child 20 // close(_pipe[0]); 21 int i=0; 22 char* _mesg_c=NULL; 23 while(i<20){ 24 // if(i<10){ 25 _mesg_c="I am a child!"; 26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1); 27 // } 28 sleep(1); 29 i++; 30 } 31 close(_pipe[1]); 32 }else{//father 33 close(_pipe[1]); 34 char _mesg[100]; 35 int j=0; 36 while(j<3) { 37 memset(_mesg,‘\0‘,sizeof(_mesg)); 38 int ret = read(_pipe[0],_mesg,sizeof(_mesg)); 39 printf("%s:code is:%d\n",_mesg,ret); 40 j++; 41 } 42 // close(_pipe[0]); 43 sleep(10); 44 if(waitpid(id,NULL,0)<0) 45 { 46 return 3; 47 } 48 } 49 return 0; 50 }
运行结果:
管道通信的特点:
1.它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间) 。
2.它是一个单向通信的通信模式,具有固定的读端和写端。
3.管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
命名管道:
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(namedpipe或FIFO)提出后,该限制得到了克服。
FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是, FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
系统调用形式:
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char*path,mode_t mod,dev_t dev)
int mkfifo(const char*path,mode_t mode)
该函数的第一个参数是一个路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open(),函数中的mode参数相同,有以下几种:
O_RDONLY:读管道
O_WRONLY:写管道
O_RDWR:读写管道
O_NONBLOCK:非阻塞
O_CREAT:
O_EXCL:
一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
FIFO读规则
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
– 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
– 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:
A.当前FIFO内有数据,但有其它进程在读这些数据;
B.另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
FIFO写规则
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
对于设置了阻塞标志的写操作:
– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
代码实现:
fiforead.c
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<unistd.h> 5 #include<fcntl.h> 6 #include<string.h> 7 #define _PATH_"/tmp/file.tmp" 8 #define _SIZE_ 100 9 10 int main() 11 { 12 int fd = open(_PATH_,O_RDONLY); 13 if(fd<0){ 14 printf("open file error!\n"); 15 return 1; 16 } 17 char buf[_SIZE_]; 18 memset(buf,‘\0‘,sizeof(buf)); 19 while(1){ 20 int ret = read(fd,buf,sizeof(buf)); 21 if(ret<=0)//error or end of file 22 { 23 printf("read end or error!\n"); 24 break; 25 } 26 } 27 close(fd); 28 return 0; 29 }
fifowrite.c
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<unistd.h> 5 #include<fcntl.h> 6 #include<string.h> 7 #define _PATH_"/tmp/file.tmp" 8 #define _SIZE_ 100 9 10 int main() 11 { 12 int ret=mkfifo(_PATH_,0666|S_IFIFO); 13 if(ret==-1){ 14 printf("mkfifo error\n"); 15 return 1; 16 } 17 18 int fd = open(_PATH_,O_RDONLY); 19 if(fd<0){ 20 printf("open file error!\n"); 21 return 1; 22 } 23 char buf[_SIZE_]; 24 memset(buf,‘\0‘,sizeof(buf)); 25 while(1){ 26 scanf("%s",buf); 27 int ret = write(fd,buf,sizeof(buf)+1); 28 if(ret<=0)//error or end of file 29 { 30 printf("write error!\n"); 31 break; 32 } 33 if(strncmp(buf,"quit",4)==0){ 34 break; 35 } 36 } 37 close(fd); 38 return 0; 39 }