进程间的通信——管道篇

进程间的通信——消息队列https://blog.csdn.net/q496958148/article/details/79951727
进程间的通信——共享内存https://blog.csdn.net/q496958148/article/details/79953349
进程间的通信——信号量https://blog.csdn.net/q496958148/article/details/79977093

进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。
通常进程间通信有以下几种:

  1. 管道
  2. 消息队列
  3. 共享内存
  4. 信号量

管道

  • 管道是Unix中比较老的进程间通信的方式
  • 我们把从一个进程链接到另一个进程的数据流称为“管道”
    这里写图片描述

匿名管道:

//功能————创建匿名管道
int pipe(int pipefd[2]);
//fd:文件描述符数组,pipefd[0]代表读端,pipefd[1]代表写端
//返回值0代表成功,失败返回错误码

实例:

//实现目的:从键盘读取数据,写入管道,再从管道读取数据,写到显示器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main()
{
    int pipefd[2];
    char buf[256];
    int len;

    if(pipe(pipefd) != 0)
    {
        perror("pipe");
        exit(1);
    }
    while(fgets(buf,256,stdin))
    {
        len = strlen(buf);
        if(write(pipefd[1],buf,len) != len)
        {
            perror("write");
            break;
        }
        memset(buf,0x00,sizeof(buf));
        if((len = read(pipefd[0],buf,256)) == -1)
        {
            perror("open");
            break;
        }
        if(write(1,buf,len) != len)
        {
            perror("write");
            break;
        }
    }
}

这里写图片描述

上述实例是基于pipe函数创建的管道,而我们也可以基于“fork”函数创建子进程来进行管道实现。

操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同。

通过上述描述,我们可以知道如如所示的演示:
这里写图片描述
实例:

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

int main()
{
    int pipefd[2];
    char buf[256] = "hello,i am a man!\n";
    int len;
    char arr[256];

    if(pipe(pipefd) != 0)
    {
        perror("pipe");
        exit(1);
    }
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }
    else if(id == 0)
    {
        close(pipefd[0]);
        if(write(pipefd[1],buf,strlen(buf)) < 0)
        {
            perror("write");
            return -1;
        }
        close(pipefd[1]);
    }
    else
    {
        close(pipefd[1]);
        if(read(pipefd[0],arr,strlen(buf)) < 0)
        {
            perror("read");
            return -1;
        }
        close(pipefd[0]);
        printf("%s",arr);
    }
    return 0;
}

这里写图片描述

管道读写规则:

  • 当没有数据可读时
    O_NONBLOCK disabke:read调用堵塞,及进程暂停执行,直到有数据来时再读
    O_NONBLOCK enable:read调用返回-1,errno为EAGAIN
  • 当管道满时
    O_NONBLOCK disabke:write调用堵塞,直到有进程读取数据
    O_NONBLOCK enable:write调用返回-1,errno为EAGAIN
  • 如果所有的管道写端对应的文件描述符关闭,read返回0
  • 如果所有的管道读端对应的文件描述符关闭,write操作会产生SIGPIPE信号,使得进程退出
  • 当要写入的数据大小小于PIPE_MAX时,Linux将保证写入的原子性
  • 当要写入的数据大小大于PIPE_MAX时,Linux将不保证写入的原子性

管道特点:

  • 只能用于具有共同祖先(因为共同祖先代码相同,数据写实拷贝)之间的通信,通常进程fork创建子进程用来通信
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步和互斥
  • 管道是半双工的,数据只能单向流动;而需要双向通信时则需要俩个管道
    这里写图片描述

命名管道:
- 管道的实现有个限制:必须具有许愿关系的进程通信
- 如果我们想在不相关的进程间通信,我们就得使用FIFO文件来做这项工作,它被成为“命名管道
- 命名管道是一种特殊类型的文件

创建命名管道:

  1. 可以在命令行上创建
    [XX]# mkfifo filename
  2. 也可以在程序内创建
    int mkfifo(const char *filename,mode_t mode);

    实例:

fifo.c:

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

int main()
{
    umask(0);
    if(mkfifo("fifoname",0644) < 0)
    {
        perror("mkfifo");
        return-1;
    }
    int infd = open("fifoname",O_RDONLY);
    if(infd < 0)
    {
        perror("open");
        return -1;
    }
    char buf[256];
    while(1)
    {
        buf[0] = 0;
        ssize_t s = read(infd,buf,sizeof(buf)-1);
        if(s > 0)
        {
            buf[s-1] = 0;
            printf("xiaoming:%s\n",buf);
        }
        else if(s == 0)
        {
            printf("xiaoming quit.\n");
            break;
        }
        else
        {
            perror("read");
            return -1;
        }
    }
    close(infd);
    return 0;
}

1fifo.c:

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

int main()
{
    int outfd = open("fifoname",O_WRONLY);
    if(outfd < 0)
    {
        perror("open");
        return -1;
    }
    char buf[256];
    while(1)
    {
        buf[0] = 0;
        printf("pleas enter:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0)
        {
            buf[s] = 0;
            write(outfd,buf,strlen(buf));
        }
        else
        {
            perror("read");
            return -1;
        }
    }
    close(outfd);
    return 0;
}

Makefile:

.PHONY:all
all:fifo 1fifo


fifo:fifo.c
    gcc $^ -o $@;
1fifo:1fifo.c
    gcc $^ -o $@;

.PHONY:clean
clean:
    rm -f fifo 1fifo

效果图:
这里写图片描述

命名管道和匿名管道的区别:

  • 匿名管道由pipe函数创建并打开
  • 命名管道是由mkfifo函数创建,用open打开的
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于他们创建和打开的方式不同,一旦创建并打开了,他们具有相同的语义

命名管道的打开规则:

  • 如果打开操作是为读操作而打开的FIFO
    1>O_NONBLOCK disable:阻塞直到有相应进程为写操作而打开FIFO
    2>O_NONBLOCK enable:立刻返回成功
  • 如果打开操作是为写操作而打开的FIFO
    1>O_NONBLOCK disable:阻塞直到有相应进程为读操作而打开FIFO
    2>O_NONBLOCK enable:立即返回失败,错误码为ENXIO
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页