zoukankan      html  css  js  c++  java
  • [进程通信] 进程间通信 之 管道

    所谓“进程间通信(IPC,inter-process communication)”,按照其目的讲就是让进程之间能够“共享数据”,“传输数据”,“事件通知”,“进程通知”等,我所知道的一共有“管道” “信号” “消息(报文)” “共享内存” “套接字” 这几种方式,我们会挨个挨个说,今天就说说管道。

    所谓管道嘛,顾名思义类似于我们生活中的水管,只不过其中流动的是“数据”或者说一个一个字节,只能单向流动的我们称为“半双工”,能双向流动的称为“全双工”,其有两个端点,数据流入的那一端称为“写端”,反之则称为“读端”,这两个端点实际上是两个“描述字”。这样的管道可以连接在两个进程之间,成为数据传输的通道。

    按照是否有名字,管道分为“未命名管道”也就是你经常看到的pipe,以及“有名管道”也就是你经常看到的FIFO(first in first out, 和数据结构中的FIFO一样,其也是按照“先进先出”的法则传输数据)。

    1,pipe

    pipe一词虽然是“管道”二字的英文翻译,但在这里其不是管道的统称,而是一种最基本和简单的管道形式:未命名管道。由于其没有名字(或者说id之类的),所以其无法在两个毫无干系的两个进程间使用。试想一下,进程A创建了一个管道,进程B无法去找到该管道并使用它,因为没有任何可拿去查找的凭据,连名字都没有... 但在一种特殊情况下其是有用的,如下图,假设进程A创建了一个管道:

      

    其中的箭头代表数据写入和读出,很明显,进程A可以从管道的写端将数据写入,然后再从读端读出,但这似乎没有什么意义。
    但,如果在管道建立以后,我们将进程A进行一次fork(),有意思的事情发生了, 子进程会复制父进程的大部分信息,这些信息里当然包含了代表了管道读端和写端的两个“描述字”(但管道仍然只有一份,就像被两个进程读写的某个硬盘文件只有一份一样),所以其就演变成下图这个样子:

     如果此时,我们将上图中左上角以及右下角的两个箭头抛弃掉(相当于是说将这两个箭头所对应的描述字关闭), 那么就如下所示咯:
      

    哈,注意到了吗?我们建立了一个从进程A流向进程B的数据通道。

    那么,将上述过程写成代码的形式就很简单了。

    首先是 int pipe(int f[2]) 这个函数,其需要头文件<unistd.h>,这个函数将创建一个未命名管道,并将管道的读端描述字包含在f[0]中,将写端描述字放在f[1]中,然后你就可以像利用普通文件描述字一样来读写数据了。

    然后是fork函数,不多讲,不了解的同学一定要先搞清楚了,比如看这篇文章。
    再次是int close(int fd) 函数, 其需要头文件<unistd.h>,其用于关闭指定的文件描述字。

    最后是write和read函数, 其需要头文件<unistd.h>,用于读写数据。

    OK,上代码:

    复制代码
    #include <stdio.h>
    #include 
    <unistd.h>
    #include 
    <string.h>
    #include 
    <stdlib.h>

    #define BUFF_SZ 256

    int main()
    {
        printf(
    "app start... ");
        
        pid_t          pid;
        
    int            pipe_fd[2];
        
    char           buf[BUFF_SZ];
        
    const char     data[] = "hi, this is the test data";
        
    int            bytes_read;
        
    int            bytes_write;
        
        
    //clear buffer, all bytes as 0
        memset(buf, 0sizeof(buf));
        
        
    //creat pipe
        if(pipe(pipe_fd) < 0)
        {
            printf(
    "[ERROR] can not create pipe ");
            exit(
    1);
        }
        
        
        
    //fork an new process
        if(0 == (pid=fork()))
        {
            
    //close the write-point of pipe in child process
            close(pipe_fd[1]);
            
            
    //read bytes from read-point of pipe in child process
            if((bytes_read = read(pipe_fd[0], buf, BUFF_SZ)) > 0)
            {
                printf(
    "%d bytes read from pipe : '%s' ", bytes_read, buf);
            }
            
            
    //close read-point of pipe in child process
            close(pipe_fd[0]);
            exit(
    0);
        }
        
        
        
    //close read-point of pipe in parent process
        close(pipe_fd[0]);
        
        
    //write bytes to write-point of pipe in parent process
        if((bytes_write = write(pipe_fd[1], data, strlen(data))))
        {
            printf(
    "%d bytes wrote to pipe : '%s' ", bytes_write, data);
        }
        
        
    //close write-point of pipe in parent process
        close(pipe_fd[1]);
        
        
    //wait child process exit
        waitpid(pid, NULL, 0);
        
        printf(
    "app end ");
        
        
    return 0;
    }
    复制代码

    运行输出为:

    复制代码
    app start...
    25 bytes wrote to pipe : 'hi, this is the test data'
    25 bytes read from pipe : 'hi, this is the test data'
    app end
    复制代码

    2,FIFO

    与“无名管道”不同的是,FIFO拥有一个名称来标志它,所谓的名称实际上就是一个路径,比如“/tmp/my_pipe”,其对应到磁盘上的一个管道文件,如果我们用file命令来查看其文件类型的话,会得到如下输出:

    按 Ctrl+C 复制代码
    按 Ctrl+C 复制代码
    为了简化对FIFO的理解,我们可以这样来假想:进程A在磁盘上创建了一个名为my_pipe的文件,并向其中写入一些数据,然后进程B打开该文件,并将数据从文件中读出,这样我们便实现了进程A和进程B之间的通信。大致原理如此,只不过FIFO做了更精细的一些操作,以便实现起来更可靠。

    另外,我们需要知道的是,FIFO是单向(半双工)传输数据的。

    函数 int mkfifo (char* path, mode_t mode) 负责创建FIFO管道,其需要头文件<sys/stat.h>,参数path即要创建的管道文件存放位置,mode参数即文件权限,更多的参考这里。 

    FIFO管道创建完成以后,便可以使用open函数来打开它,然后进行读写操作了。 

    看下面这个简单的demo,其将测试数据由进程A传递给进程B(为防止混淆视线,我将一些条件判断和异常处理代码删掉了): 

    先创建一个程序A,其负责创建FIFO管道,并向其中写入一些数据:

    复制代码
    /*
     * process A: create FIFO and write data
     
    */

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

    #define FIFO_NAME "/tmp/my_fifo"

    int main()
    {
        
    int pipe_fd;
        
        
    //if the pipe file do not exist
        if (access(FIFO_NAME, F_OK) == -1)
        {
            
    //creat FIFO pipe file
            mkfifo(FIFO_NAME, 0777);
        }
        
        
    //open FIFO pipe file.
        
    //this will be brocked until some one open another end point(read-point) of this pipe
        pipe_fd = open(FIFO_NAME, O_WRONLY);
        
        
    //write data into pipe 
        write(pipe_fd, "hi, this is a test", PIPE_BUF);
        
        
    //close FIFO pipe file descriptor
        close(pipe_fd);
        
        
    return 0;
    }
    复制代码

    然后创建程序B,它从管道中读取数据并显示出来:

    复制代码
    /*
     * process B: read data from FIFO
     
    */

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

    #define FIFO_NAME "/tmp/my_fifo"
    #define BUFFER_SIZE PIPE_BUF

    int main()
    {
        
    int pipe_fd;
        
        
    char buffer[BUFFER_SIZE + 1];
        
    //reset all bytes in buffer as '' 
        memset(buffer, ''sizeof(buffer));
        
        
    //open FIFO pipe file.
        
    //this will be brocked until some one open another end point(write-point) of this pipe
        pipe_fd = open(FIFO_NAME, O_RDONLY);
        
        
    if(read(pipe_fd, buffer, BUFFER_SIZE) > 0)
        {
            printf(
    "data from FIFO : %s ", buffer);
        }
        
        
    //close pipe file descriptor
        close(pipe_fd);

        
    return 0;
    }
    复制代码

    运行下程序便会发现,无论是先运行A或是B,先运行起来的都会等待另外一个,这时open函数第二个参数的原因,我们可以添加O_NONBLOCK选项来取消阻塞。关于open函数,更多的看这里 

    下面这个demo比较有意思,在程序A中敲入字符,其会立即传递给程序B(无需等待回车键),然后程序B会将它显示出来,关于如何取消控制台对回车键的等待,可以参考这里。 

    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <termios.h>


    #define FIFO_NAME "/tmp/my_fifo"
    #define BUFFER_SIZE PIPE_BUF

    static struct termios oldt;

    //restore terminal settings
    void restore_terminal_settings(void)
    {
        tcsetattr(0, TCSANOW, &oldt);  /* Apply saved settings */
    }

    //make terminal read 1 char at a time
    void disable_waiting_for_enter(void)
    {
        
    struct termios newt;
        
        
    //Save terminal settings
        tcgetattr(0&oldt); 
        
    //init new settings
        newt = oldt;  
        
    //change settings
        newt.c_lflag &= ~(ICANON | ECHO);
        
    //apply settings
        tcsetattr(0, TCSANOW, &newt);
        
    //make sure settings will be restored when program ends
        atexit(restore_terminal_settings);
    }

    int main()
    {
        
    int pipe_fd;
        
    int res;

        
    char buffer[BUFFER_SIZE + 1];
        memset(buffer, ''sizeof(buffer));
        
        
    //if the pipe file do not exist
        if (access(FIFO_NAME, F_OK) == -1)
        {
            
    //creat FIFO pipe file
            res = mkfifo(FIFO_NAME, 0777);
            
    if (res != 0)
            {
                fprintf(stderr, "Could not create fifo %s ", FIFO_NAME);
                exit(EXIT_FAILURE);
            }
        }
        
        
    //open FIFO pipe file.
        
    //this will be brocked until some one open another end point(read-point) of this pipe
        pipe_fd = open(FIFO_NAME, O_WRONLY);
        
        
    //if FIFO pipe file open sucessfully
        if (pipe_fd != -1)
        {
            printf("input something and press RETURN ");
            
    char ch;
            
            disable_waiting_for_enter();
            
            
    while ((ch = getchar()) != ' '
            {
                buffer[0= ch;
                
                
    //write data into pipe 
                res = write(pipe_fd, buffer, BUFFER_SIZE);
                
                
    if (res == -1)
                {
                    fprintf(stderr, "Write error on pipe ");
                    exit(EXIT_FAILURE);
                }
                
            }
            
            
    //close FIFO pipe file descriptor
            close(pipe_fd);
        }
        
    else
        {
            exit(EXIT_FAILURE);
        }
        
        printf("Process %d finish ", getpid());
        exit(EXIT_SUCCESS);
    }
    复制代码
    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>

    #define FIFO_NAME "/tmp/my_fifo"
    #define BUFFER_SIZE PIPE_BUF

    int main()
    {
        
    int pipe_fd;
        
    int res;
        
        
    char buffer[BUFFER_SIZE + 1];
        
    //reset all bytes in buffer as '' 
        memset(buffer, ''sizeof(buffer));
        
        
    //open FIFO pipe file.
        
    //this will be brocked until some one open another end point(write-point) of this pipe
        pipe_fd = open(FIFO_NAME, O_RDONLY);
        
        
    if (pipe_fd != -1)
        {
            printf("data read from buffer :  ");
            
            
    //read all data from pipe file by BUFFER_SIZE each time
            do
            {
                res = read(pipe_fd, buffer, BUFFER_SIZE);
                
    if(res>0)
                {
                    printf("%s", buffer);
                    fflush(stdout);
                }
                
            }while(res > 0);
            
            
    //close pipe file descriptor
            close(pipe_fd);
        }
        
    else
        {
            exit(EXIT_FAILURE);
        }
        
        printf(" Process %d finish ", getpid());
        exit(EXIT_SUCCESS);
    }
  • 相关阅读:
    Adding links to a Panel container’s status text in Flex
    修改flex应用默认的装载界面
    在Flex中复制文字到操作系统的剪贴板
    kubernetesdashboard安装
    需求分析与代码设计统一新方法(摘自:Jdon创始人彭晨阳)
    System.IO命名空间
    第一篇随笔怎么写?
    Linux下使用VsCode进行Qt开发环境搭建 TssiNG
    Apache Rewrite实现URL的跳转和域名跳转
    Subversion1.6.6 + apache2.2.14 + windows安装配置
  • 原文地址:https://www.cnblogs.com/youngerchina/p/5624577.html
Copyright © 2011-2022 走看看