• 进程间通信(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!
    ");
                    exit(1);/*出错退出*/
            }
            write(fd[1],"create the pipe successfully!
    ",31);
    		/*向管道写入端写入数据*/
            read(fd[0],str,sizeof(str));
    		/*从管道输出端读出数据*/
            printf("%s",str);
            printf("pipe file description are %d , %d
    ",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!
    ";/*写入管道的缓冲区*/
            pid_t pid;/*定义进程ID的变量*/
            int len;
            if((pipe(fd)) < 0)/*创建管道*/
                    err_quit("pipe failed
    ");
            if((pid = fork()) < 0)/*创建一个子进程*/
                    err_quit("fork failed
    ");
            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
    ");
            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!
      ";/*缓冲区*/
                pid_t pid;
                int len;
                if((pipe(fd)) < 0) /*创建管道*/
                        err_quit("pipe  failed
      ");
                if((fork()) < 0)  /*创建第一个子进程*/
                        err_quit("fork failed
      ");
                else if(pid = 0)  /*子进程中*/
                {
                        close(fd[0]); /*关闭不使用的文件描述符*/
                        write(fd[1],buf,strlen(buf));/*写入消息*/
                        exit(0);
                }
                if((pid = fork()) < 0)
                        err_quit("fork failed
      ");
                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}
      ");
        		exit(1);
        	}
        	if((mkfifo(argv[1],mode)) < 0)
        	{
        		perror("failed to mkfifo!
      ");
        		exit(1);
        	}
        	else
        	{
        		printf("you successfully create a FIFO name is:%s
      ",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
    ",getpid());
    	        if((fd = open("fifo1",O_WRONLY)) < 0)
    	        {
    	                printf("Open failed!
    ");
    	                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
    ",buf);
    	                if((write(fd,buf,n+1)) < 0)
    	                {/*写入到FIFO中*/
    	                        printf("write failed!
    ");
    	                        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!
    ");
    	                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
  • 相关阅读:
    不同的ospf进程发布互联网段可以互通
    大数分解
    主席树(非权值)
    块状数组
    Codeforces Round #744 (Div. 3) G. Minimal Coverage
    记录一种从天而降的掌法(动态维护中位数的方法)
    快速统计二进制中1的数量
    网络流(小常数)
    矩阵快速幂
    米勒罗宾素性检验
  • 原文地址:https://www.cnblogs.com/myidea/p/5031471.html
Copyright © 2020-2023  润新知