Linux 进程间通信基础(四)--fifo管道

最近正好有一些空余时间,在这里总结一下曾经使用过的Linux进程间通信的几种方法,贴出来帮助有需要的人,也有助于自己总结经验加深理解。上一次我们梳理了pipe管道的相关知识,这一次梳理fifo管道。

(一)概念

我们先来简单说一下管道的概念。

管道其实是一个很形象的表示术语。当一个进程把它的数据流连接到另一个进程时,我们就说他们构架了管道连接。管道通常不是杂乱的,是能单向。意思就是管道通常只能把一个进程的输出连接到另一个进程的输入,而不能把输入连接输入,输出连接输出。可以把进程想象成一个水管,而其中流动的水就是数据流,我们我们想把一个水管中的水引入另外一个水管,那么只能在他们中间架设小型的水管,而且被引入的水管一端一定要低于引入的水管一端。这个水管就像是管道,从一个进程的输出端连接到领一个进程的输入端。可以参考下面这张简图:


管道其实有很多种,最常用的应该就是shell中的"|"。他其实就是一个管道符,将前面的表达式的输出,引入后面表达式当作输入,比如我们常用的"ps aux|grep ssh"可以查看ssh的相关进程。但是我们常用在进程间通信管道的有两种,一种是pipe管道,又可以叫做亲族管道,与之对应的则是fifo管道,又可以叫做公共管道。

接下来我们来说一下fifo管道。

我们之前曾经记述了pipe管道,他已经可以完成在两个进程之间通信的任务,不过它似乎完成的不够好,也可以说是不够彻底。它只能在两个有亲戚关系的进程之间进行通信,这就大大限制了pipe管道的应用范围。我们在很多时候往往希望能够在两个独立的进程之间进行通信,这样就无法使用pipe管道,所以一种能够满足独立进程通信的管道应运而生,就是fifo管道。

fifo管道的本质是操作系统中的命名文件,当然Linux的理念就是万物皆文件,它在操作系统中以命名文件的形式存在,我们可以在操作系统中看见fifo管道,在你有权限的情况下,甚至可以读写他们。

(二)在操作系统中使用fifo管道

扫描二维码关注公众号,回复: 2436801 查看本文章

在进入代码之前,我们可以现在操作系统中直观的观察一下fifo的管道使用过程。在操系统中我们可以完成我们在c语言中完成的一切,只不过是通过使用shell命令行的方式。那么我们先来创建一个fifo管道,在shell中可以通过mkfifo命令来创建fifo管道文件,我们先来创建一个:

root@Server:/home/root/workspace/pipe/fifo# ls
root@Server:/home/root/workspace/pipe/fifo# mkfifo examplefifo
root@Server:/home/root/workspace/pipe/fifo# ls
examplefifo

可以看到我们的目录下面就出现了一个examplefifo的管道文件,我么你使用"ls -l"命令查看一下:

root@Server:/home/root/workspace/pipe/fifo# ls -l
total 52
prw-r--r-- 1 root root     0 May 23 15:07 examplefifo

注意到这个文件的开头是一个p,这就表示该文件是一个管道文件。那么我们现在尝试利用它进行数据传输,首先我们尝试把它里面的东西都输出出来,可以使用"cat"命令,将管道的输出打印到屏幕上:

root@Server:/home/root/workspace/pipe/fifo# cat < examplefifo 

可以看到现在管道里还没有任何数据,所以程序处于卡死状态,当管道里有数据的时候它就会将管道中的数据打印到屏幕上,那么我们现在重新启动一个终端,尝试向管道中写入一些数据,可以使用"echo"命令:

root@Server:/home/root/workspace/pipe/fifo# echo "hello" > examplefifo

我们尝试向管道中输入"hello",然后我们再来看下另外一个终端:

root@Server:/home/root/workspace/pipe/fifo# cat < examplefifo 
hello

屏幕上打印出了"hello",这是读取到了我们在另一个终端的输入。可以看到我们的管道可以在两个终端之间传输数据了。

(三)使用fifo管道进行线程通信

现在我们尝试在程序中使用fifo管道,我们可以使用mkfifo()系统调用来创建一个命名管道,然后同样适用read()和write()函数读取和写入其中的数据(记得我们说过fifo管道的本质也是文件)。

int mkfifo( const char* filename, mode_t mode );

其中,

--filename是你要创建的fifo管道的名字,操作系统是通过管道的路径名来区分不同的管道的(记得我们说过fifo管道的本质也是文件)。

--mode是你要以何种权限来创建这个管道,本文中我们为了方便理解将它设置为"0777",即所有人都可以读写它,但这实际上是不安全的。

当创建了一个fifo管道之后,你的程序还需要打开它,使用"open()"函数,打开一个已经创建的管道;

int open( const char* pathname, int oflag );

其中,

--pathname是你要打开的管道文件的路径。

--oflag是你要以何种模式打开,这个参数有三个宏可供选择,分别是"O_RDONLY"(只读),"O_WRONLY"(只写),"O_NONBLOCK"(不阻塞)。但是他们三个不是互相独立的填入参数内的,其中"O_RDONLY"(只读)和"O_WRONLY"(只写)可以独立作为参数,而"O_NONBLOCK"(不阻塞)只能跟在其他两个参数的后面,作为附加条件,所以就有四种组合方式:

1)"O_RDONLY":以只读的方式打开一个管道文件,同时进程将会阻塞,直到有另一个进程以只写的方式打开这个管道文件。

2)"O_RDONLY | O_NONBLOCK":以只读的方式打开一个管道文件,但是进程不会阻塞。

3)"O_WRONLY":以只写的方式打开一个管道文件,同时进程将会阻塞,直到有另一个进程以只读的方式打开这个管道文件。

4)"O_WRONLY | O_NONBLOCK":以只写的方式打开一个管道文件,但是进程不会阻塞。

现在我们来尝试写一个fifo的管道的服务器端,我们先创建一个管道,然后以只读的方式打开它,等待客户连接,当有客户链接上以后就会循环的读取管道中的数据,我们创建一个"fifo_server.c"文件:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define FIFO_NAME "myfifo"

int main()
{
	int result;

	int fifo_fd;
	char buffer[32];
	int buffer_len;

	/* 先删除之前可能遗留的管道文件,然后再次创建它 */
	unlink( FIFO_NAME );
	result = mkfifo( FIFO_NAME, 0777 );
	if( result != 0 )
	{
		printf( "error:can't create a fifo.\n" );
		return 1;
	}

	/* 以只读的方式打开管道文件 */
	fifo_fd = open( FIFO_NAME, O_RDONLY );
	if( fifo_fd < 0 )
	{
		printf( "error:can't open a fifo.\n" );
		return 1;
	}

	/* 循环从管到文件中读取数据 */
	do
	{
		memset( buffer, 0, 32 );
		buffer_len = read( fifo_fd, buffer, 31 );
		buffer[buffer_len] = '\0';
		printf( "read:%s\n", buffer );
	}
	while( memcmp( buffer, "close", 5 ) != 0 );

	close( fifo_fd );
	unlink( FIFO_NAME );

	return 0;
}

现在我们来写客户端,客户端会判断一下管道是否存在,如果存在则以只写的方式打开它,然后可以循环的向管道中写入数据,我们来创建一个"fifo_client.c":

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define FIFO_NAME "myfifo"

int main()
{
	int result;

	int fifo_fd;
	char buffer[32];
	int buffer_len;

	/* 判断一下管道文件是否存在,不存在就退出程序 */
	result = access( FIFO_NAME, F_OK );
	if( result == -1 )
	{
		printf( "error:can't find the fifo.\n" );
		return 1;
	}

	/* 以只写的方式打开管到文件 */
	fifo_fd = open( FIFO_NAME, O_WRONLY );
	if( fifo_fd < 0 )
	{
		printf( "error:can't open a fifo.\n" );
		return 1;
	}

	/* 循环向管到文件中写入数据 */
	do
	{
		memset( buffer, 0, 32 );
		printf( "Please input something:" );
		scanf( "%s", buffer );
		buffer_len = write( fifo_fd, buffer, strlen( buffer ) );
		printf( "write:%s\n", buffer );
	}
	while( memcmp( buffer, "close", 5 ) != 0 );	

	close( fifo_fd );
	unlink( FIFO_NAME );

	return 0;
}

好的,现在我们来编译一下这两个文件,并且尝试启动服务器:

root@Server:/home/root/workspace/pipe/fifo# gcc fifo_server.c -o fifos
root@Server:/home/root/workspace/pipe/fifo# gcc fifo_client.c -o fifoc
root@Server:/home/root/workspace/pipe/fifo# ./fifos

现在我们来尝试利用客户端发送信息,我们重新启动一个终端,在上面执行客户端,尝试输入一些数据,最后输入"close"来关闭服务器和客户端:

root@Server:/home/root/workspace/pipe/fifo# ./fifoc
Please input something:hello
write:hello
Please input something:hi
write:hi
Please input something:areyouok?
write:areyouok?
Please input something:funthinkyou
write:funthinkyou
Please input something:close
write:close
root@Server:/home/root/workspace/pipe/fifo# 

我们再开看一下服务器那边的情况:

root@Server:/home/root/workspace/pipe/fifo# ./fifos
read:hello
read:hi
read:areyouok?
read:funthinkyou
read:close
root@Server:/home/root/workspace/pipe/fifo# 

可以看到服务器收到了我们从客户端发送的所有数据。两个进程能够正常的使用管道进行通信了。

(四)一个服务器-多客户端双向通信的例子

我们之前在记述pipe管道是曾经提到过,利用管道进行数据交互的最好方法就是创建两个管道,而一个管道只负责一个方向通信。这样是因为我们无法梳理数据的读写顺序,尤其是在拥有多个客户端的情况下。也就是说我们无法保证自己写入的数据不被自己读取,或者是自己想要获得数据不被他人读取。这个法则对fifo管道也同样适用。

管道通信往往只应用于进程间的简单数据交流,不需要向互联网通信那样支持多客户端高并发等等,只需要读写对应的文件就可以了。这里就不再过多的描述,但是在下面给出一个使用fifo管道的服务器多客户端的例子,以便于自己以后或者有需要的人进行参考。我们首先来写服务器端,创建文件"fifo_server2.c":

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "fifo_server2.h"

int main()
{
	int result;

	int server_fd;
	int client_fd;
	char fifo_client[32];
	FIFO_DATA read_data;
	FIFO_DATA write_data;

	/* 先尝试删除遗留的fifo文件,然后创建server的fifo文件 */
	unlink( FIFO_SERVER_NAME );
	result = mkfifo( FIFO_SERVER_NAME, 0777 );
	if( result != 0 )
	{
		printf( "error:can't create a fifo.\n" );
		return 1;
	}

	do
	{
		/* 以只读方式打开服务器管道 */
		server_fd = open( FIFO_SERVER_NAME, O_RDONLY );
		if( server_fd < 0 )
		{
			printf( "error:can't open a fifo.\n" );
			return 1;
		}
		
		/* 读取服务器管道的数据 */
		memset( &read_data, 0, sizeof(read_data) );
		read( server_fd, &read_data, sizeof(read_data) );
		printf( "receive:%d-%s\n", read_data.thread_pid, read_data.data );

		/* 组建客户端管道名 */
		memset( fifo_client, 0, sizeof(fifo_client) );
		sprintf( fifo_client, FIFO_CLIENT_NAME, read_data.thread_pid );

		/* 关闭客户端管道 */
		close( server_fd );

		/* 以只写方式打开客户端管道 */
		client_fd = open( fifo_client, O_WRONLY );
		if( client_fd < 0 )
		{
			printf( "error:received a message from a client but have no fifo file to reply it.\n" );
		}

		/* 向客户端管道发送数据 */
		memset( &write_data, 0, sizeof(write_data) );
		write_data.thread_pid = getpid();
		strcpy( write_data.data, "OK!" );
		result = write( client_fd, &write_data, sizeof(write_data) );

		/* 关闭server管道 */
		close( client_fd );
	}
	while( memcmp( read_data.data, "closeserver", 10 ) != 0 );
	
	unlink( FIFO_SERVER_NAME );

	return 0;
}

接下来来写客户端,创建文件"fifo_client2.c":

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "fifo_server2.h"

int main()
{
	int result;

	int server_fd;
	int client_fd;
	FIFO_DATA read_data;
	FIFO_DATA write_data;

	char fifo_client[32];

	/* 客户端管道名字包含自己的线程pid,创建客户端管道 */
	memset( fifo_client, 0, 32 );
	sprintf( fifo_client, FIFO_CLIENT_NAME, getpid() );
	result = mkfifo( fifo_client, 0777 );
	if( result != 0 )
	{
		printf( "error:can't create a fifo.\n" );
		return 1;
	}

	do
	{
		/* 以只写方式打开服务器管道 */
		server_fd = open( FIFO_SERVER_NAME, O_WRONLY );
		if( server_fd < 0 )
		{
			printf( "error:can't find the server fifo.\n" );
			return 1;
		}

		/* 向服务器管道发送数据 */
		memset( &write_data, 0, sizeof(write_data) );
		write_data.thread_pid = getpid();
		printf( "Please input something:" );
		scanf( "%s", write_data.data );
		write( server_fd, &write_data, sizeof(write_data) );

		/* 关闭server管道 */
		close( server_fd );

		/* 以只读方式打开客户端管道 */
		client_fd = open( fifo_client, O_RDONLY );
		if( client_fd < 0 )
		{
			printf( "error:can't open a fifo.\n" );
			close( server_fd );
			return 1;
		}

		/* 从客户端管道读取服务器返回的数据 */
		memset( &read_data, 0, 32 );
		result = read( client_fd, &read_data, sizeof(read_data) );
		printf( "receive:%s\n", read_data.data );

		/* 关闭客户端管道 */
		close( client_fd );
	}
	while( memcmp( write_data.data, "close", 5 ) != 0 );

	unlink( fifo_client );

	return 0;
}

现在我们来试验一下我们的代码,首先来编译一下,然后运行服务器:

root@Server:/home/root/workspace/pipe/fifo# gcc fifo_server2.c -o fifos2
root@Server:/home/root/workspace/pipe/fifo# gcc fifo_client2.c -o fifoc2
root@Server:/home/root/workspace/pipe/fifo# ./fifos2

然后我们再打开一个终端,启动客户端,并尝试发送一些数据:

root@Server:/home/root/workspace/pipe/fifo# ./fifoc2 
Please input something:hello
receive:OK!
Please input something:hi
receive:OK!
Please input something:no
receive:OK!
Please input something:close
receive:OK!
root@Server:/home/root/workspace/pipe/fifo# 

我们再来看下服务器的输出,因为要支持多客户端,所以我们打印出了客户端的进程号:

root@Server:/home/root/workspace/pipe/fifo# ./fifos2
receive:2753-hello
receive:2753-hi
receive:2753-no
receive:2753-close

好了,现在我们尝试同时使用两个客户端与服务器进行通信:

root@Server:/home/root/workspace/pipe/fifo# ./fifoc2 
Please input something:iamcomming
receive:OK!
Please input something:mum
receive:OK!
Please input something:donotala
receive:OK!
Please input something:close
receive:OK!
root@Server:/home/root/workspace/pipe/fifo# 
root@Server:/home/root/workspace/pipe/fifo# ./fifoc2
Please input something:iamcommingtoo
receive:OK!
Please input something:fuck
receive:OK!
Please input something:okk
receive:OK!
Please input something:close
receive:OK!
root@Server:/home/root/workspace/pipe/fifo# 

来看一下服务器端:

root@Server:/home/root/workspace/pipe/fifo# ./fifos2
receive:2759-iamcomming
receive:2760-iamcommingtoo
receive:2759-mum
receive:2760-fuck
receive:2759-donotala
receive:2760-okk
receive:2759-close
receive:2760-close

可以看到,两个客户端都在与服务器端通信。

现在我们打开一个客户端关闭服务器:

root@Server:/home/root/workspace/pipe/fifo# ./fifoc2 
Please input something:closeserver
receive:OK!
root@Server:/home/root/workspace/pipe/fifo# 
root@Server:/home/root/workspace/pipe/fifo# ./fifos2
receive:2759-iamcomming
receive:2760-iamcommingtoo
receive:2759-mum
receive:2760-fuck
receive:2759-donotala
receive:2760-okk
receive:2759-close
receive:2760-close
receive:2761-closeserver
root@Server:/home/root/workspace/pipe/fifo# 

那么这样呢,这个例子就可以正常工作了。



猜你喜欢

转载自blog.csdn.net/hyklose/article/details/80419450