Linux多进程08-进程间通信与管道

进程间通信

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。

不同的进程需要进行信息的交互和状态传递(如: 数据传输/通知事件/资源共享/进程控制) , 因此需要进程间通信(IPC: Inter Processes Communication)

image

管道

管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。

image

统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两
个进程来分别执行 ls 和 wc。

管道特点

  • 管道的存储能力有限
  • 管道有文件的特质:读和写, 匿名管道没有文件实体, 有名管道有文件实体, 但不存储数据
  • 从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少
  • 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺
    序是完全一样的。
  • 传递方向是单向的, 一端写一端读, 半双工
  • 据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据
  • 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
    image

获取管道大小

/*
#include <unistd.h>

long fpathconf(int fd, int name);
*/

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

int main(int argc, char const *argv[])
{
    int pipefd[2];
    int ret = pipe(pipefd);

    //获取管道的大小
    long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
    printf("size: %ld\n", size); //size: 4096

    return 0;
}

匿名管道

#include <unistd.h>
int pipe(int pipefd[2]);
    功能:创建一个匿名管道,用来进程间通信
    参数: int pipefd[2] 这个数组是传出参数
        pipefd[0] 对应管道读端
        pipefd[1] 对于管道写端
    返回值:
        成功 0
        失败 -1

    注意:  匿名管道只能用于具有关系的进程之间的通信
        管道默认是阻塞的,如果管道中没有数据,read阻塞,如果管道满了,write阻塞
//子进程发送数据给父进程,父进程读取数据输出
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    //在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret == -1)
    {
        perror("pipe err");
        exit(0);
    }

    //创建子进程
    pid_t pid = fork();
    if (pid > 0)
    {
        //父进程
        printf("i am parent process, pid: %d\n", getpid());

        char buf[1024] = {0};
        while (1)
        {
            //从管道的读取端读取数据
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("parent recv: %s, pid = %d\n", buf, getpid());
            //读不用暂停,如果管道中没数据,会阻塞在那里

            //向管道中不断写入数据
            char *str = "hello, i am parent";
            write(pipefd[1], str, strlen(str));
            sleep(1);
        }
    }
    else if (pid == 0)
    {
        //子进程
        printf("i am child process, pid: %d\n", getpid());
        char buf[1024] = {0};
        while (1)
        {
            //向管道中不断写入数据
            char *str = "hello, i am child";
            write(pipefd[1], str, strlen(str));
            sleep(1);

            //从管道的读取端读取数据
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("child recv: %s, pid = %d\n", buf, getpid());
            bzero(buf,1024);
        }
    }

    return 0;
}

设置管道为非阻塞

/*
    设置管道非阻塞
    int flags = fcntl(fd[0], F_GETFL); //获取原来的flag
    flags != O_NONBLOCK;    //修改flag的值
    fcntl(fd[0], F_SETFL,flags);    //设置新的flag

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

int main(int argc, char const *argv[])
{
    //在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret == -1)
    {
        perror("pipe err");
        exit(0);
    }

    //创建子进程
    pid_t pid = fork();
    if (pid > 0)
    {
        //父进程
        printf("i am parent process, pid: %d\n", getpid());
        //关闭写端
        close(pipefd[1]);
        char buf[1024] = {0};

        int flags = fcntl(pipefd[0], F_GETFL); //获取原来的flag
        flags |= O_NONBLOCK;                   //修改flag的值
        fcntl(pipefd[0], F_SETFL, flags);      //设置新的flag

        while (1)
        {
            //从管道的读取端读取数据
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("len : %d\n", len);
            printf("parent recv: %s, pid = %d\n", buf, getpid());
            memset(buf, 0, 1024); //清空缓冲区
            sleep(1);
        }
    }
    else if (pid == 0)
    {
        //子进程
        printf("i am child process, pid: %d\n", getpid());
        //关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while (1)
        {
            //向管道中不断写入数据
            char *str = "hello, i am child";
            write(pipefd[1], str, strlen(str));
            sleep(5);
        }
    }

    return 0;
}

有名管道

匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件。

有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用(如read()、write()和close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。

有名管道(FIFO)和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:

  1. FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。
  2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
  3. FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。
有名管道 创建fifo文件

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

int mkfifo(const char *pathname, mode_t mode);
    参数:
        - pathname: 管道名称路径
        - mode: 文件权限 和 open的mode一样 8进制数
    返回值:
        成功 0
        失败 -1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //判断文件是否存在
    int ret = access("fifo1", F_OK);
    if (ret == -1)
    {
        printf("管道不存在,创建管道\n");

        ret = mkfifo("fifo1", 0664);
        if (ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    return 0;
}

向有名管道中写数据:

//向管道中写数据

/*
    有名管道的注意事项:
        1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
        2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道

    读管道:
        管道中有数据,read返回实际读到的字节数
        管道中无数据:
            管道写端被全部关闭,read返回0,(相当于读到文件末尾)
            写端没有全部被关闭,read阻塞等待
    
    写管道:
        管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
        管道读端没有全部关闭:
            管道已经满了,write会阻塞
            管道没有满,write将数据写入,并返回实际写入的字节数。
*/

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

int main(int argc, char const *argv[])
{
    //判断文件是否存在
    int ret = access("testfifo", F_OK);
    if (ret == -1)
    {
        printf("管道不存在,创建管道\n");

        //创建管道文件
        ret = mkfifo("testfifo", 0664);
        if (ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    //只写方式打开管道
    int fd = open("testfifo", O_WRONLY);
    if (fd == -1)
    {
        perror("open err");
        exit(0);
    }
    for (int i = 0; i < 100; i++)
    {
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);
        printf("write data : %s\n", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);

    return 0;
}

读有名管道的数据

//从管道中读数据
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

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

    //打开管道文件
    int fd = open("testfifo", O_RDONLY);
    if (fd == -1)
    {
        perror("open err");
        exit(0);
    }

    //读数据
    while (1)
    {
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if (len == 0)
        {
            printf("写端断开链接了...\n");
            break;
        }
        printf("recv buf: %s\n", buf);
    }
    close(fd);

    return 0;
}
posted @ 2023-05-17 18:37  言叶以上  阅读(29)  评论(0编辑  收藏  举报