今天我们接着谈Linux系统编程中的进程间的通信,上一节我们讨论了进程的基本操作。这一节我们来讨论一下进程间的通信。
        常见的进程间的通信方式有:无名管道、命名管道、信号、共享内存、消息队列、信号量、套接字。


接下来我们先来谈:
                一、无名管道:
                      1、管道是UNIX系统的IPC的最古老方式,并且多数unix系统都提供此种通信方式。、
                      2、管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
                      3、虽然管道是半双工的,但是我们可以通过创建两个无名管道实现父子进程间的相互通信。我们来看下图常用的编程模型:

图片.png


图片.png

                    4、函数原型:

       #include <unistd.h>

       int pipe(int pipefd[2]);

                    5、上面就是无名管道的一些基本语法,当使用无名管道通信的时候,在父进程创建子进程之前,我们创建的数据都是共享的,所以,通过在fork之前创建管道就可以实现父子进程之间的通信,这里给出一个测试用例,帮助理解如何使用管道:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    pid_t pid = -1;
    char str[BUFSIZE] = {0};
    
    //先实现单双工,父写子读
    int fd[1];
    int res = -1;

    res = pipe(fd);

    if(-1 == res)
    {
        perror("pipe");
        exit(1);
    }

    pid = fork();

    if(pid > 0)
    {
        //父进程,父写子读,就需要在父进程关闭读端,0读1写
        printf("I am Parent\n");
        memset(str,0,BUFSIZE);
        int status = 0;

        //关闭读端
        close(fd[0]);
        strcpy(str,"Hello World Hello Linux\n");
        //向管道写数据
       res =  write(fd[1],str,strlen(str));

       if(-1 == res)
       {
           perror("write");
           exit(1);
       }

       res = wait(status);

       if(-1 == res)
       {
           perror("wait ");
           exit(1);
       }
    }
    else if(0 == pid)
    {
        //子进程,父写子读,在子进程里关闭写端
        printf("I am child\n");

        close(fd[1]);
        memset(str,0,BUFSIZE);

        res = read(fd[0],str,BUFSIZE);
        if(-1 == res )
        {
            perror("resd ");
            exit(1);
        }
        res = write(STDOUT_FILENO,str,strlen(str));
        if(-1 == res)
        {
            perror("write");
            exit(1);
        }
    }

    return 0;
}

上边给出了半双工的无名管道的通信方式,所谓全双工的测试代码,就是在创建进程之前创建两个管道,在父子进程中分别关掉一个读写端,这样就可以实现父子进程间通信。虽然这样实现了父子进程间的通信,我们也能看到一个问题,有名管道只能用到有亲缘关系的父子进程间通信。这样是很不方便的,下来我们来看有名管道:


       二、 FIFO(有名管道)

          a、基础概念:

                    FIFO有时被命名为命名管道。管道只能由相关进程使用,这些相关进程的共同祖先进程创建了管道。通过FIFO不相关的进程也能交换数据。
                    FIFO是一种文件类型。stat结构成员st_mode的编码指明文件是否是FIFO类型,可以用S_ISFIFO宏对其进行测试。
                    b、函数原型:
                        创建FIFO类似于创建文件。
                        #include <sys/types.h>
                        #include <sys/stat.h>
                        int mkfifo(const char *pathname, mode_t mode);
                   一旦已经用了mkfifo创建了一个FIFO,就可用open打开它。其实,一般的文件I/O函数(close、read、write、unlink等)都可以用。 
                    c、FIFO的两种用途:
                        1、FIFO由shell命令使用以便将数据从一条管线传送到另一条,为此无需创建中间临时文件。
                        2、FIFO用于客户进程-服务器进程应用程序中,一再客户进程和服务器进程之间传递数据
                    d、 实例:
                        1、FIFO复制输出流:
                            FIFO可以复制串行管道命令之间的输出流,于是也就不需要写数据到中间磁盘文件中。管道只能用于进程间的线性连接,然而FIFO可以用来非线性连接。
                        2、客户-服务器进程
                    FIFO的另一个应用就是在客户进程和服务器进程之间传输数据。如果有一个服务器进程,它与多个客户进程都可以将其请求写到一个该服务器进程创建的众所周知的FIFO中,这中类型的客户进程-服务器进程通信中使用FIFO的问题是:服务器如何将回答送回各个客户进程。
                不能使用单个FIFO,因为该服务器进程发出对各个客户进程的请求响应,而请求者却不能知道什么时候去读才能恰如其分的读到对它的相应。一种解决方法是每个客户进程都在其请求中包含它的进程ID。然后服务器进程为每个进程创建一个FIFO,所使用的路径名是以客户进程的进程ID为基础的。

                       3、下来我们来看具体代码:

            我们用mkfifo实现进程间简单通信,这里存在一个进程间同步问题,通俗来讲就是当一个进程写入数据后,另一个进程没有读取之前都被自己读取了,进程间的同步我们到后边总结,这里先简单实现两个进程间的通信。       

/**********************************************************
*    > File Name: mkfifo-ser-1.c
*    > Author:xiao_k
*    > Mail:18629086235@163.com 
*    > Created Time: Wed 21 Feb 2018 09:55:21 PM CST
**********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    char buf[BUFSIZE];
    int res = -1,fd = -1;

   res =  mkfifo("./testfifo",0777);

   if(-1 == res)
   {
       perror("mkfifo ");
       exit(1);
   }

   fd = open("./testfifo",O_RDWR|O_EXCL);
   if(-1 == fd)
   {
       perror("open");
   }

   while(1)
   {
       printf("ser-> ");
       memset(buf,0,BUFSIZE);
       scanf("%s",buf);
       if(0 == strcmp(buf,"quit"))
           break;
       write(fd,buf,strlen(buf));
       sleep(2);
       read(fd,buf,BUFSIZE);
            printf("cli-> %s\n",buf);
   }

   if(open("./testfifo",O_EXCL) > 0 )
   {
        if(-1 == (res = unlink("./testfifo")))
        {
            perror("unlink");
            exit(1);
        }
    }
   else
   {
       perror("open ");
       exit(1);
   }

    return 0;
}

/**********************************************************
 *    > File Name: mkfifo-ser-1.c
 *    > Author:xiao_k
 *    > Mail:18629086235@163.com 
 *    > Created Time: Wed 21 Feb 2018 09:55:21 PM CST
 **********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    char buf[BUFSIZE];
    int fd = -1,res= -1;


    fd = open("./testfifo",O_RDWR|O_EXCL);

    if(-1 == fd)
    {
        perror("open");
    }

    while(1)
    {
        memset(buf,0,BUFSIZE);
        read(fd,buf,BUFSIZE);
        printf("ser-> %s\n",buf);
        memset(buf,0,BUFSIZE);
        printf("cli->");
        scanf("%s",buf);
        if(0 == strcmp(buf,"quit"))
            break;
        write(fd,buf,strlen(buf));
        sleep(1);

    }

    if(open("./testfifo",O_EXCL) > 0 )
    {
        if(-1 == (res = unlink("./testfifo")))
        {
            perror("unlink");
            exit(1);
        }
    }
    else
    {
        perror("open ");
        exit(1);
    }
    return 0;
}

            上边就是Linux通信的最基本的两种方式都只是做了最基本的举例,后几种我将分成专题分别总结,总结完了所有专题后,将综合运用。