【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例


匿名管道(pipe)


管道是一种最基本的IPC机制,由pipe函数创建。


头文件:#include

函数声明:int pipe( int pipefd[2] )



描述::调用 pipe() 函数时在内核中开辟一块称为管道的缓冲区用于通信。然后通过参数pipefd传出给用户程序两个描述符。pipefd[0] 指向管道的读端,pipefd[1]指向管道的写端。  管道在用户程序看来就像一个打开的文件。通过系统调用read()和write(), 在管道中传输数据。 pipe调用成功返回0,失败则返回-1.


文章的最后面有一些关于进程等待的宏描述。



示意图:

(1):父进程创建管道

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第1张图片

(2):父进程调用fork()函数创建出子进程

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第2张图片

(3):父进程关闭读端【close( pipefd[0])】,子进程关闭写端【close( pipefd[1])】

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第3张图片



1.父进程调用pipe开辟管道,取得两个指向管道的文件描述符。

2.当父进程调用fork 创建子进程时,子进程会把父进程的文件描述符也一同拷贝过去。

3.父进程关闭读端,子进程关闭写端就可以实现父进程向子进程发送数据。。父进程关闭写端,子进程关闭读端。可以实现子进程向父进程写。

4.也就是说数据只能向一个方向流动。需要双向通信时,要建立起两个管道。


正常通信代码:

/*************************************************************************
#	> File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
 ************************************************************************/

#include
#include  // pipe()  fork()
#include  // strlen()

int main()
{

	int mypipe[2] = {0, 0};// [0] read [1]  write

	if(pipe(mypipe) < 0)
	{
		printf("pipe erroe!\n");
		return 1;
	}

	pid_t id = fork();

	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if (id == 0)
	{ // child -> write 
		close(mypipe[0]);  // close read 

		const char * msg = "Hello World!";
		int count = 5;

		while(count--)
		{
			write(mypipe[1], msg, strlen(msg) + 1);
		}

	   	close(mypipe[1]);
	}
	else
	{ // father -> read
		close(mypipe[1]);   // close write
		int buf[128];
		int count = 5;
		while(count--)
		{
			ssize_t _s = read(mypipe[0], buf, sizeof(buf));
			if( _s > 0)
			{ // read sucess
				printf("%d: child -> father: %s \n", 5-count ,buf);
			}
			else
			{
				printf("pipe write is close\n");
				break;
			}
		}	

		close(mypipe[0]);
	}
	return 0;
}

打印结果:

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第4张图片



1、如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取后,再次read会返回零。

将代码修改为如下:子进程提前关闭写端,而父进程while(1)一直在读

#include
#include  // pipe()  fork()
#include  // strlen()

int main()
{

	int mypipe[2] = {0, 0};// [0] read [1]  write

	if(pipe(mypipe) < 0)
	{
		printf("pipe erroe!\n");
		return 1;
	}

	pid_t id = fork();

	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if (id == 0)
	{ // child -> write 
		close(mypipe[0]);  // close read 

		const char * msg = "Hello World!";
		int count = 5;

		while(count--)
		{
			write(mypipe[1], msg, strlen(msg) + 1);
			if(count == 0)
			{
				close(mypipe[1]);  //  写五条后  关闭文件描述符
			}
		}

	   	close(mypipe[1]);
	}
	else
	{ // father -> read
		close(mypipe[1]);   // close write
		int buf[128];
		int count = 5;
		while(1)   // 修改为一直读取
		{
			ssize_t _s = read(mypipe[0], buf, sizeof(buf));
			if( _s > 0)
			{ // read sucess
				printf("%d: child -> father: %s \n", 5-count ,buf);
			}
			else if (_s == 0) 
			{
				printf(" end of file  read return is %d\n", _s);
				break;
			}
			else
			{
				printf("read error\n");
				break;
			}
		}	

		close(mypipe[0]);
	}
	return 0;
}

打印结果:

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第5张图片


2、如果指向管道写端的文件描述符没关闭,而 持有管道写端的进程也没有向管道中写数据,这时有进程从管道读取数据,那么管道中剩余的数据都被读取后,再次读取会阻塞。直到管道中有数据可读才读取数据并返回。

/*************************************************************************
#	> File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
 ************************************************************************/

#include
#include  // pipe()  fork()
#include  // strlen()
#include
#include

int main()
{

	int mypipe[2] = {0, 0};// [0] read [1]  write

	if(pipe(mypipe) < 0)
	{
		printf("pipe erroe!\n");
		return 1;
	}

	pid_t id = fork();

	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if (id == 0)
	{ // child -> write 
		close(mypipe[0]);  // close read 

		const char * msg = "Hello World!";
		int count = 5;

		while(count--)
		{
			write(mypipe[1], msg, strlen(msg) + 1);
			sleep(1);  
			if(count == 0)
			{
				sleep(5);   //此时文件描述符没关闭 也不写数据 
				count = 3; //5s 后读端继续读取 
			}
		}

	   	close(mypipe[1]);
	}
	else
	{ // father -> read
		close(mypipe[1]);   // close write
		int buf[128];
		int count = 5;
		while(1)   
		{
			ssize_t _s = read(mypipe[0], buf, sizeof(buf));//父进程一直读取  子进程slep时 父进程等待 sleep结束子进程开始写 父进程继续读取 
			if( _s > 0)
			{ // read sucess
				printf("%d: child -> father: %s \n", count ,buf);
			}
			else if (_s == 0) 
			{
				printf(" end of file  read return is %d\n", _s);
				break;
			}
		}	

	}
	return 0;
}

打印结果:

打印出前五条后,子进程作为写方sleep停止写入,而父进程此时作为读方,继续等待,知道子进程开始写入。

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第6张图片


3、如果所有指向管道读端的文件描述符都被关闭了,这时有进程向管道写,那么该进程会收到信号SIGPIPE,导致进程异常终止。

/*************************************************************************
#	> File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
 ************************************************************************/

#include
#include  // pipe()  fork()
#include  // strlen()
#include
#include

int main()
{

	int mypipe[2] = {0, 0};// [0] read [1]  write

	if(pipe(mypipe) < 0)
	{
		printf("pipe erroe!\n");
		return 1;
	}

	pid_t id = fork();

	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if (id == 0)
	{ // child -> write 
		close(mypipe[0]);  // close read 

		const char * msg = "Hello World!";
		int count = 5;

		while(1)
		{
			write(mypipe[1], msg, strlen(msg) + 1); // 写端一直在写
			sleep(1);  
		}

	   	close(mypipe[1]);
	}
	else
	{ // father -> read
		close(mypipe[1]);   // close write
		int buf[128];
		int count = 5;
		while(1)   
		{
			count--;
			ssize_t _s = read(mypipe[0], buf, sizeof(buf));
			if( _s > 0)
			{ // read sucess
				printf("%d: child -> father: %s \n", count ,buf);
			}
			else if (_s == 0) 
			{
				printf(" end of file  read return is %d\n", _s);
				break;
			}
			if(count == 0)
			{
				close(mypipe[0]); // 关闭读端
				break;
			}
		}	
		int status = 0;
		ssize_t ret = waitpid(id, &status, 0);
		if(ret>0&&WIFSIGNALED(status) ) // 等待子进程 等待成功且子进程异常终止则为真
		{
			printf("status: %d\n",WTERMSIG(status));// 返回一个信号编号
		}


	}
	return 0;
}
【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第7张图片

我们可以看到返回13是:SIGPIPE

【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第8张图片

4、如果指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读取数据,这时有进程向管道写端写数据,那么在管道被写满时,再次write会阻塞,直到有空位置才继续写入数据。



几个关于进程等待的宏

    WIFEXITED(status) :若正常终止子进程返回的状态,则为真。  用来查看进程是否正常退出。

WEXITSTATUS(status)若 WIFEXIFED(status)非零,提取子进程退出码。 用来查看进程的退出码。

如果进程不是正常退出,即WIFEXITED返回0,那么这个值就没有意义。



WIFSIGNALED(status)若为子进程异常终止返回状态(收到一个未捕捉的信号),则为真。如上面测试(3)中使用捕捉子进程异常退出信号

WTERMSIG(status):若WIFSIGNALED(status)非零,则返回一个信号编号,linux可以用kill  -l 命令查看信号编号列表。



WIFSTOPPED(status):若子进程意外终止,则为真。

WTOPSIG(status):若WIFSTOPPED为真,则返回一个信号编号。




命名管道(pipe)


概念:管道的一个不足之处是没有名字,只能用于具有亲缘关系的进程间通信,在命名管道提出后,就克服了该限制。命名管道是一个设备文件,因此,及时进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。而且总是按照先进先出的原则,第一次被写入的数据首先从管道中读出。



命名管道的创建:Linux有两种方式创建命名管道。第一种是在Shell下交互地建立一个命名管道,第二种是在程序中使用系统函数建立命名管道。Shell下可以使用命令  mknod或者 mkfifo。系统函数有mknod和mkfifo。


头文件:

      #include

      #include

定义:

     int mknod(const char *path, mode_t mode, dev_t dev);

     int mkdifi(const char* path , mode_t mode);


path: 创建命名管道的全路径名

mode:为创建的命名管道的模式

dev : 是设备值,取决于文件创建的种类。


返回值:调用成功返回0,失败返回-1.


使用举例:


umask(0);
if(mknod("./fifo", S_IFIFO|0666) == -1)
{
	perror("mknod error");
	exit(1);	
}



umask(0);
if(mkfifo("./fifo", S_IFIFO|0666) == -1)
{
	perror("mkfifo error");
	exit(1);	
}


"S_IFIFO|0666" 指明创建一个命名管道且存储权限位0666.

管道的几种打开方式:

O_RDWR   读写方式  不会阻塞

O_RDONLY   读模式

O_WRONLY   写模式 


一个命名管道的使用实例:


client代码

#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
	int _o = open("./fifo",O_WRONLY);
	if(_o == -1)
	{
		perror("open\n");
		return 2;
	}
		 
	
	char buf[128];
	while(1)
	{
		printf("请输入聊天内容:");
		fflush(stdout);
		ssize_t _s = read(0, buf, sizeof(buf));
		if(_s > 0)
		{
			buf[_s] = '\0';
			write(_o, buf,strlen(buf));
		}
	}
	close(_o);
	return 0;
}

server代码:

#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
	umask(0);
	
	int _pipe = mkfifo("./fifo", 0666|S_IFIFO);
	if(_pipe < 0)
	{
		perror("mkfifo\n");
		return 1;
	}

	
	int _o = open("./fifo",O_RDWR);
	//int _o = open("./fifo",O_RDONLY);
	if(_o == -1)
	{
		perror("open\n");
		return 2;
	}
		 
	
	char buf[128];
	while(1)
	{// server read
		ssize_t _s = read(_o, buf, sizeof(buf));
		if(_s > 0)
		{
			buf[_s-1] = '\0';
			printf("client #: %s\n", buf);

		}
		else if (_s == 0)
		{
			printf("client quit, I should quit!\n");
			break;
		}
		else
		{
			perror("read\n");
		}

	}
	close(_o);
	return 0;
}



通信成功截图


【Linux】进程间通信(IPC)之匿名管道和命名管道以及测试用例_第9张图片


总结:

1、管道是一个环形队列缓冲区,允许两个进程以生产者消费者模型进程通信。是一个先进先出(FIFO)队列,由一个进程写,而由另一个进程读。

2、管道在创建时获得一个固定大小的字节数。当一个进程试图往管道中写时,如果有足够的空间,则写请求立即被执行,否则该进程被阻塞。如果一个进程试图读取的字节数多于当前管道中的字节数,也将被阻塞。

3、操作系统强制实行互斥,只能有一个进程可以访问管道。

4、只有有血缘关系(父子关系)的进程才可以共享匿名管道,不相关的进程只能共享命名管道。


你可能感兴趣的:(linux)