管道(有名管道、无名管道、进程间利用有名管道双向通信例子(父进程首先向子进程传送两次数据)、父进程的输出连接子进程输入、无名管道(双进程读写管道,写进程创建FIFO文件)、管道模型)

一、
管道是unix中最古老的进程间通信工具,管道包括无名管道和有名管道。前者在父子进程中流行,后者由于可独立成磁盘文件形式存在,能够被无血缘关系的进程共享管道是一种队列类型的数据结构,他的数据从一端输入,另一端输出。其最常见应用是连接两个进程的输入输出,即把一个进程的输出作为另一个进程的输入。
二、
1、无名管道通常直接称之为管道,占用两个文件描述符,不能被非亲缘关系的进程共享,一般应用在父子进程中。UNIX系统中一切皆为文件,管道也是文件的一种,称为管道文件。当系统创建一个管道时,他返回两个文件描述符:一个文件以只写打开,作为管道的输入端;另一个文件以只读打开,作为管道的输出端。Unix中采用pipe创建无名管道

#include<unistd.h>
Int pipe(int fildes[2]);

函数pipe在内核创建一个管道。并分配两个文件描述符标识管道两端,存储于fildes数组中,fildes[1]描述输入端(写端),fildes[0]描述输出端(读端)。
管道的两端被一个进程控制没有太大的意义,如果管道的两端分别控制在不同进程中,这两个进程之间就能够进行通信。拥有管道输入端的进程,可以向管道发送信息,拥有管道输出端的进程,可以从管道中接收前一个进程发送来的信息。

2、 从父(子)进程流向子(父)进程通道
在父进程创建无名管道并产生子进程后,父子进程均拥有管道两端的访问权。此时关闭父进程的管道输出端、关闭子进程的管道输入端,就形成一个从父进程到子进程的管道流,数据由父进程写入,从子进程读出。

过程:(ps:从子进程流向父进程通道完全类似,3)4)中将关闭的输入/输出端调换)
1) 创建管道 ;Int fildes[2], pipe(fildes);
2) 创建子进程,子进程继承无名管道文件描述符;
3) 父进程关闭管道的输出端 close(fildes[0]);
4) 子进程关闭管道的输入端 close(fildes[1]);
3、在进程通信中,无法判断每次通信中报文的字节数,即无法对数据流进行自动拆分,从而发生子进程一次性读取父进程两次通信的报文情况。为实现正常拆分发送报文,采用如下方法:
1)固定长度 : 管道输入时固定写入len个字符,管道输出时也固定读取len个字符,采用左对齐方式,多余部分填充ASCII码0。
2)显式长度:每条报文由"长度域"和"数据域"组成,"长度域"大小固定,存储了"数据域"的长度,分为字符串型和整型两种,"数据域"是传输的实际报文数据。接受进程先获取"长度域"数据,转换为"数据域"的长度,再读取相应长度的信息即为"数据域"内容。以4字节长度域传输数据“Hello!”为例,整型长度域报文为:0x06 0x00 0x00 0x00 “Hello!”。字符串型长度域报文为:“0006Hello!” 出于兼容性考虑,一般采用网络字节顺序的整型(大端)。
3)短连接:每当进程间需要通信时,创建一个通信线路,发送一条报文后立即废弃这条通信路线。这种方式为Socket通信中很常用。
4、管道是进程之间的一种单向交流方式,要实现进程间的双向交流,就必须通过两个管道来完成。
1)创建管道,返回两个无名管道文件描述符fildes1和fildes2;
int fildes1[2],fildes2[2]; pipe(fildes1);pipe(fildes2);
2)创建子进程,子进程中继承管道1和管道2;
3)父进程关闭管道1的输出端;
子进程关闭管道1的输入端;
父进程关闭管道2的输入端;
子进程关闭管道2的输出端。
5、实例:父进程首先向子进程传送两次数据,再接收子进程传送过来的两次数据。父进程流向子进程的管道1采用固定长度法传送数据,子进程流向父进程的管道2采用显式长度方法回传数据。

#include <unistd.h>
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
char buf[255];
void WriteG(int fd, char *str, int len)
{
	memset(buf, 0, sizeof(buf));
	sprintf(buf, "%s", str);
	write(fd, buf, len);
}
char *ReadG(int fd, int len)
{
	memset(buf, 0, sizeof(buf));
	read(fd, buf, len);	
    return buf;
}
void WriteC(int fd, char *str)
{
	sprintf(buf, "%04d%s", strlen(str), str);//对于%04d有疑问的朋友看下面链接
	write(fd, buf, strlen(buf));	
}
char *ReadC(int fd) //子进程流向父进程
{
    int i, j;
	memset(buf, 0, sizeof(buf));
	j = read(fd, buf, 4);	
	i = atoi(buf);
	j = read(fd, buf, i);	
	return buf;
}

int main()
{
	int fildes1[2];  //管道1父进程传送给子进程 子进程接收  父: close (fildes1[0]) 子:close (fildes1[1])
	int fildes2[2];  //管道2子进程传送给父进程 父进程接收  父: close (fildes2[1]) 子:close (fildes2[0])
    char buf[255];
	pid_t pid;
	if(pipe(fildes1) < 0 || pipe(fildes2) < 0)
	{
		fprintf(stderr, "pipe error!\n");
		return;
	}
    if((pid = fork()) < 0 )
	{
		fprintf(stderr, "fork error!\n");
		return;
	}
	if(pid == 0)	//子进程
	{
		close(fildes1[1]);	            
		close(fildes2[0]);            

		strcpy(buf, ReadG(fildes1[0],10)); //子进程接收
		fprintf(stderr, "[child] buf=[%s]\n", buf);
		WriteC(fildes2[1], buf); //发给父进程

		strcpy(buf, ReadG(fildes1[0],10));
        fprintf(stderr, "[child] buf=[%s]\n", buf);
		WriteC(fildes2[1], buf);	
		return;
	}
	close(fildes1[0]);	
	close(fildes2[1]);

	WriteG(fildes1[1], "Hello!", 10); //父进程向子进程传输 
	WriteG(fildes1[1], "World!", 10);

	fprintf(stderr, "[father] buf=[%s]\n", ReadC(fildes2[0]));  //父进程接收
	fprintf(stderr, "[father] buf=[%s]\n", ReadC(fildes2[0]));

	return 0;
}

在这里插入图片描述
https://blog.csdn.net/scarificed/article/details/121463786
三、连接标准I/O的管道模型
1、使用管道将父进程标准输出连接到子进程标准输入的方法如下:
1) 创建管道,返回无名管道的两个文件描述符;
2) 创建子进程,子进程中继承无名管道文件描述符;
3) 父进程关闭管道的输出端;
4) 父进程将标准输出重定向为文件描述符fildes[1];

dup2(fildes[1],1);

5) 子进程关闭管道的输入端;
6) 子进程将标准输入重定向为文件描述符fildes[0]。

dup2(fildes[0],0);

2、父进程的输出连接子进程输入通信实例

#include <stdio.h>	    
#include <unistd.h>
int  main()
{
	int fildes[2];		
	pid_t pid;
	int i, j;	
	char buf[255];
	if(pipe(fildes) < 0 || (pid = fork()) < 0) 
	{
		fprintf(stderr, "error!\n");
		return 1;
	}
    if(pid == 0)	
	{
		close(fildes[1]);	
		dup2(fildes[0],0);
		close(fildes[0]);	
		gets(buf);
		fprintf(stderr, "child:[%s]\n", buf);
		return 2;
	}
	close(fildes[0]);	
	dup2(fildes[1], 1);
	close(fildes[1]);	
	puts(“Hello!);
	return 0;
}

ps:
1)习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1。
2)int dup2(int odlfd, int newfd);
函数说明:dup2( )用来复制参数oldfd 所指的文件描述词, 并将它拷贝至参数newfd 后一块返回。(将newfd替换为odlfd)

3、popen模型

#include <stdio.h>
FILE *popen(const char *command, char *type);
int pclose(FILE *stream);

函数popen首先fork一子进程,然后调用exec执行command中给定的shell命令。该函数自动在父进程和子进程之间建立一个管道,可连接子进程的标准输入,也可以连接子进程的标准输出。参数type决定了管道的I/O类型。(r:创建与子进程的标准输出连接的管道;w:创建与子进程的标准输入连接的管道。)

#include <stdio.h>
void main()
{
	FILE *out, *in;	char buf[255];
	if((out = popen("grep init," "w")) == NULL)	
	{
		fprintf(stderr, "error!\n");
		return;
	}
	if((in = popen("ps -ef","r")) == NULL)
	{
		fprintf(stderr, "error!\n");
		return;
	}
	while(fgets(buf, sizeof(buf), in))
		fputs(buf, out);
	pclose(out);
	pclose(in);
}

四、有名管道
1、FIFO管道 有名的管道,以一种特殊的文件类型存储于文件系统中,供无血缘关系进程访问。
2、命令 mknod创建管道

mknod命令可创建特殊类型的文件  //创建块设备或字符设备文件,主从设备号
/etc/mknod name [ b|c ] major minor	
/etc/mknod name p	  //创建管道文件
/etc/mknod name s	  //创建信号量
/etc/mknod name m  //创建共享内存
例 创建有名管道k1。
答:mknod k1 p

3、命令mkfifo创建管道

mkfifo [ -m Mode ] File 
例 创建一个用户本身可读写,其它用户只读的管道文件k2。
答:mkfifo -m 0644 k2

0ugo(0644) u:文件所有者 g:用户组 o:其他用户 r权限值是4,w权限值是2,x权限值是1

函数mkfifo创建管道
#include <sys/types>
#include <sys/stat.h>
int mkfifo(char *path, mode_t mode);

函数mkfifo创建有名管道,字符串path指定管道文件的路径和名称,参数mode决定了管道文件的访问权限。

管道本身就是文件,所有文件操作适合于管道文件,可按以下步骤应用管道:1)创建管道文件;2)读进程:只读打开管道文件,读管道;3)写进程:只写打开管道文件,写管道;4)关闭管道文件(应用函数close或fclose)。

4、实例:
双进程读写管道,写进程创建FIFO文件,再打开其写端口,然后读取键盘输入并将输入信息发送到管道中,当键盘输入“exit”或“quit”时程序退出。
读进程打开管道文件的读端口,然后从管道中读取信息,并将此信息打印到屏幕上。当从管道中读取到“exit”或“quit”时程序退出。

//写有名管道
#include <stdio.h>   
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>    
extern int errno;
int main(int argc, char *argv[])
{
	FILE *fp;	
	char buf[255];
	if(mkfifo("myfifo", S_IFIFO|0666) < 0 && errno != EEXIST)
	return;
	while(1)	
	{
		if((fp = fopen("myfifo", "w")) == NULL)			
			return;
		gets(buf);
		fputs(buf, fp);
		fputs("\n", fp);
		if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0)	break;
		fclose(fp);
	}
	return 0;
}
//读有名管道

#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
	FILE *fp;	
	char buf[255];
	while(1)	
	{
		if((fp = fopen("myfifo", "r")) == NULL)
			return;
		fgets(buf, sizeof(buf), fp);
		printf("gets:%s", buf);
		if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0)
			break;
		fclose(fp);
	}
	return 0;
}

在这里插入图片描述

五、管道模型
1、“1-1”模型:本模型用于两个进程之间双向通信。
在这里插入图片描述

2、“n-1”模型:适合于非交互式服务进程
在这里插入图片描述

3、“n-1-n”模型 本模型适合于交互式服务系统。
在这里插入图片描述
首先,客户进程将请求报文写入FIFO中,然后,服务进程从公共FIFO中读取客户进程提交的报文信息并进行处理,处理完毕后将应答报文写入客户进程对应的私有FIFO中。最后,客户进程从自己的FIFO中获取报文应答信息,完成整个交互式服务程序。

六、小结
1、虽然无名管道没有路径,但仍为文件,占据i节点和数据块;
2、无名管道没有路径,所以只能通过继承方式操作;
3、无名管道一旦关闭,就不能再次使用;
4、如管道一次性写入小于PIPE_BUF字节的数据,该操作为原子操作,否则拆分再发送;
5、管道最大能存储PIPE_BUF字节数据,一旦达到最大容量,写操作将阻塞;
6、管道以FIFO(先进先出)方式处理数据;
7、管道数据一旦读出,就从管道中删除,所以不具有可再现性。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值