首页 > 代码库 > linux 管道通信

linux 管道通信

 

进程通信

进程是系统分配资源的最小单位, 不同进程之间是相互隔离的, Linux常用于进程通信的几种方式有

  1. 匿名管道及有名管道 : 匿名管道用于具有亲缘关系的进程通信, 有名管道则可用于一般进程之间.
  2. 信号 : 软件层对中断机制的一种模拟.
  3. 消息队列 
  4. 共享内存 : 不同进程享同一块内存区域, 不同进程可以实时查看对方对共享内存的更新. 需要借助同步机制, 如互斥锁, 信号量.
  5. 信号量 : 主要用于不同进程以及同一进程不同线程的同步和互斥.
  6. 套接字 : 广泛用于网络间进程通信.

 

无名管道

管道是是基于文件描述符的通信方式, 无名管道只能用于具有亲缘关系之间的进程通信.  建立一个管道时 它会创建两个文件描述符, fd[0] 和 fd[1] , 其中 fd[0] 用于读取数据,  fd[1] 用于写入数据, 请看下图 :  

技术分享

 

 

如果父进程需要向子进程发送数据通信,  那么可以在父进程上创建一个管道,  关闭父进程的fd[0]和子进程的fd[1],  子进程向父进程发送数据就与之相反.

 

技术分享

 

 

下面演示了父进程向管道写入数据, 子进程从中读取.  sleep()函数确保父进程已经关闭了相应的文件描述符.

#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>

int main(void){

    pid_t pid;
    int pipe_fd[2];
    char buf[1024];
    const char data[] = "管道测试";
    int real_read, real_write;

    memset(buf, 0, sizeof(buf));
    /* 创建管道 */
    if(pipe(pipe_fd) < 0){
        perror("pipe");
        exit(1);
    }

    if((pid = fork()) < 0){
        perror("fork");
        exit(1);
    }else if(pid == 0){
        /* 子进程关闭写描述符, 等待1秒让父进程关闭读描述符*/
        close(pipe_fd[1]);
        sleep(2);
        if((real_read = read(pipe_fd[0], buf, 1024)) > 0){
            printf("读取管道内容 : %s\n", buf);
        }
        close(pipe_fd[0]);
        exit(0);
    }else{
        /* 父进程关闭读描述符, 等待1秒让子进程关闭写描述符 */
        close(pipe_fd[0]);
        sleep(1);
        if((real_write = write(pipe_fd[1], data, strlen(data))) > 0){
            printf("写入管道内容 : %s\n", data);
        }
        close(pipe_fd[1]);
        /* 收集子进程退出信息 */
        waitpid(pid, NULL, 0);
        exit(0);
    }
}

 

 

 

管道读写需要注意几点

  • 向管道写入数据时, 管道读端必须存在, 否则写进程将收到内核传来SIGPIPE信号.
  • 向管道写入数据, 不保证原子性, 如果读进程不读取管道缓冲区中的数据, 那么写进程会一直阻塞.
  • 父子进程运行是, 并不能保证先后顺序, 这里简单用sleep()解决.

 

标准流管道

相当系统调用,  用于创建连接到另一个进程之间的管道,  这里的进程是指可进行一定操作的可执行文件, 标准流管道把一系列创建过程合并到popen() 中了.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>

/*  标准管道流操作 */
int main(void){
     FILE *fp;
     char *cmd = "ps -ef";
     char buf[1024];

     /* r 文件指针连接到command的标准输出*/
     if((fp = popen(cmd, "r")) == NULL){
          printf("popen error");
          exit(1);
     }
     while((fgets(buf, 1024, fp)) != NULL){
         printf("%s\n", buf);
     }
     pclose(fp);
     exit(0);
}

 

 

 

有名管道

Linux中专门设立了一个专门的特殊文件系统--管道文件,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据.但在磁盘上只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样.

管道文件的读写可能有阻塞问题

对于读进程

  1. 如果管道是阻塞打开, 且当前FIFO没有数据, 则读进程一直阻塞.
  2. 如果非阻塞打开, 立即执行读操作.

对于写进程

  1. 如果管道是阻塞打开, 则一直阻塞到可以写入.
  2. 如果非阻塞打开而不能全部写入, 则写入部分或者写入失败. 

 

下面包含两个程序, 一个用于读取管道, 并在该程序中创建管道, 另一个用于写管道.  首先要调用读程序.

 

读程序

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

/* 由读管道创建 */
#define MYFIFO    "/tmp/myfifo"

int main(int argc, char *argv[]){

    int fd;
    char buf[PIPE_BUF];
    int nread;
    
    /* 如果管道不存在则创建 */
    if(access(MYFIFO, F_OK) == -1){
        /* 0是管道文件, 666是权限 */
        if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST)){
            perror("create fifo");
            exit(1);
        }
    }

    /* 只读阻塞方式打开管道 */
    fd = open(MYFIFO, O_RDONLY);
    if(fd == -1){
        perror("open");
        exit(1);
    }
    
    /* 读取字符串 */
    while(1){
        memset(buf, 0, sizeof(buf));
        if((nread = read(fd, buf, PIPE_BUF)) > 0){
            printf("read %s\n", buf);
        }
    }

    close(fd);
    exit(0);
}

 

 

 

写程序

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

/* 由读管道创建 */
#define MYFIFO    "/tmp/myfifo"

int main(int argc, char *argv[]){

    int fd;
    char buf[PIPE_BUF];
    int nwrite;

    if(argc <= 1){
        printf("Usage: ./fifo_write <strring>");
        exit(1);
    }
    sscanf(argv[1], "%s", buf);
    
    /* 只写阻塞方式打开管道 */
    fd = open(MYFIFO, O_WRONLY);
    if(fd == -1){
        perror("open");
        exit(1);
    }
    
    /* 写入字符串 */
    if((nwrite = write(fd, buf, PIPE_BUF)) > 0){
        printf("write : %s\n", buf);
    }
    close(fd);
    exit(0);
}

 

 

编译运行

读程序
$ gcc fifo_read.c -o fifo_read
$ ./fifo_read
read 写入1
read 写入2
read 写入3


写程序
$ gcc fifo_write.c -o fifo_write
$ ./fifo_write
$ ./fifo_write 写入1
write : 写入1
$ ./fifo_write 写入2
write : 写入2
$ ./fifo_write 写入3
write : 写入3

 

linux 管道通信