第十三章 进程间通信 管道

来源:互联网 发布:淘宝如何做企业店铺 编辑:程序博客网 时间:2024/06/05 16:26

13.1 什么是管道

当从一个进程连接数据流到另一个进程时,我们使用术语管道(pipe).我们通常是把一个进程的输出通过管道连接到另一个进程的输入.

13.2 进程管道

popen和pclose

#include <stdio.h>FILE *popen(const char *command, const char *open_mode);int close(FILE *stream_to_close);
  1. popen函数允许一个程序将另外一个程序作为新进程来启动,并且可以传递数据给它或者通过它接受数据.command字符串是要运行的程序名和相应的参数.
  2. open_mode必须是”r”或者”w”,如果是”r”可以使用fread来读取被调用程序的输出.如果是”w”,调用程序就可以用fwrite调用向被调用程序发送数据,而被调用程序可以再自己的标准输入上读取这些数据.被调用程序通常不会意识到自己正在从另一个进程读取数据,他只是在标准输入流上读取数据,然后做出相应的操作.
  3. 每个popen调用都必须指定”r”或者”w”,在popen函数的标准视线中不支持任何其他选项.这意味着我们不能调用另一个程序并同时对它进行读写操作.popen失败时返回一个空指针.如果想通过管道实现双向通信,最普通的解决办法是使用两个管道,每个管道负责一个方向的数据流
  4. 用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流.pclose调用只在popen启动的进程结束后才返回.如果调用pclose时它人在运行,pclose调用将等待该进程的结束
  5. pclose调用的返回值通常是它所关闭的文件流所在进程的退出码.如果调用进程在调用pclose之前执行了一个wait语句,被调用进程的退出状态就会丢失,因为被调用的进程已结束.此时,pclose将返回-1,并设置errno为ECHILD.

读取外部程序的输出

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    FILE *read_fp;    char buffer[BUFSIZ + 1];    int char_read;    memset(buffer, '\0', sizeof(buffer));    read_fp = popen("uname -a", "r");    if(read_fp != NULL){        char_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);        if(char_read > 0){            printf("Output was: -\n%s\n", buffer);        }        pclose(read_fp);        exit(EXIT_SUCCESS);    }    exit(EXIT_SUCCESS);}

13.3 将输出送往popen

将输出送往外部程序

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    FILE *write_fp;    char buffer[BUFSIZ + 1];    sprintf(buffer, "Once upon a time,there was ...\n");    write_fp = popen("od -c", "w");    if(write_fp != NULL){        fwrite(buffer, sizeof(char), strlen(buffer), write_fp);        pclose(write_fp);        exit(EXIT_SUCCESS);    }    exit(EXIT_SUCCESS);}

13.3.1

通过管道读取大量数据

fread读取一次之后read_fp指针会后移到下一次将要读取的位置

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    FILE *read_fp;    char buffer[BUFSIZ + 1];    int char_read;    memset(buffer, '\0', strlen(buffer));    read_fp = popen("ps -ax", "r");    if(read_fp != NULL){        char_read = fread(buffer, sizeof(char), 1024, read_fp);        while(char_read > 0){            buffer[1023] = '\0';            printf("Reading %d: -\n%s\n", 1024, buffer);            char_read = fread(buffer, sizeof(char), 1024, read_fp);        }        pclose(read_fp);        exit(EXIT_SUCCESS);    }    exit(EXIT_SUCCESS);}

13.3.2 如何实现popen

请求popen调用运行一个程序时,它首先会启动shell,然后将command字符串作为一个参数传递给它

popen启动shell

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    FILE *read_fp;    char buffer[BUFSIZ + 1];    int char_read;    printf("char size = %ld\n", sizeof(char));    memset(buffer, '\0', sizeof(buffer));    read_fp = popen("cat popen*.c | wc -l", "r");    if(read_fp != NULL){        char_read = fread(buffer,sizeof(char) ,BUFSIZ, read_fp);        if(char_read > 0){            buffer[BUFSIZ - 1] = '\0';            printf("Reading: -\n %s\n", buffer);            char_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);        }        pclose(read_fp);        exit(EXIT_SUCCESS);    }    exit(EXIT_SUCCESS);}

13.4 pipe调用

pipe函数:通过这个函数在两个程序之间传递数据不需要启动一个shell来解释请求的命令,它同时还提供了对读写数据的更多控制

#include <unistd.h>int pipe(int file_descriptor[2]);
  1. pipe函数的参数是一个由两个整数类型的文件描述符组成的数组指针.该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno来表明失败的原因.Linux定义了下面一些错误
     
    EMFILE:进程使用的文件描述符过多
    ENFILE:系统的文件表已满
    EFAULT:文件描述符无效
  2. 两个文件描述符以一种特殊方式连接起来.写到file_descriptor[1]的所有数据都可以从file_descriptor[2]读回来.数据基于现金先出的原则(通常简写为FIFO)进行处理.这意味着如果你把1,2,3写到file_descriptor[1],从file_descriptor[0]读取到的数据也会是1,2,3.这与栈的处理方式不同,栈采用后进先出的原则,通常简写为LIFO.
  3. 特别要注意,这里使用的是文件描述符而不是文件流,所以我们必须用底层的read和write调用来访问数据,而不是文件流库函数fread和fwrite.
  4. file_descriptor使用的连个文件描述符分别代表读管道和写管道
     
    file_descriptor[0]:读管道
    file_descriptor[1]:写管道

pipe1.c

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    int process_date;    int file_pipes[2];    const char some_date[] = "123";    char buffer[BUFSIZ + 1];    memset(buffer, '\0', sizeof(buffer));    if(pipe(file_pipes) == 0){        process_date = write(file_pipes[1], some_date, strlen(some_date));        printf("Wrote %d bytes\n", process_date);        process_date = read(file_pipes[0], buffer, BUFSIZ);        printf("Read %d bytes: %s\n", process_date, buffer);        exit(EXIT_SUCCESS);    }    exit(EXIT_SUCCESS);}

fork

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

我们来看一个例子:

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <stdlib.h>/** @brief main For the understanding of the fork()** @param argc* @param argv[]** @return 在父进程中返回子进程的进程号;在子进程中返回0。*/int main(int argc, char *argv[]){    pid_t pid;     int cnt = 0;    pid = fork();    if (pid == -1) {        perror("fork error");        exit(1);    } else if (pid == 0) {        printf("The returned value is %d\nIn child process!!\nMy PID is %d\n",        pid, getpid());        cnt++;    } else {        printf("The returned value is %d\nIn father process!!\nMy PID is %d\n",        pid, getpid());        cnt++;    }    printf("cnt = %d\n", cnt);    return 0;}     运行结果是:     The returned value is 20473     In father process!!     My PID is 20472     cnt = 1     The returned value is 0     In child process!!     My PID is 20473     cnt = 1
  1. 在语句pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(pid == -1)……
  2. 为什么两个进程的pid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

    1)在父进程中,fork返回新创建子进程的进程ID;2)在子进程中,fork返回0;3)如果出现错误,fork返回-1;

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

    pid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的pid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其pid为0.

  3. fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。
    创建新进程成功后,系统中出现两个基本完全相同的进程,子进程从父进程处继承了整个进程的地址空间,包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端, 父子进程不共享这些存储空间部分,父子进程共享正文段。这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

    每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

    有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。

    执行完fork后,进程1的变量为cnt=0,pid != 0(父进程)。进程2的变量为count=0,pid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过pid来识别和操作父子进程的。

  4. 还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int cnt=0;fork只拷贝下一个要执行的代码到新的进程。

跨越fork调用的管道

vim 替换

  1. :s/vivian/sky/ 替换当前行第一个 vivian 为 sky
  2. :s/vivian/sky/g 替换当前行所有 vivian 为 sky
  3. :%s/vivian/sky/g 替换所有的 vivian 为 sky

13.5 父进程和子进程

在接下来的对pipe调用的研究中,我们将学习如何在子进程中运行一个与其父进程完全不同的另外一个程序,而不仅仅是运行一个相同程序

管道和exec函数

sscanf(const char *buffer, char *format, buf1, buf2, ...);1. sscanf会从buffer里读进数据,依照format格式将数据   写入到argument里面.2. buf1, buf2, ...均为地址/* pipe3.c:数据产生者 */#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    int data_processed;    int file_pipes[2];    char buffer[BUFSIZ + 1];    const char some_data[] = "123";    pid_t fork_result;    memset(buffer, '\0', sizeof(buffer));    if(pipe(file_pipes) == 0){        fork_result = fork();        if(fork_result == (pid_t)-1){            fprintf(stderr, "fork failed.");            exit(EXIT_FAILURE);        }        if(fork_result == 0){            sprintf(buffer, "%d", file_pipes[0]);            (void)execl("pipe4", "pipe4", buffer, NULL);            exit(EXIT_FAILURE);        }        else {            data_processed = write(file_pipes[1], some_data, strlen(some_data));            printf("%d - wrote %d bytes\n", getpid(), data_processed);        }    }    exit(EXIT_SUCCESS);}/* pipe4.c:数据消费者 */#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(int argc, char *argv[]){    int data_processed;    char buffer[BUFSIZ + 1];    int file_descriptor;    memset(buffer, '\0', sizeof(buffer));    sscanf(argv[1], "%d", &file_descriptor);    data_processed = read(file_descriptor, buffer, BUFSIZ);    printf("%d - read %d bytes: %s\n", getpid(), data_processed, buffer);    exit(EXIT_SUCCESS);}

13.5.1 管道关闭后的读操作

当并不知道有多少数据需要读取,往往采用循环的方法,读取数据—-处理数据—-读取更多的数据,直到没有数据可读为止.当没有数据可读时,read调用通常会阻塞,即他将暂停进程来等待有数据到达为止.如果管道的另外一端已被关闭,也就是说没有进程打开这个管道并向他写数据了,这是read调用就会阻塞.但是这样的阻塞不是很有用,因此对一个已关闭写数据的管道做read调用将返回0而不是阻塞.如果跨越fork使用管道,就会有两个不同的文件描述符可以用于向管道写数据,一个在父进程中,一个在子进程中.只有把父子进程中针对管道的写文件描述符都关闭,管道才会被认为是关闭了,对管道的read调用才会失败

13.5.2 把管道用作标准输入和标准输出

dup函数

#include <unistd.h>int dup(int file_descriptor);int dup2(int file_descriptor_one, int file_descriptor_two);
  1. dup调用的目的是打开一个新的文件描述符,这与open调用有点类似.不同之处是,dup调用创建的新文件描述符与作为他的参数的那个已有文件描述符指向同一个文件(或管道).
  2. 对于dup函数来说,新的文件描述符总是取最小的可用值.而对于dup2函数来说,它所创建的新文件描述符或者与参数file_descriptor_two相同,或者是第一个对于该参数的可用值.

管道和dup函数

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int main(){    int data_processed;    int file_pipes[2];    const char some_data[] = "123";    pid_t fork_result;    if(pipe(file_pipes) == 0){        fork_result = fork();        if(fork_result == (pid_t)-1){            perror("fork failed.");            exit(EXIT_FAILURE);        }        if(fork_result == 0){            close(0);            dup(file_pipes[0]);            close(file_pipes[0]);            close(file_pipes[1]);            execlp("od", "od", "-c", (char *)0);            exit(EXIT_SUCCESS);        }        else{            close(file_pipes[0]);            data_processed = write(file_pipes[1], some_data, strlen(some_data));            close(file_pipes[1]);            printf("%d - wrote %d bytes\n", getpid(), data_processed);        }    }}

13.6 命名管道:FIFO

命令行创建命名管道

mknod filename pmkfifo filename

两个命名管道函数

#include <sys/types.h>#include <sts/stat.h>int mkfifo(const char *filename, mode_t mode);int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
  1. 与mknod命令一样,我们可以用mknod函数建立许多特殊类型的文件.想要通过这个函数创建一个命名管道,唯一具有可移植性的方式是使用一个dev_t类型的值0,并将文件访问模式与S_IFIFO按位或

创建命名管道:fifo1.c

13.6.1 访问FIFO文件

首先,我们来尝试读取这个(空的)FIFO文件

cat < /tmp/my_fifo

现在我们我一尝试向FIFO写数据.必须使用另外一个终端来执行下面的命令,因为第一个命令现在被挂起以等待数据出现在FIFO中

echo "hello world" > /tmp/my_fifo

你将看到cat命令产生输出.如果不向FIFO发送任何数据,cat命令将一直被挂起,直到你中断它.

实验解析:因为FIFO中没有数据,所以cat和echo程序都阻塞了,cat等待数据的到来,而echo等待其他进程读取数据

1. 使用open代开FIFO文件**
  1. 打开FIFO的一个主要限制是,程序不能以O_RDWR模式打开FIFO文件进行读写.如果一个管道以读/写方式打开,进程就会从这个管道都会他自己的输出
  2. 打开FIFO文件和打开普通文件的另一点区别是,对open_flag的O_NONBLOCK选项的用法.

R_RDONLY,O_WRONLY和O_NONBLOCK四种标志组合介绍

open(const char *path, O_RDONLY);

在这种情况下,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则他不会返回.

open(const char *path, O_RDONLY | O_NONBLOCK);

即使没有其它进程以写方式打开FIFO,这个open调用也将成功并立即返回

open(const char *path, O_WRONLY);

在这种情况下open调用将阻塞,直到有一个进程以读的方式打开同一个FIFO为止

open(const char *path, O_WRONLY | O_NONBLOCK);

这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1并且FIFO也不会被打开.

打开FIFO文件

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"int main(int argc,char *argv[]){    int res;    int open_mode = 0;    int i;    if(argc < 2){        fprintf(stderr, "Need at least 2 parameters.\n");        exit(EXIT_FAILURE);    }    for(i = 1; i < argc; i++){        if(strncmp(*++argv, "O_RDONLY", 8) == 0)          open_mode |= O_RDONLY;        if(strncmp(*argv, "O_WRONLY", 8) == 0)          open_mode |= O_WRONLY;        if(strncmp(*argv, "O_NONBLOCK", 10) == 0)          open_mode |= O_NONBLOCK;    }    if(access(FIFO_NAME, F_OK) == -1){        res = mkfifo(FIFO_NAME, 0777);        if(res != 0){            printf("could not create fifo %s\n", FIFO_NAME);            exit(EXIT_FAILURE);        }    }    printf("Process %d opening FIFO.\n", getpid());    res = open(FIFO_NAME, open_mode);    printf("Process %d result %d\n", getpid(), res);    sleep(5);    if(res != -1)      (void)close(res);    printf("Process %d finished\n", getpid());    exit(EXIT_SUCCESS);}
2. 不带O_NONBLOCK标志的O_RDONLY和O_WRONLY
./fifo2 O_RDONLY &./fifo2 O_WRONLY
3. 带O_NONBLOCK标志的O_RDONLY和不带该标志的O_WRONLY
./fifo2 O_RDONLY O_NONBLOCK &./fifo2 O_WRONLY
4. 对FIFO进行读写操作
/* 生产者程序 */#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"#define BUFFER_SIZE PIPE_BUF#define TEN_MEG (1024 * 1024 * 10)int main(){    int pipe_fd;    int res;    int bytes_sent = 0;    int open_mode = O_WRONLY;    char buffer[BUFFER_SIZE + 1];    if(access(FIFO_NAME, F_OK) == -1){        res = mkfifo(FIFO_NAME, 0777);        if(res != 0){            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);            exit(EXIT_FAILURE);        }    }    printf("Process %d opening FIFO O_WRONLY.\n", getpid());    pipe_fd = open(FIFO_NAME, open_mode);    printf("Process %d result %d.\n", getpid(), pipe_fd);    if(pipe_fd != -1){        while(bytes_sent < TEN_MEG){            res = write(pipe_fd, buffer, BUFFER_SIZE);            if(res == -1){                fprintf(stderr, "write error on pipe.\n");                exit(EXIT_FAILURE);            }            bytes_sent += res;        }        (void)close(pipe_fd);    }    else {        exit(EXIT_FAILURE);    }    printf("Process %d finished.\n", getpid());    exit(EXIT_SUCCESS);}/* 消费者程序 */#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"#define BUFFER_SIZE PIPE_BUFint main(){    int pipe_fd;    int res;    int open_mode = O_RDONLY;    char buffer[BUFFER_SIZE + 1];    int bytes_read = 0;    memset(buffer, '\0', sizeof(buffer));    printf("Process %d opening FIFO O_RDONLY.\n", getpid());    pipe_fd = open(FIFO_NAME, open_mode);    printf("Process %d result %d.\n", getpid(), pipe_fd);    if(pipe_fd != -1){        do{            res = read(pipe_fd, buffer, BUFFER_SIZE);            bytes_read += res;        }while(res > 0);        (void)close(pipe_fd);    }    else {        exit(EXIT_FAILURE);    }    printf("Process %d finished, %d bytes read.\n", getpid(), bytes_read);    exit(EXIT_SUCCESS);}

13.6.2 高级主题:使用FIFO的客户端/服务器应用程序

/* 头文件client.h */#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#define SERVER_FIFO_NAME "/tmp/server_fifo"#define CLIENT_FIFO_NAME "/tmp/client_%d_fifo"#define BUFFER_SIZE 20struct data_to_pass_st {    pid_t client_pid;    char some_data[BUFFER_SIZE - 1];};/* 服务端程序:server.c */#include "client.h"#include <ctype.h>int main(){    int server_fifo_fd, client_fifo_fd;    struct data_to_pass_st my_data;    int read_res;    char client_fifo[256];    char *tmp_char_ptr;    mkfifo(SERVER_FIFO_NAME, 0777);    server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);    if(server_fifo_fd == -1){        fprintf(stderr, "server fifo failed.\n");        exit(EXIT_FAILURE);    }    sleep(10);    do{        read_res = read(server_fifo_fd, &my_data, sizeof(my_data));        if(read_res > 0){            tmp_char_ptr = my_data.some_data;            while(*tmp_char_ptr){                *tmp_char_ptr = toupper(*tmp_char_ptr);                tmp_char_ptr++;            }            sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);            client_fifo_fd = open(client_fifo, O_WRONLY);            if(client_fifo_fd != -1){                write(client_fifo_fd, &my_data, sizeof(my_data));                close(client_fifo_fd);            }        }    } while(read_res > 0);    close(server_fifo_fd);    unlink(SERVER_FIFO_NAME);    exit(EXIT_SUCCESS);}/* 客户端程序:client.c */#include "client.h"#include <ctype.h>int main(){    int server_fifo_fd, client_fifo_fd;    struct data_to_pass_st my_data;    char client_fifo[256];    int times_to_sent;    server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);    if(server_fifo_fd == -1){        fprintf(stderr, "sorry, no server.\n");        exit(EXIT_FAILURE);    }    my_data.client_pid = getpid();    sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);    if(mkfifo(client_fifo, 0777) == -1){        fprintf(stderr, "sorry, can't make %s.\n", client_fifo);        exit(EXIT_FAILURE);    }    for(times_to_sent = 0; times_to_sent < 5; times_to_sent++){        sprintf(my_data.some_data, "Hello from %d", my_data.client_pid);        printf("%d sent %s", my_data.client_pid, my_data.some_data);        write(server_fifo_fd, &my_data, sizeof(my_data));        client_fifo_fd = open(client_fifo, O_RDONLY);        if(client_fifo_fd != -1){            if(read(client_fifo_fd, &my_data, sizeof(my_data)) > 0){                printf("recevied: %s.\n", my_data.some_data);            }            close(client_fifo_fd);        }    }    close(server_fifo_fd);    unlink(client_fifo);    exit(EXIT_SUCCESS);}
0 0
原创粉丝点击