# unamed pipe

既然可以通过 “文件描述符” 来操作管道,那么他就是一个文件(管道文件),但无名管道文件比较特殊,他没有文件名。

# 函数原型

#include "unistd.h"
int piep(int fd[2]);

# 功能

创建一个用于亲缘进程(父子进程)之间通信的无名管道(缓存),并将管道与两个读写文件描述符关联起来。

无名管道只能用于亲缘进程间通信,为什么只能用于亲缘进程之间通信呢?

因为,没有文件名,进程没有办法通过 open 打开管道文件,从而得到文件描述符,所以只有一种办法,那就是父进程先调用 pipe 创建出管道,并得到读写管道的文件描述符;然后,再 fork 出子进程,让子进程通过继承父进程打开的文件描述符,父子进程就能操作同一个管道,从而实现通信。

读管道时,如果没有数据的话,读操作会被休眠(阻塞)

# 参数

缓存地址,缓存用于存放读写管道的文件描述符

这个缓存就是拥有两个元素的 int 型数组

元素[0] : 里面放的是读管道的读文件描述符

元素[1] : 里面放的是写管道的写文件描述符

返回值 :成功返回 0 ,失败返回 -1 ,并且 errno 被设置

# 单向通信

代码演示:

实现过程

  • 父进程在 fork 之前先调用 pipe 创建无名管道,获取读写文件描述符

  • fork 创建出子进程,子进程继承无名管道读写文件描述符

  • 父子进程使用各自管道的读写文件描述符进行读写操作,即可实现通信

// 现在实现父进程写,子进程读
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "strings.h"
int main(void)
{
	// 调用 pipe 创建无名管道,获取文件描述符
	int ret=0; // 接受函数返回值
	int fd[2];// 文件描述符
	int pipe(int fd[2]);
	ret=pipe(fd);
	if(ret==-1){
        printf("pipe error");
        exit(-1);
    }
	// 创建子进程
	ret=fork();
	if(ret>0)
	{
        close(fd[0]);
		while(1)
		{
			write(fd[1],"hello",5);
            sleep(1);
		}
	}else if(ret == 0){
        close(fd[1]);
		while(1)
        {
            char childbuf[30];
            bzero(childbuf,sizeof(childbuf));// 清空缓冲区
            read(fd[0],childbuf,sizeof(childbuf));
            printf("child receive the data:%s\n",childbuf);
        }
	}
	return 0;
}
// 外教给的 zz 例子
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
#include "sys/wait.h"
int main(void)
{
	int pid,child_pid,status;
	int fd[2],num_bytesRead;
	//create pipe
	pipe(fd);
	printf("I'm main,my PID is:%d\n",getpid());
	pid=fork();
	//child process
	if(pid==0){
        while(1){
            printf("child:%d,my parent:%d\n",getpid(),getppid());
			char childbuf[200];
            strcpy(childbuf,"i am child!");
            close(fd[0]);
            write(fd[1],childbuf,200);
            sleep(3);
            close(fd[1]);
        }
	}else{
        while(1){
            char mybuf[200];
            close(fd[1]);
            num_bytesRead=read(fd[0],mybuf,200);
            sleep(3);
            close(fd[0]);
            printf("got message from child, %s\n",mybuf);
            child_pid=wait(&status);
        }
	}
	return 0;
}
一共三句话:
I'm main,my PID is:%d\n",getpid()
child:%d,my parent:%d\n",getpid(),getppid()
got message from child, %s\n",mybuf

其中只有 2,3 在循环里,分别对应父子进程,而结果显示是最后只有父进程了,孩子没了,这也印证了 wait 的作用

# SIGPIPE 信号

为什么会产生这个信号?

写管道时,如果管道的读端被 close 的话,向管道 “写” 数据的进程会被内核发送一个 SIGPIPE 信号,发这个信号的目的就是通知你,管道所有的 “读” 都被关闭了。这就好比,别人把水管的出口(读端)堵住了,你还往里面一直灌水(写),别人会警告你,不然管道就被你弄坏了。只有管道的所有读端都被关闭,才会出现这个信号,只要还有一个读端开着,就不会产生它。

// 我们改一下前面的代码,在关闭子进程写端的基础上,关闭读端
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "strings.h"
int main(void)
{
	// 调用 pipe 创建无名管道,获取文件描述符
	int ret=0; // 接受函数返回值
	int fd[2];// 文件描述符
	int pipe(int fd[2]);
	ret=pipe(fd);
	if(ret==-1){
        printf("pipe error");
        exit(-1);
    }
	// 创建子进程
	ret=fork();
	if(ret>0)
	{
        close(fd[0]);
		while(1)
		{
			write(fd[1],"hello",5);
            sleep(1);
		}
	}else if(ret == 0){
        close(fd[1]);
        close(fd[0]);
		while(1)
        {
            char childbuf[30];
            bzero(childbuf,sizeof(childbuf));// 清空缓冲区
            read(fd[0],childbuf,sizeof(childbuf));
            printf("child receive the data:%s\n",childbuf);
        }
	}
	return 0;
}

尝试运行

下图是没有关闭子进程读端的情况,观察右图,发现创建了两个 test ,一个是子进程,一个是父进程

关闭了子进程读端之后, ps -a 发现只创建了一个 test ,这是因为子进程关闭了读端时,父进程尝试往管道中写入,被内核发送了 sigpipe 信号阻止了

# 双向通信

用无名管道实现双向通信,有这样一个问题,由于双向的读写同时进行,同一侧的写会被读抢先读走,所以,需要两个管道。

// 通过无名管道创建双向通信
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "strings.h"
int main(void)
{
	// 调用 pipe 创建无名管道,获取文件描述符
	int ret=0; // 接受函数返回值
	int fd1[2];// 文件描述符
	int fd2[2];
	int pipe(int fd1[2]);
	int pipe(int fd2[2]);
	ret=pipe(fd1);
	if(ret==-1){
        printf("pipe error");
        exit(-1);
    }
    ret=pipe(fd2);
	if(ret==-1){
        printf("pipe error");
        exit(-1);
    }
	// 创建子进程
	ret=fork();
	if(ret>0)
	{
        close(fd1[0]);
        close(fd2[1]);
        char mybuf[30];
		while(1)
		{
			write(fd1[1],"hello",5);
            sleep(1);
            bzero(mybuf,sizeof(mybuf));// 清空缓冲区
            read(fd2[0],mybuf,sizeof(mybuf));
            printf("parent got the data:%s\n",mybuf);
		}
	}else if(ret == 0){
        close(fd1[1]);// 对于管道 1,父进程不需要写端,关闭写端
        close(fd2[0]);// 对于管道 2,父进程不需要读端,关闭读端
        char childbuf[30];
		while(1)
        {
            bzero(childbuf,sizeof(childbuf));// 清空缓冲区
            read(fd1[0],childbuf,sizeof(childbuf));
            printf("child receive the data:%s\n",childbuf);
            sleep(1);
            write(fd2[1],"world",5);
        }
	}
	return 0;
}

通过以上的例子,我们不难发现,无名管道的缺点:

  • 无法用于非亲缘进程之间

    因为非亲缘进程之间没办法继承管道的文件描述符

  • 无法实现多进程之间的网状通信

    如果非要使用无名管道实现多进程之间的网状通信,文件描述符的继承关系 将非常的复杂,所以无名管道基本上知识和两个进程之间的通信

适用情况:

如果通信的进程只有两个,而且还是亲缘进程时

  • 直接继承父子进程之间的通信
  • 间接继承关系的两进程之间的通信

# named pipe

相反的,如果有文件名,就是有名管道;也就是说我们调用相应的 API 创建好 “有名管道” 后,会在相应的路径下看到一个有名字的文件;但不管有名与否,本质上就是通过共享操作这段缓存来实现,只不过这段操作缓存的方式,是以读写文件的形式来操作的。

# 函数原型

#include "sys/types.h"
#include "sys/stat.h"
int mkfifo(const char *pathname,mode_t mode);

# 功能

创建有名管道文件,创建好后便可 open 打开,如果是创建普通文件的话,我们可以使用 openO_CREAT 选项来创建,比如:

open("./file",O_RDWR|O_CREAT,0664);

但是对于 “有名管道” 这种特殊文件,只能使用 mkfifo 函数来创建。

较于无名管道,它可以用于非亲缘进程之间的通信

因为有文件名,所以进程可以直接调用 open 函数打开文件,从而得到文件描述符,不需要通过无名管道一样,必须通过继承的方式才能获取到文件描述符。

读管道时,如果管道没有数据的话,读操作同样会被阻塞(休眠)

当进程写一个所有读端都被关闭的管道时,进程会被内核返回 sigpipe 信号,如果不想被该信号终止的话,我们需要忽略、捕获、屏蔽该信号。

# 参数

pathname : 被创建管道文件的文件路径名

mode : 指定被创建时原始权限,一般为 0664(110110100) ,必须包含读写权限

返回值 :成功返回 0 ,失败返回 -1 ,并且 errno 被设置

# 大致步骤

  • 进程待用 mkfifo 创建有名管道

  • open 打开有名管道

  • read/write 读写管道进行通信

对于通信的两个进程来说,创建管道时,只需要一个人创建,另一个人直接使用即可;为了保证管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道已经被创建好了,那就直接 open 打开使用。

# 单向通信

#define FIFONAME1 "./fifo1"
// 首先创建 pipe 管道文件
int create_open_fifo(char *fifoname,int open_mode){
	int ret = -1;
	int fd = -1;
	ret = mkfifo(fifoname,0664);
		if(ret == -1 && errno!=EEXIST){  // 如果不是因为 "已经存在了" 这个问题,你才去报错
			printf("mkfifo error");
			exit(-1);
		}
		fd = open(fifoname,open_mode);
		if(fd == -1){
			printf("open fail");
			exit(-1);
		}
	return fd;
}
int main()
{
	char buf[100];
	int ret = -1;
	int fd = -1;
	fd = create_open_fifo(FIFONAME1,O_WRONLY);
	while(1)
	{
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(fd,buf,sizeof(buf));
	}
	return 0;
}

这里有个小问题,创建一个管道文件,结束后没用了也没删掉。

由于我们手动结束程序的方法基本上是 CTRL+C ,直接关闭,它作为程序终止的信号 SIGINT ,我们可以使用 signal 函数制作一个函数来处理这个信号

#define FIFONAME1 "./fifo1"
// 寻找终止信号,发现即删掉 FIFONAME1 并退出
void signal_fun(int signo)
{
	//unlink();
	remove(FIFONAME1);
	exit(-1);
}
int main()
{
	char buf[100];
	int ret = -1;
	int fd = -1;
	signal(SIGINT,signal_fun);
	fd = create_open_fifo(FIFONAME1,O_WRONLY); // 只写地方式创建并打开 FIFONAME1
	while(1)
	{
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(fd,buf,sizeof(buf));
	}
	return 0;
}
//serve.c  (client.c 稍作修改)
#define FIFONAME1 "./fifo1"
// 创建 pipe 管道文件
int create_open_fifo(char *fifoname,int open_mode){
	int ret = -1;
	int fd = -1;
	ret = mkfifo(fifoname,0664);
		if(ret == -1 && errno!=EEXIST){
			printf("mkfifo error");
			exit(-1);
		}
		fd = open(fifoname,open_mode);
		if(fd == -1){
			printf("open fail");
			exit(-1);
		}
	return fd;
}
void signal_fun(int signo)
{
	//unlink();
	remove(FIFONAME1);
	exit(-1);
}
int main()
{
	char buf[100];
	int ret = -1;
	int fd = -1;
	signal(SIGINT,signal_fun);
	fd = create_open_fifo(FIFONAME1,O_RDONLY); // 只读地方式创建并打开 FIFONAME1
	while(1)
	{
		bzero(buf,sizeof(buf));
		read(fd,buf,sizeof(buf));
		printf("recv:%s\n",buf);
	}
	return 0;
}
//head.h
#include "unistd.h"
#include "strings.h"
#include "signal.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h" // 需要用到 open 函数
#include "stdlib.h"
#include "stdio.h"
#include "errno.h"
#include "signal.h"

双客户自然也可以实现

外教的 zz 例子

//client.c
#include "header.h"
int main()
{
	int fdout,status;
	char message[80];
	status=mkfifo(PIPE_1,0666);
	if (( status == -1) && (errno != EEXIST)) {
	perror("Error creating the named pipe - remove pipe if already exists");
	exit (1);
	}
	sprintf(message,"This is a client,my pid is:%d\n",getpid());
	if((fdout=open(PIPE_1,O_WRONLY))==-1)
	{
		perror("./fifo1");
		exit(-1);
	}
	write(fdout,message,sizeof(message));
	close(fdout);
	return 0;
}
//serve.c
#include "header.h"
int main()
{
	int i,fdin,status,count,numRead;
	char buff[BUF_SIZE];
	status=mkfifo(PIPE_1,0666);
	if((status==-1)&&(errno!=EEXIST))
	{
		perror("Error creating the named pipe - remove pipe if already exist");
		exit(1);
	}
	fdin=open(PIPE_1,O_RDONLY);
	numRead=read(fdin,buff,BUF_SIZE);
	if(numRead==0)
	{
		printf("PIPE_1 write side closed,server now exits\n");
		exit(-1);
	}
	printf("got this string from pipe %s\n",buff);
	for(i=0;i<sizeof(buff);i++)
	{
		if(islower(buff[i]))
			buff[i]=toupper(buff[i]);
	}
	printf("now converted uppercase string %s\n",buff);
	close(fdin);
	unlink(PIPE_1);
	return 0;
}
//head.h
#include "stdlib.h"
#include "stdio.h"
#include "fcntl.h"
#include "unistd.h"
#include "errno.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "strings.h"
#define PIPE_1 "./fifo2"
#define BUF_SIZE 256

# 双向通信

与无名管道的双向通道相似,由于双向的读写同时进行,同一侧的写会被读抢先读走,所以,需要两个管道,各司其职。

//client.c
#include "head.h"
// 创建 pipe 管道文件
int create_open_fifo(char *fifoname,int open_mode){
	int ret = -1;
	int fd = -1;
	ret = mkfifo(fifoname,0664);
		if(ret == -1 && errno!=EEXIST){
			printf("mkfifo error");
			exit(-1);
		}
		fd = open(fifoname,open_mode);
		if(fd == -1){
			printf("open fail");
			exit(-1);
		}
	return fd;
}
void signal_fun(int signo)
{
	//unlink();
	remove(FIFONAME1);
	exit(-1);
}
int main(void)
{
	char buf[100]={0};
	int ret = -1;
	int fd1 = -1;
	int fd2 = -1;
	fd1 = create_open_fifo(FIFONAME1,O_WRONLY); // 只写地方式创建并打开 FIFONAME1
	fd2 = create_open_fifo(FIFONAME2,O_RDONLY);
	ret=fork();
	if(ret>0)
	{
		signal(SIGINT,signal_fun);
		while(1)
		{
			bzero(buf,sizeof(buf));
			scanf("%s",buf);
			write(fd1,buf,sizeof(buf));
		}
	}else if(ret==0){
		while(1)
		{
			bzero(buf,sizeof(buf));
			read(fd2,buf,sizeof(buf));
			printf("recv:%s\n",buf);
		}
	}
	/*while(1)
	{
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(fd1,buf,sizeof(buf));
		read(fd2,buf,sizeof(buf));
	}*/
	return 0;
}
//serve.c
#include "head.h"
// 创建 pipe 管道文件
int create_open_fifo(char *fifoname,int open_mode){
	int ret = -1;
	int fd = -1;
	ret = mkfifo(fifoname,0664);
		if(ret == -1 && errno!=EEXIST){
			printf("mkfifo error");
			exit(-1);
		}
		fd = open(fifoname,open_mode);
		if(fd == -1){
			printf("open fail");
			exit(-1);
		}
	return fd;
}
void signal_fun(int signo)
{
	//unlink();
	remove(FIFONAME1);
	exit(-1);
}
int main(void)
{
	char buf[100]={0};
	int ret = -1;
	int fd1 = -1;
	int fd2 = -1;
	fd1 = create_open_fifo(FIFONAME1,O_RDONLY); // 只读地方式创建并打开 FIFONAME1
	fd2 = create_open_fifo(FIFONAME2,O_WRONLY); // 只写地方式创建并打开 FIFONAME2
	ret = fork();
	if(ret>0)
	{
		signal(SIGINT,signal_fun);
		while(1)
		{
			bzero(buf,sizeof(buf));
			read(fd1,buf,sizeof(buf));
			printf("recv:%s\n",buf);
		}
	}else if(ret==0){
		while(1)
		{
			bzero(buf,sizeof(buf));
			scanf("%s",buf);
			write(fd2,buf,sizeof(buf));
		}
	}
	/*while (1) 这里有个问题,读操作会把写操作阻塞
	{
		bzero (buf,sizeof (buf));
		read (fd1,buf,sizeof (buf));
		printf ("recv:% s\n",buf);
		write (fd2,buf,sizeof (buf));
	}*/
	return 0;
}
//head.h
#include "unistd.h"
#include "strings.h"
#include "signal.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h" // 需要用到 open 函数
#include "stdlib.h"
#include "stdio.h"
#include "errno.h"
#include "signal.h"
#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

可以实现握手!

更新於 閱讀次數

請我喝[茶]~( ̄▽ ̄)~*

fygod 微信支付

微信支付

fygod 支付寶

支付寶

fygod PayPal

PayPal