Linux进程间通信之---管道

1.管道简介

管道是半双工的,数据只能向一个方向流动。需要双方通信时,需要建立起两个管道。一个进程向管道中写的内容被管道另一端的进程读出。如果试图从读端写入数据或者从写端读出数据都会出错。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据(类似与先进先出队列FIFO)。管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等。管道分为无名管道和有名管道。无名管道:也叫匿名管道,只能用于具有亲缘关系的进程间通信。无名管道对于其的两端进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中,其大小是有限的,不同的内核所支持的大小不一样。有名管道:除了具有无名管道特点外,还能在无亲缘关系的进程之间进行通信。有名管道是以实实在在的文件存在于系统中(FIFO的形式),这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作

2.匿名管道相关操作

1)创建:pipe和pipe2函数

函数名称:int pipe(int pipefd[2]);

头文件:#include <unistd.h>

参数说明:int pipefd[2],该数组存放的是匿名管道的读端和写端的地址,pipefd[0]存放的是读端地址,pipefd[1]存放的是写端地址

返回值:创建错误,返回-1,创建成功,返回0

函数说明:该函数用于创建(会阻塞的)匿名管道。对文件的I/O操作函数read/write/close都对管道有效,分别对应读/写/关闭管道。匿名管道没有打开的动作,一旦创建,管道就已经打开,所有一旦调用close函数将管道关闭将无法再次打开。

函数名称:int pipe2(int pipefd[2], int flags);

头文件:同pipe函数

参数说明:int pipefd[2],同pipe函数,int flags,管道的模式,当flags=0时,此时pipe2相当于pipe,当flags=O_NONBLOCK时,创建的匿名管道不会阻塞。

返回值:同pipe函数

2)数据读取和写入规则

数据的读写端都存在时,对于阻塞的匿名管道,将在读动作时阻塞住(读阻塞只对进程中第一个读函数有效,如果进程中有多个读操作,只要第一个解开,其他就不再阻塞),直到有数据写入管道中,而对于非阻塞型匿名管道,则不阻塞,只不过read函数返回-1。如果读取请求的数据大于匿名管道的大小,返回管道中所有的数据。如果请求的字节数目不大于管道的大小,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。

读取数据时,如果管道的写端不存在(在父子进程中都调用了close函数关闭了写端),则认为已经读到了数据的末尾,读函数返回的读出字节数为0

向管道中写入数据时,内核不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果管道已满,读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。只有在管道的读端存在时,向管道中写入数据才有意义。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	int nPipeFd[2] = {0};
	int nRet = 0;
	char cBuff = '0';
	pid_t pid = 0;

	nRet = pipe2(nPipeFd, 0);			
	
	if(0 == nRet)		//Creat PIPE Successfully
	{
		pid = fork();

		if(pid > 0)		//Parent Progress
		{
			while(1)
			{
				printf("This is Parent\n");
				sleep(5);
				write(nPipeFd[1], &cBuff, 1);
			}

			return 1;
		}
		else				//Child Progress:read PIPE and Print the data to Screen
		{
			while(1)
			{
				nRet = read(nPipeFd[0], &cBuff, 1);

				if(0 == nRet)
				{
					printf("This is child,Nothing to read in PIPE\n");
				}
				else if(nRet > 0)
				{
					printf("This is child,The Data Read From PIPE is %c\n", cBuff);
				}
				else
				{
					printf("This is child,Read PIPE Worry\n");			
				}

				sleep(1);
			}
			
			return 2;
		}	
	
	}
	else
	{
		printf("Sorry Creat PIPE Failed\n");
	}

	return 0;
}
执行结果:

This is Parent
This is Parent
This is child,The Data Read From PIPE is 0
结果分析:
这段代码创建的是一个阻塞的无名管道(将nRet = pipe2(nPipeFd, 0); 改为 nRet = pipe(nPipeFd);  也是一样的),父进程每隔5s中往管道中写入数据,子进程每隔1s读取管道中的数据,由于是阻塞的匿名管道,所以子进程在读函数read处阻塞,直到有数据写入到管道中,也就是第一次打印This is Parent后,5s后才会继续执行。

如果将nRet = pipe2(nPipeFd, 0);改为nRet = pipe2(nPipeFd, O_NONBLOCK);创建的就是非阻塞的管道,此时的执行结果为

This is Parent
This is child,Read PIPE Worry
This is child,Read PIPE Worry
This is child,Read PIPE Worry
This is child,Read PIPE Worry
This is child,Read PIPE Worry
This is Parent
This is child,The Data Read From PIPE is 0
This is child,Read PIPE Worry
当子进程执行的read函数时,管道中虽然还没有数据,但是也不阻塞,值不过返回值为-1,即读取失败。当父进程休眠5s后将数据写入管道后,子进程可以成功的读出管道中的数据。

3.有名管道相关操作

1)创建:mkfifo函数

头文件:#include<sys/types.h>
                #include<sys/stat.h>

                #include<fcntl.h>

函数名称:int mkfifo(const char * pathname,mode_t mode);

参数说明:const char * pathname:创建的有名管道(FIFO)的路径

                    mode_t mode:创建的FIFO的权限

返回值:若成功则返回0,否则返回-1,错误原因存于errno中。

函数说明:调用该函数,创建pathname的FIFO,这个IFIFO必须是不存在的,如果是已经存在的FIFO,创建会失败,返回-1。

注意点:实际上,创建一个匿名管道,其权限并不是有mkfifo的第二参数决定的,而是由umask函数的参数cMask和mkfifo的mode一起决定的。其管道的权限=mode&(~cMask),所以为了使其权限是有mkfifo决定,一般在调用mkfifo前先调用umask(0);

2)打开:open函数

头文件:同mkfifo函数

函数名称:int open( const char * pathname, int flags);

参数说明:const char * pathname:需要打开的有名管道的路径

                    int flags:打开方式,与普通文件一样,只是多一个非阻塞标志O_NONBLOCK

返回值:打开成功返回该FIFO的句柄(可用于读写和关闭管道),失败返回-1

函数说明:调用该函数,打开一个已经存在的有名管道,如果该管道不存在,返回-1.

注意点:不要试图在打开有名管道的时候通过设定flags的参数(O_CREAT)来创建一个不存在的有名管道,通过这种方式只能创建一个普通文件,不能创建有名管道。

打开规则:

(a)读写打开都没有设定非阻塞标志:先执行的进程会在打开时阻塞,直到另一个进程打开才解除阻塞。

(b)读写打开都设定了非阻塞标志:进程都不会阻塞,对于读进程,无论是否有进程进行写打开,都可以打开成功;对于写进程,如果没有进程进行了读打开,则打开失败。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#define FIFO_PATH		("/home/hcx/progress/test1.txt")

int main()
{
	pid_t pid = 0;
	int nRet = 0;

	pid = fork();

	if(pid > 0)
	{
		sleep(5);
		nRet = open(FIFO_PATH, O_RDONLY);
		printf("Parent nRet = %d\n", nRet);
		wait(NULL);
	}
	else if(0 == pid)
	{		
		nRet = open(FIFO_PATH, O_WRONLY);		
		printf("Child nRet = %d\n", nRet);
		exit(0);
	}
	else
	{
		printf("fork error\n");
		return -1;
	}

	return 0;
}
执行结果:

先阻塞大约5s,后打印

Parent nRet = 3
Child nRet = 3

结果分析:这段代码中,读打开和写打开都是阻塞型的,先执行的进程会等待另一个(比如先执行子进程,在open那里进程将阻塞,直到父进程直到到open那,此时父进程发现有进程试图以写方式打开管道,所有父不阻塞,同时子进程也解除阻塞,继续执行)。上面的代码做些改动可以测试读写的阻塞和非阻塞的区别。

3)关闭:close函数

头文件:#include <unistd.h>

函数名称:int close(int fd);

参数说明:int fd:需要关闭的有名管道的句柄(即open函数的返回值)

函数说明:关于已经存在的有名管道。

返回值:关闭成功,返回0,否则返回-1,错误原因存于errno中。

注意点:调用完close函数后,原来管道中的数据将被清空,如果此时再将管道打开,并读取数据,将读取不到数据

4)数据的读取规则

(a)没有设定非阻塞标志:造成阻塞的原因有两个:1.当前FIFO内有数据,但是其他进程在读这些数据;2.FIFO内没有数据。解除阻塞的原因是FIFO中有新的数据写入,无论吸入数据量的大小,也无论读操作请求多少数据。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。

(b)设定了非阻塞标志:有进程写打开FIFO,但是FIFO中无数据,则会返回-1

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#define FIFO_PATH		("/home/hcx/progress/test1.txt")

int main()
{
	pid_t pid = 0;
	int nFd = 0;
	int nRet = 0;
	char cBuff = '0';

	pid = fork();

	if(pid > 0)
	{	
		nFd = open(FIFO_PATH, O_WRONLY);
		printf("Parent nFd = %d\n", nFd);
		sleep(3);
		write(nFd, &cBuff, 1);
		wait(NULL);
	}
	else if(0 == pid)
	{	
		nFd = open(FIFO_PATH, O_RDONLY);
		printf("Read Fd  = %d\n", nFd);	
		nRet = read(nFd, &cBuff, 1);	
		printf("Read nRet = %d\n", nRet);
		exit(0);
	}
	else
	{
		printf("fork error\n");
		return -1;
	}

	return 0;
}

执行结果:

Read Fd  = 3
Parent nFd = 3
Read nRet = 1

结果分析:这段代码没说有设定非阻塞标志,当子进程执行到read函数时,由于管道中没有数据,将阻塞,大约过了5s,父进程写一个数据到管道,此时子进程阻塞解除,读取到数据。如果将子进程打开时加非阻塞标志,将不阻塞,read函数返回-1.

4)数据的写入规则

(a)没有设置非阻塞标志:当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
(b)设置了非阻塞标志:当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写

新手学习笔记,如果有错误,请指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值