进程间通信(1)——管道和命名管道

进程间通信包括本地进程间通信网络进程间通信

  • 网络进程间通信:不同计算机的进程间通信,是基于socket进程的通信

  • 本地进程间通信:同一台计算机系统中各进程间的通信,包括下面五种

【Linux下的各种进程间通信方式】

  • 管道
  • 命名管道
  • 消息队列
  • 共享内存
  • 信号量

【1】管道

(1)概述

  • 管道实现数据以数据流的方式在进程间流动。在系统中其相当于文件系统上的一个文件,来缓存所要传输的数据。

  • 在某些特性上又不同于文件,例如,当数据读出后,则管道中就没有数据了,但文件没有这个特性。

  • 顾名思义,匿名管道在系统中没有实名,并不可以在文件系统中以任何方式看到该管道。它只是进程的一种资源,会随着进程的结束而被系统清除

  • 创建一个管道时生成了两个文件描述符,但对于管道中所使用的文件描述符并没有路径名,也就是不存在任何意义上的文件,它们只是在内存中跟某一个索引节点相关联的两个文件描述符

  • shell中,经常使用(l)来连接两个甚至多个命令

(2)管道的创建

Linux下使用pipe函数创建一个匿名管道,函数原型如下:

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

返回: 成功返回0,出错返回-1

fd[2]:长度为2的文件描述符数组

  • fd[0]是读出端的文件描述符
  • fd[1]是写入端的文件描述符
  • 当函数成功返回后,则自动维护了一个从fd[1]到fd[0]的数据通道

(3)管道的关闭

管道的关闭使用的是基于文件描述符close函数。

(4)一个使用管道的例子,create_pipe.c:

程序中先使用函数pipe建立管道,并使用管道传输数据,在程序结束部分,释放掉管道占用的文件资源(两个文件描述符)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
        int fd[2];/*管道的文件描述符数组*/
        char str[256];
        if((pipe(fd)) < 0)
        {
                printf("create the pipe failed!\n");
                exit(1);/*出错退出*/
        }
        write(fd[1],"create the pipe successfully!\n",31);
		/*向管道写入端写入数据*/
        read(fd[0],str,sizeof(str));
		/*从管道输出端读出数据*/
        printf("%s",str);
        printf("pipe file description are %d , %d\n",fd[0],fd[1]);
        close(fd[0]);
        close(fd[1]);
        return 0;
}


运行结果:

hyx@hyx-virtual-machine:~/test$ ./create_pipe
create the pipe successfully!
pipe file description are 3 , 4

(5)管道的读写

使用read和write对管道进行操作

  • 管道读取规则

(1)如果管道写端不存在,则认为读到了数据末尾,读函数返回的读出字节数为0

(2)写端存在时,

  • 若请求字节数 > PIPE_BUF,则返回管道中现有的数据字节数
    (PIPE_BUF在include/linux/limits.h中定义)
  • 若请求字节数 < PIPE_BUF,则返回管道中现有数据字节数(此时管道中数据量小于请求数据量)
    或返回请求字节数(此时管道中数据量不小于请求数据量)
  • 管道写入规则:
  • 管道缓冲区一旦有空闲区域,写进程就会立即试图向管道写入数据,如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞

(6)管道在父子进程中的应用

parent _ pipe _ child.c (使用pipe和fork组合实现父子进程通信)

先使用pipe函数建立管道,再使用fork函数创建子进程

在父子进程中维护管道的数据方向,并在父进程中向子进程发送消息,在子进程中接收消息并输出到标准输出

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <limits.h>
#define BUFSIZE PIPE_BUF /*管道默认一次性读写的数据长度*/
void err_quit(char *msg)
{
        printf(msg);
        exit(1);
}
int main(void)
{
        int fd[2];
        char buf[BUFSIZE] = "hello my child!\n";/*写入管道的缓冲区*/
        pid_t pid;/*定义进程ID的变量*/
        int len;
        if((pipe(fd)) < 0)/*创建管道*/
                err_quit("pipe failed\n");
        if((pid = fork()) < 0)/*创建一个子进程*/
                err_quit("fork failed\n");
        else if(pid > 0)
        {
                close(fd[0]);/*父进程中关闭管道的读出端*/
                write(fd[1],buf,strlen(buf));
                exit(0);
        }
        else
        {
                close(fd[1]);/*子进程中关闭管道的写入端*/
                len = read(fd[0],buf,BUFSIZE);/*子进程从管道中读出数据*/
        if(len < 0)
                err_quit("process failed when read a pipe\n");
        else
                write(STDOUT_FILENO,buf,len);/*输出到标准输出*/
        exit(0);
        }
}

hyx@hyx-virtual-machine:~/test$ hello my child!

(7)管道在兄弟进程间的应用

brother_pipe.c:

  • 管道在兄弟进程间应用时,应该先在父进程中建立管道,然后调用fork函数创建两个子进程,在兄弟子进程中维护管道的数据方向

  • 在父进程中创建管道,并使用fork函数创建2个子进程。在第一个子进程中发送消息到第2个子进程,第2个子进程读出消息并处理。在父进程中,由于并不使用管道通信,所以什么都不做,直接关闭了管道的两端并退出

  • 创建第一个子进程时,并没有关闭管道的两端,创建第二个子进程时,关闭了管道

  • 创建第二个子进程时,子进程可以继承存活的管道,而不是一个两端已经关闭的管道,否则下面的读操作无法完成

      #include <stdlib.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <sys/types.h>
      #include <limits.h>
      #define BUFSIZE PIPE_BUF /*管道默认一次性读写的数据长度*/
      void err_quit(char *msg)
      {
              printf(msg);
              exit(1);
      }
      int main(void)
      {
              int fd[2];
              char buf[BUFSIZE] = "hello my brother!\n";/*缓冲区*/
              pid_t pid;
              int len;
              if((pipe(fd)) < 0) /*创建管道*/
                      err_quit("pipe  failed\n");
              if((fork()) < 0)  /*创建第一个子进程*/
                      err_quit("fork failed\n");
              else if(pid = 0)  /*子进程中*/
              {
                      close(fd[0]); /*关闭不使用的文件描述符*/
                      write(fd[1],buf,strlen(buf));/*写入消息*/
                      exit(0);
              }
              if((pid = fork()) < 0)
                      err_quit("fork failed\n");
              else if(pid > 0)   /*父进程中*/
              {
                      close(fd[0]);
                      close(fd[1]);
                      exit(0);
              }
              else		/*第二个子进程中*/
              {
                      close(fd[1]); /*关闭不使用的文件描述符*/
                      len = read(fd[0],buf,BUFSIZE);/*读取消息*/
                      write("STDOUT_FILENO,buf,len");/*将消息输出到标准输出*/
                      exit(0);
              }
              return 0;
      }
    

【2】命名管道

(1)概述

  • 命名管道(named pipe)也称为FIFO,它是一种文件类型,创建一个FIFO文件类似于创建一个普通文件

  • FIFO解决了只有具有亲缘关系的进程间才能通信。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存放于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据

命名管道区别于管道主要是以下两点:

  • 命名管道可以用于任何两个进程间的通信,而并不限制这两个进程同源,因此命名管道的使用比管道的使用要方便灵活

  • 命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放于内存(使用完毕后消失)。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失

(2)FIFO管道通过mkfifo创建,函数原型:

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

返回:成功返回0,出错返回-1

第一个参数是一个普通的路径名,即创建后FIFO文件的名字

第二个参数与打开普通文件的open()函数中的mode参数相同

如果mkfifo第一个参数是一个已经存在的路径名时,则返回EEXIST错误,所以般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用FIFO,如closeread、write等

(3)关于FIFO的程序示例

create_FIFO.c

演示了如何使用mkfifo创建一个FIFO

  • 程序中从命令行参数中得到一个文件名,然后使用mkfifo函数创建FIFO文件。新创建的FIFO只具有读写权限。由于FIFO文件的特性,所以它被隐性地规定不具有执行权限。

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <errno.h>
      #include <stdio.h>
      #include <stdlib.h>
      int main(int argc,char *argv[])
      {
      	mode_t mode = 0666;
      	if(argc != 2)
      	{
      		printf("USEMSG:Create_FIFO{FIFO name}\n");
      		exit(1);
      	}
      	if((mkfifo(argv[1],mode)) < 0)
      	{
      		perror("failed to mkfifo!\n");
      		exit(1);
      	}
      	else
      	{
      		printf("you successfully create a FIFO name is:%s\n",argv[1]);
      	}
      	return 0;
      }
      
    
      运行结果:
    
      hyx@hyx-virtual-machine:~/test$ ./create_FIFO
      USEMSG:Create _FIFO{FIFO name}
      hyx@hyx-virtual-machine:~/test$ ./create_FIFO testfifo
      you successful create a FIFO  name is : testfifo
      hyx@hyx-virtual-machine:~/test$ 
    

(4)命名管道的读写

  • 与普通文件类似,可以使用系统调用open打开一个命名管道,使用read和write函数对命名管道进行读写,使用close关闭一个命名管道

(5)使用FIFO进行两个进程间通信的例子

write_fifo.c和read_fifo.c

  • 在程序write_fifo.c中打开一个名为fifo1的FIFO文件,并分10次向这个FIFO中写入数据。在程序read_fifo.c中先打开fifo1文件,然后读取里面的数据并输出到标准输出。

  • 运行程序时,要先使用 mkfifo -m 666 fifo1 创建fifo1文件

  • 因为分别进行读和写,所以在两个shell中进行

write_fifo.c

	#include <sys/types.h>
	#include <sys/stat.h>
	#include <unistd.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <limits.h>
	#include <fcntl.h>
	#define BUFES PIPE_BUF
	int main(void)
	{
	        int fd;
	        int n,i;
	        char buf[BUFES];
	        time_t tp;
	        printf("I am %d\n",getpid());
	        if((fd = open("fifo1",O_WRONLY)) < 0)
	        {
	                printf("Open failed!\n");
	                exit(1);
	        }
	        for(i = 0;i < 10;i++)
	        {
	                time(&tp);/*提取系统当前时间*/
	                n = sprintf(buf,"write_fifo %d sends %s",getpid(),ctime(&tp));/*使用sprintf函数向buf中格式化写入进程ID和时间值*/
	                printf("send msg:%s\n",buf);
	                if((write(fd,buf,n+1)) < 0)
	                {/*写入到FIFO中*/
	                        printf("write failed!\n");
	                        close(fd);/*关闭FIFO文件*/
	                        exit(1);
	                }
	                        sleep(3);/*进程睡眠3秒,便于观察*/
	        }
	                close(fd);/*关闭FIFO文件*/
	                exit(0);
	}

read_fifo.c

	#include <sys/types.h>
	#include <sys/stat.h>
	#include <unistd.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <limits.h>
	#include <fcntl.h>
	#define BUFES PIPE_BUF
	int main(void)
	{
	        int fd;
	        int len;
	        char buf[BUFES];
	        mode_t mode = 0666;  /*FIFO文件的权限*/
	        if((fd=open("fifo1",O_RDONLY)) < 0)/*打开FIFO文件*/
	        {
	                printf("open failed!\n");
	                exit(1);
	        }
	        while((len = read(fd,buf,BUFES)) > 0)/*开始进行通信*/
	        printf("read_fifo read:%s",buf);
	        close(fd);/*关闭FIFO文件*/
	        exit(0);
	}

运行结果:

	hyx@hyx-virtual-machine:~/test$ mkfifo -m 666 fifo1
	hyx@hyx-virtual-machine:~/test$ ./write_fifo
	I am 2964
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:26 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:29 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:32 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:35 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:38 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:41 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:44 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:47 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:50 2015
	
	send msg:write_fifo 2964 sends Thu Dec  3 16:29:53 2015
	
	
	
	hyx@hyx-virtual-machine:~/test$ ./read_fifo
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:26 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:29 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:32 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:35 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:38 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:41 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:44 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:47 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:50 2015
	read_fifo read:write_fifo 2964 sends Thu Dec  3 16:29:53 2015
posted @ 2015-12-08 23:32  adacn  阅读(1271)  评论(0编辑  收藏  举报