无名管道简介:
管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程):管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,并且只存在于内存中。管道的读写规则:fifo先进先出规则,写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
无名管道相关函数说明:
#include <unistd.h>
int pipe(int fd[2])
返回值:若成功,返回0;若出错,返回-1。
函数传入值 fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符。
调用此函数之后,返回两个文件描述符,fd[0]用于读管道,fd[1]用于写管道。
管道的运用:
通常先是创建一个管道,再通过 fork()函数创建一子进程,该子进程会继承父进程所创建的管道。这个时候,子进程和父进程都有两个文件描述符,fd[0]用于读管道,fd[1]用于写管道。我们想要让其通信,只需要一个进程保留写端,一个进程保留读端。
示例程序:
/* pipe.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main ()
{
pid_t pid;
int count=0;
int pipe_fd[2];
char read_pipe_buf[100];
char read_pipe_buf_last[100];
char write_pipe_buf[] = "hello";
int read_num;
//创建管道,pipe_fd[0]用于读管道,pipe_fd[1]用于写管道
if(pipe(pipe_fd)<0)
{
perror("pipe create error\n");
exit(-1);
}
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0) //子进程
{
//关闭pipe_fd[1]写管道,表明要读取管道的数据
close(pipe_fd[1]);
sleep(1);
//子进程读取管道内容
if((read_num=read(pipe_fd[0],read_pipe_buf,5))>0)
{
printf("this is child, %d numbers first read from pipe is %s\n",read_num,read_pipe_buf);
}
if((read_num=read(pipe_fd[0],read_pipe_buf_last,5))>0)
{
printf("this is child, %d numbers second read from pipe is %s\n",read_num,read_pipe_buf_last);
}
//关闭子进程读描述符
close(pipe_fd[0]);
exit(0);
}
else //父进程
{
//关闭pipe_fd[0]读管道,表明要向管道写数据
close(pipe_fd[0]);
/*if(write(pipe_fd[1],write_pipe_buf,sizeof(write_pipe_buf)) == -1)
{
perror("write pipe");
exit(-1);
}*/
if(write(pipe_fd[1],"hello",5) == -1)
{
perror("write pipe");
exit(-1);
}
if(write(pipe_fd[1],"world",5) == -1)
{
perror("write pipe");
exit(-1);
}
//关闭父进程写描述符
close(pipe_fd[1]);
//等待子进程退出
waitpid(pid,NULL,0);
exit(0);
}
return 0;
}
实验结果:
ubuntu:~/test/process_test$ gcc pipe.c -o pipe
ubuntu:~/test/process_test$ ./pipe
this is child, 5 numbers first read from pipe is hello
this is child, 5 numbers second read from pipe is world
以上程序,先是调用pipe(pipe_fd),得到pipe_fd[0]用于读管道,pipe_fd[1]用于写管道。然后调用fork,创建出子进程,在子进程保留读端,用于读取管道的数据,在父进程保留写端,用于写管道,把字符串”hello”、”world”先后写入管道,接着子进程分两次读取管道的内容,并把它打印出来。子进程先读取的字符串是父进程先写入的字符串”hello”,这也体现了管道数据fifo先进先出的原则。
有名管道:
无名管道只能用在亲缘关系的进程之间,并且管道是存储在内存中,进程一终止,数据就没有了。而有名管道没有这种限制,可以用在互不相关的两个进程之间,文件也能保存在文件系统中。有名管道和无名管道都遵循先进先出规则。
有名管道函数说明:
#include <sys/state.h>
int mkfifo(const char *filename,mode_t mode)
返回值:若成功,返回文件流指针;若出错,返回-1
参数:
- filename:要创建的管道
- mode:
- O_RDONLY:读管道
- O_WRONLY:写管道
- O_RDWR:读写管道
- O_NONBLOCK:非阻塞
- O_CREAT:如果该文件不存在,那么就创建一个新的文件,并为其设置权限
- O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在
可把有名管道的操作类似于对普通文件的操作,创建使用函数 mkfifo(),该函数类似普通文件中的 open()操作,然后在创建管道成功之后,就可以使用 open、read、write 这些函数了。
接下来创建两个程序,一个用于写管道,一个用于读管道,这两个程序是互不相关的。
写管道示例程序:
/* write_fifo.c*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFO "myfifo"
int main(int argc,char** argv)
{
/*创建有名管道*/
if(mkfifo(FIFO,O_CREAT|O_EXCL|777)<0)
perror("cannot create fifoserver\n");
/*打开管道*/
int fd=open(FIFO,O_RDWR|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
}
char write_buf[128];
int i = 0,write_num = 0;
while(1)
{
printf("enter something to the myfifo:");
scanf("%s",write_buf);
if (write_num = write(fd,write_buf,sizeof(write_buf)) < 0)
{
perror("write");
}
printf("This is a write fifo....write %s to the FIFO\n",write_buf);
}
return 0;
}
读管道示例程序:
/* read_fifo.c*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define FIFO "myfifo"
int main(int argc,char** argv)
{
/*创建有名管道*/
if(mkfifo(FIFO,O_CREAT|O_EXCL|777)<0)
perror("cannot create fifoserver\n");
/*打开管道,默认阻塞模式*/
int fd = open(FIFO,O_RDONLY,0);
//int fd = open(FIFO,O_RDONLY|O_NONBLOCK,0);//非阻塞模式
if(fd == -1)
{
perror("open");
}
char read_buf[128];
int i = 0,read_num;
while(1)
{
memset(read_buf,0,sizeof(read_buf));
if (read_num = read(fd,read_buf,sizeof(read_buf)) < 0)
{
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("This is a read fifo....read %s from FIFO\n",read_buf);
//sleep(2);
}
return 0;
}
我们在A终端运行写管道的程序,在B终端运行读管道的程序,根据A终端的提示,输入任意字符串,然后在B终端看看是否有读到相应的字符串。如果读不到,B终端会一直阻塞,因为我们程序设置了打开管道的默认属性阻塞,我们也可以设置非阻塞模式。从下面的实验结果,可以发现有名管道可以运用在没有亲缘关系的进程中。
//A终端
ubuntu:~/test/process_test$ gcc write_fifo.c -o write_fifo
ubuntu:~/test/process_test$ ./write_fifo
enter something to the myfifo:helloworld
This is a write fifo....write helloworld to the FIFO
enter something to the myfifo:
//B终端
ubuntu:~/test/process_test$ gcc read_fifo.c -o read_fifo
ubuntu:~/test/process_test$ ./read_fifo
cannot create fifoserver
: File exists
This is a read fifo....read helloworld from FIFO