文章目录
- 无名管道通信
- 有名管道通信(FIFO)
无名管道通信
无名管道只能用于具有亲缘关系的进程之间的通信,即父子进程或者兄弟进程之间,它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数,但它不是普通文件,并不属于其他任何文件系统,只存在于内存中。
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1],其中fd[0]固定用于读管道,而fd[1]固定用于写管道。管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close函数逐个关闭各个文件描述符。
一个管道共享了多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。
创建管道调用函数pipe来实现,所需要的头文件和函数原型如下。
#include <unistd.h>
int pipe(int fd[2])
创建成功,函数返回0,创建失败返回-1。
创建无名管道的一个简单例子如下。
#include <unistd.h>
#include <stdio.h>int main()
{int pipe_fd[2];if(pipe(pipe_fd) < 0) //创建无名管道{printf("Pipe create error.\n");return -1;}elseprintf("Pipe create success.\n");close(pipe_fd[0]); //关闭管道读描述符close(pipe_fd[1]); //关闭管道写描述符
}
程序运行后先成功创建一个无名管道,打印创建成功的信息,然后再将其关闭。
用pipe函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。通常先创建一个管道,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道。因此,父子进程分别拥有自己的读写的通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。
下面的例子是子进程写,父进程读,完整的代码如下。
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{int pipe_fd[2];pid_t pid; char buf_r[100];char* p_wbuf;int r_num;memset(buf_r,0,sizeof(buf_r)); //初始化内存if(pipe(pipe_fd) < 0) //创建管道{printf("Pipe create error.\n");return -1;}pid = fork(); //创建一子进程if(pid == 0) //子进程{close(pipe_fd[0]); //关闭子进程读描述符sleep(1); //确保父进程关掉了写描述符if(write(pipe_fd[1],"Hello pipe!",11)!= -1) //写入管道的是"Hello pipe!"printf("Process write success!\n");close(pipe_fd[1]); //关闭子进程写描述符exit(0);}else if(pid > 0) //父进程{close(pipe_fd[1]); //关闭父进程写描述符if((r_num = read(pipe_fd[0],buf_r,100)) > 0){printf("length : %d string : %s\n",r_num,buf_r);} close(pipe_fd[0]); //关闭父进程读描述符 exit(0);}
}
程序运行后的结果如下图所示。
父子进程在运行时,它们的先后次序并不能保证,因此,为了保证父进程已经关闭了读描述符,可在子进程中调用sleep函数。
有名管道通信(FIFO)
无名管道只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限制,它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO是严格地遵循先进先出规则的,对管道及FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
有名管道的创建可以使用函数mkfifo(),在创建管道成功之后,就可以使用open、read、write这些函数了。对于为读而打开的管道可在open中设置O_RDONLY,对于为写而打开的管道可在open中设置O_WRONLY,这里与普通文件不同的是涉及阻塞的问题。由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在open函数中设定为O_NONBLOCK。
对于读进程,若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞直到有数据写入。若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。
对于写进程,若该管道是阻塞打开,写进程将一直阻塞直到有读进程读出数据。若该管道是非阻塞打开,即使当前FIFO内没有读操作,写进程都会立即执行读操作。
简单来说,阻塞方式就是要等另一方先执行,非阻塞方式则不会考虑另一方的状态。
创建有名管道调用函数mkfifo来实现,所需要的头文件和函数原型如下。
#include <sys/types.h>
#include <sys/state.h>
int mkfifo(const char *filename,mode_t mode)
创建成功,函数返回0,创建失败返回-1。
下面的例子包含一个读管道和一个写管道,在读管道里面创建管道,用户输入的内容以写管道里main函数的参数传入,然后读管道读出管道的内容。
read.c文件的内容如下。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo" //定义路径int main(int argc,char** argv)
{char buf_r[100];int fd;int nread;if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST)) //创建有名管道,O_CREAT文件不存在时创建,O_EXCL如果使用O_CREAT时文件存在,那么可返回错误消息printf("Create FIFO failed.\n");printf("Preparing for reading bytes...\n");memset(buf_r,0,sizeof(buf_r)); //初始化内存fd = open(FIFO,O_RDONLY|O_NONBLOCK,0); //打开有名管道,非阻塞方式//fd = open(FIFO,O_RDONLY,0); //打开有名管道,阻塞方式if(fd == -1){perror("Open");exit(1);}while(1){memset(buf_r,0,sizeof(buf_r));if((nread = read(fd,buf_r,100)) == -1){if(errno == EAGAIN)printf("No data yet.\n");}printf("String read from FIFO is %s\n",buf_r);sleep(1); //设置读管道的速度}pause(); //将调用进程挂起直至捕捉到信号为止,通常可以用于判断信号是否已到unlink(FIFO);
}
write.c文件的内容如下。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"int main(int argc,char** argv) //argc是传入参数的个数,argv[0]为自身运行目录路径和程序名,argv[1]存放的是用户输入
{int fd;char w_buf[100];int nwrite;if(fd == -1)if(errno == ENXIO)printf("Open error, no reading process.\n");fd = open(FIFO,O_WRONLY|O_NONBLOCK,0); //打开FIFO管道,并设置非阻塞标志//fd = open(FIFO,O_WRONLY,0); //打开FIFO管道,并设置阻塞标志if(argc == 1)printf("Please send string.\n");strcpy(w_buf,argv[1]);if((nwrite=write(fd,w_buf,100))==-1) //向管道中写入字符串{if(errno==EAGAIN)printf("The FIFO has not been read yet, please try later.\n");}elseprintf("String write to the FIFO is %s\n",w_buf);
}
打开两个终端,编译后先执行read,后执行write,因为read要先创建管道,read执行时如果没有权限就加sudo执行,如下图所示。
然后在执行write时需要带上用户的输入,也就是需要写入的内容。
这样就实现了有名管道的通信。
需要注意的是,如果一开始就使用sudo执行read的话,所创建的管道在write的时候也需要加sudo,否则无法通信。因为 sudo ./read执行时创建的管道文件的所有者和组都是root,write执行时无权访问,要不就加sudo,要不就使用chown和chgrp命令修改管道文件的所有者。