进程间如何进行通信[IPC](一)使用管道(1)

本文详细介绍了进程间通信的管道(Pipe)机制,包括无名管道的特点、读写规则以及在多进程通信中的应用。同时,讨论了有名管道(FIFO)的概念,强调其作为文件系统中特殊文件的存在,以及与无名管道的区别。通过示例代码展示了如何创建、读写匿名管道和FIFO,阐述了管道在数据传输、同步和互斥中的作用。
摘要由CSDN通过智能技术生成

进程间通信IPC(一)管道(1)

不同进程(一般指用户进程)之间的资源是独立的,在一个进程中无法直接访问另一个进程中的资源(比如读时共享,写时拷贝)

通信目的:数据传输,通知事件的发生,资源共享(涉及同步和互斥),进程控制

同步:(直接制约关系)为了完成某个任务而创建的多个进程,在某些位置上协调它们的工作次序而等待,传递信息所产生的制约关系,制约源自于它们的相互合作

异步:多道程序环境允许多个程序并发执行,当时进程的执行一般不是一贯到底的,它以不可预知的速度向前推进,这就是进程的异步性

互斥:间接制约关系,当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界区资源的进程退出临界区后,另一个进程才被允许去访问此临界资源

通信方法(一)管道

无名(匿名)管道

管道是内核内存中的一个缓冲区

管道在很多方面都很像文件:不带任何结构的字节序列

1.读取数据:

(1)管道读取默认阻塞,进程从管道中读取数据时,进程被挂起直到数据被写进管道

(2)管道的读取结束标志:当所有的写者关闭了管道的写数据端时,试图从管道读取数据的调用返回0,意味着文件的结束

(3)因为管道是一个队列,所以多个读者可能会造成麻烦

2.写入数据:

(1)写入数据阻塞到管道有空间去容纳新的数据

(2)写入必须保证一个最小的块大小,POSIX规定内核不会拆分小于512字节的块,Linux保证管道中可以存在4096字节连续缓存

(3)如果所有读者都已经将管道读端关闭,则对管道的写入会失败

单个进程内部通信可以使用管道,多个进程之间通信也可以使用管道

但是管道只能在有关联的进程之间通信(如父子进程)

fork调用后,两个管道文件描述符都是打开的,一对管道文件描述符只能保持父子进程单方向通信

因为管道中的数据读出后才能再写入,所以父子进程必须分别关闭fd[0]或fd[1]

从管道中读取数据是一次性操作,读完管道内的数据就会被丢弃

#include <unistd.h>
   int pipe(int pipefd[2]);
   //pipefd[1]为写,pipefd[0]为读
//成功返回0,失败返回-1

//查看管道缓冲大小命令:
ulimit -a
//查看管道缓冲大小函数
    long fpathconf(int fd, int name);

//使用strace命令可以查看进程之间的管道通信

Unix/Linux中的命令的IO重定向输入输出有些就用到了管道,且无法随机访问数据

管道的底层数据结构是一个循环队列

例如:who|sort |:管道

将who的输出输入到sort进行排序后输出

如果想要双向通信,则需要使用两个管道

socket提供了一个创建全双工管道的系统调用:

#include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socketpair(int domain, int type, int proto‐
       col, int sv[2]);

这个调用squid服务器程序有使用

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret==-1){
        perror("pipe");
        exit(0);
    }
    pid_t pid =fork();
    if(pid>0){
        //父进程写数据
        char *buf = "hello,world";
        write(pipefd[1],buf,strlen(buf));
        sleep(1);
    }else if(pid==0){
        //子进程读数据
        char buf[1024];
        int len = read(pipefd[0],buf,sizeof(buf));
        buf[len] = '\0';
        printf("child process received %s\n",buf);
    }
}

image

//管道的大小
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret==-1){
        perror("pipe");
        exit(0);
    }
    //获取管道缓冲区大小
    printf("size:%ld\n",fpathconf(pipefd[0],_PC_PIPE_BUF));
    return 0;
}

默认大小4096字节

image

使用匿名管道实现命令输入输出的重定向,重定向有三种基本方法(在另一篇博客中有写:Unix编程实践教程笔记(四) IO重定向 - ziggystardust - 博客园 (cnblogs.com))

//实现ps aux | 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret==-1){
        perror("pipe");
        exit(0);
    }
    pid_t pid = fork();
    //在子进程中执行ps命令,并且重定向输出到父进程(从标准输出到父进程)
    if(pid>0){
        //父进程接收数据,并打印到标准输出
        char buf[2048] = {0};
        int len;
        while((len = read(fd[0],buf,sizeof(buf)-1))>0){
            printf("%s",buf);
            memset(buf,0,2048);
        }
        wait(NULL);

    }else if(pid==0){
        //子进程
        //可以使用dup2来代替close(1); dup(fd);
        dup2(fd[1],STDOUT_FILENO);//将文件描述符1重定向
        execlp("ps","ps","aux",NULL);
        perror("execlp");
        //使用dup2

    }
    return 0;
}

注意write调用是阻塞的,是有缓冲区的,如果数据过大,则会分包发送,直到发完

管道的读写特点

这里用了一下视频的截图:

image

设置管道为非阻塞

即为设置文件描述符非阻塞

fcntl();

如果管道没有数据,子进程还在read,且设置管道非阻塞:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret==-1){
        perror("pipe");
        exit(0);
    }
    pid_t pid =fork();
    if(pid>0){
        //父进程写数据
        char *buf = "hello,world";
        while(1){
            write(pipefd[1],buf,strlen(buf));
            sleep(5);
        }      
    }else if(pid==0){
        //子进程读数据
        char buf[1024] = {0};
        int flag = fcntl(pipefd[0],F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(pipefd[0],F_SETFL,flag);
        while(1){
            int len = read(pipefd[0],buf,sizeof(buf));
            buf[len] = '\0';
            printf("len = %d\n",len);
            printf("child process received %s\n",buf);
            memset(buf,0,1024);
            sleep(1);
        }  
    }
    return 0;
}

image

特殊管道FIFO (命名管道)

命名管道可以用于无关联的进程之间相互通信

以FIFO的文件形式存于文件系统中,大多数操作和匿名管道是相同的

和匿名管道不同的地方:FIFO在文件系统中作为一个特殊文件存在,FIFO的内容却是放在内存中

FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用

FIFO有名字,不相关的进程可以打开有名管道进行通信

三种System V IPC也能用于无关联的多个进程之间通信,因为它们都使用一个全局唯一的键值来表示一条信道

#可以使用命令来创建有名管道的实体文件
mkfifo filename

image

也可以使用函数来创建:

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

       int mkfifo(const char *pathname, mode_t mode);
//pathname:管道名称(路径)可以是相对/绝对路径
//mode:文件权限,和open 的文件权限一样(8进制数)

不支持lseek文件定位,严格先进先出

常见的文件I/O操作都可以用于FIFO

#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
int main()
{
    //首先判断文件是否已经存在
    if((access("fifo2",F_OK))==-1){
        printf("管道不存在,创建\n");
    }
    else{
        printf("已存在\n");
        exit(1);
    }
    int ret = mkfifo("fifo2",0664);
    if(ret==-1){
        perror("mkfifo");
        exit(0);
    }
    return 0;
}

注意这个管道文件是不保存数据的,数据保存在内存中

//有名管道的读写
//1. write.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#define BUFSIZE 1024
int main()
{
    //首先判断文件是否已经存在
    if((access("fifoNew",F_OK))==-1){
        printf("管道不存在,创建\n");
        int ret = mkfifo("fifoNew",0664);
        if(ret==-1){
            perror("mkfifo");
            exit(0);
        }
    }

        int fd = open("fifoNew",O_WRONLY);
        for(int i = 0;i<100;i++){
            char buf[BUFSIZE];
            sprintf(buf,"write NO. %d message\n",i);
            printf("%s",buf);
            write(fd,buf,strlen(buf));
            sleep(1);
        }
        close(fd);

    return 0;
}

//2. read.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#define BUFSIZE 1024
int main()
{

    int fd = open("fifoNew",O_RDONLY);
    while (1)
    {
        char buf[BUFSIZE] = {0};
        int len =  read(fd,buf,BUFSIZ-1);
        if(len==0)
        {
            printf("write端断开/没有数据写入\n");
            break;
        }
        printf("recv message:%s\n",buf);
    }
    close(fd);
    return 0;
}

如果读端写端只有一方打开或只有一方在执行操作,那么会阻塞到open调用处

image

image

如果先运行读端,写端未写入时,读端也会阻塞到open调用

如果先将读端关闭,写端进程也会被关闭(接收到一个SIGPIPE信号,终止进程)(以上代码的测试结果是这样的)

image

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值