APUE学习笔记-15章进程间通信

IPC进程间通信

进程间通信的方式主要是有:

  1. 管道
  2. 消息队列
  3. 信号量
  4. 共享内存
  5. 信号
  6. Socket套接字

管道

半双工方式是最常用的IPC形式(数据只能是在一个方向上流动)

#include 

int pipe(int fd[2]);

单个进程中的管道意义不大,通常是pipe之后调用fork来创建父子之间的ipc管道。

我们可以通过关闭父进程或者子进程的读写的端口来实现 控制数据流的方向

:从父进程流向子进程的管道, 父进程关闭管道的读端(fd[0]), 子进程关闭管道的写端(fd[1])

如果想从子进程向父进程的流向,关闭的顺序相反就行

父进程到子进程的管道

#include "apue.h"

int main()
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];


    if (pipe(fd) < 0) 
        err_sys("pipe error");
    if ((pid = fork()) < 0) 
        err_sys("fork error");
    else if (pid > 0)  {                //parent
        close(fd[0]);                   //close read port 
        write(fd[1], "hello from father\n", 18);
    } else {                            //child 
        close(fd[1]);
        sleep(1);
        n = read(fd[0], line, n);
        write(STDOUT_FILENO, line, n);      //将从管道读取的数据写到标准输出中
    }
    exit(0);
}

:使用两个管道实现父子进程之间的同步

#include "apue.h"

static int pfd1[2], pfd2[2];

void TELL_WAIT() {
    if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
        err_sys("pipe error");
}


void TELL_PARENT(pid_t pid) {
    if (write(pfd2[1], "c", 1) != 1)
        err_sys("write error");
}


void TELL_CHILD(pid_t pid) {
    if (write(pfd1[1], "p", 1) != 1)
        err_sys("write error");
}


void WAIT_PARENT() {
    char c;

    if (read(pfd1[0], &c, 1) != 1) 
        err_sys("read error");
    if (c != 'p')
        err_quit("wait parent");
}

void WAIT_CHILD() {
    char c;

    if (read(pfd2[0], &c, 1) != 1) 
        err_sys("read error");
    if (c != 'c')
        err_quit("wait parent");
}

函数popen和pclose

常见的使用管道的方式是:创建一个管道同一个进程进行交互,向另一个进程的管道的端口进行交互

popen:创建管道,fork子进程,关闭未使用的管道端口,执行shell命令,等待命令终止

#include 

FILE* popen(const char* cmdstring, const char *type);

int pclose(FILE* fp);

函数popen首先fork,然后调用exec执行cmdstring,返回一个标准IO文件指针。如果type 是 “r”, 文件指针连接到cmdstring 的标准输出,为“w”则反之

APUE学习笔记-15章进程间通信_第1张图片

:创建过滤程序将用户输入的字符转换成小写

Trans.cpp

#include "apue.h"
#include 

int main()
{
    int c;
    while ((c = getchar()) != EOF) {
        if (isupper(c))
            c = tolower(c);
        if (putchar(c) == EOF)
            err_sys("output error");
        if (c == '\n')
            fflush(stdout);
    }
    exit(0);
}

main.cpp

#include "apue.h"
#include 

int main()
{
    char line[MAXLINE];

    FILE *fpin;

    if ((fpin = popen("./mytrans", "r")) == NULL)             //将文件流连接到trans程序的标准输出上面(也就是能够读取到字符转换后的结果)
        err_sys("popen error");
    for (;;) {
        //下面都是父进程的工作,子进程调用mytrans,用户就是同mytrans进行交互,父进程来进行调度
        fputs("prompt> ", stdout);                          //用户的标准输出上打印prompt >
        fflush(stdout);
        if (fgets(line, MAXLINE, fpin) == NULL)             //从trans的标准输出中读取
            break;
        if (fputs(line, stdout) == EOF)
            err_sys("fputs error to pipe");
    }

    if (pclose(fpin) == -1) {
        err_sys("pclose error");
    }

    putchar('\n');
    exit(0);
}

解析:用户的输入是同trans程序进行交互的,父进程进行总体的调度

协同进程

APUE学习笔记-15章进程间通信_第2张图片

当一个过滤的程序既产生某个过滤程序的输入又读取该过滤程序的输出的时候,就变成了协同进程

APUE学习笔记-15章进程间通信_第3张图片

:协同进程的实例

协同进程从标准输入读取两个数字,将和写到标准输出中(简单实例,实际上协同进程做更加有意义的事情)

#include "apue.h"
int main()
{
    int n, num1, num2;
    char line[MAXLINE];

    while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
        line[n] = 0;            //null terminate 
        if (sscanf(line, "%d%d", &num1, &num2) == 2) {
            sprintf(line, "%d\n", num1 + num2);
            n = strlen(line);
            if (write(STDOUT_FILENO, line, n) != n) 
                err_sys("write error");
        } else {
            if (write(STDOUT_FILENO, "invalid args\n", 13) != 13) 
                err_sys("write error");
        }
    }
    exit(0);
}

进行协同进程的样例演示,父子进程关闭不需要的端口, 子进程使用dup2让管道描述符号移至标准输入和标准输出,最终调用execl

#include "apue.h"

static void    sig_pipe(int);        /* our signal handler */

int
main(void)
{
    int        n, fd1[2], fd2[2];
    pid_t    pid;
    char    line[MAXLINE];

    if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
        err_sys("signal error");

    if (pipe(fd1) < 0 || pipe(fd2) < 0)
        err_sys("pipe error");

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid > 0) {                            /* parent */
        close(fd1[0]);
        close(fd2[1]);

        while (fgets(line, MAXLINE, stdin) != NULL) {
            n = strlen(line);
            if (write(fd1[1], line, n) != n)
                err_sys("write error to pipe");
            if ((n = read(fd2[0], line, MAXLINE)) < 0)
                err_sys("read error from pipe");
            if (n == 0) {
                err_msg("child closed pipe");
                break;
            }
            line[n] = 0;    /* null terminate */
            if (fputs(line, stdout) == EOF)
                err_sys("fputs error");
        }

        if (ferror(stdin))
            err_sys("fgets error on stdin");
        exit(0);
    } else {                                    /* child */
        close(fd1[1]);
        close(fd2[0]);
        if (fd1[0] != STDIN_FILENO) {
            if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
                err_sys("dup2 error to stdin");
            close(fd1[0]);
        }

        if (fd2[1] != STDOUT_FILENO) {
            if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
                err_sys("dup2 error to stdout");
            close(fd2[1]);
        }
        if (execl("./add2", "add2", (char *)0) < 0)
            err_sys("execl error");
    }
    exit(0);
}

static void
sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

FIFO

fifo称为命名管道,未命名的管道只能是在两个相关的进程之间使用.通过fifo,不相关的进程之间也是可以进行交换数据

#include 

int mkfifo(const char* path, mode_t mode);

int mkfifoat(int fd, const char* path, mode_t mode);

阻塞与非阻塞:(O_NOBLOCK)

  • 没有指定非阻塞的情况下, 只读open要阻塞到 某个进程为了写而打开这个fifo。只写open类似
  • 如果指定了非阻塞,那么open之后立即返回,如果没有可读就返回-1, 设置error : ENXIO

用途:

  • shell命令使用FIFO将数据从一条管道传送到另一条管道,无需创建中间临时文件
  • 客户进程-服务器进程应用程序中,FIFO用作汇聚点,在两者进程之间传递数据

信号量、消息队列、共享内存

我看书上的解释也不是太多,这里就贴上小林Coding的解析好了地址

Socket

章节内容等到unp栏目再记笔记

你可能感兴趣的:(APUE,学习,c++,APUE,进程间通信)