Linux高级编程:IPC之管道

一、无名管道

        1.1 无名管道的概述

        管道(pipe)又称无名管道。 无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。

任何一个进程在创建的时候,系统都会 给他分配4G的虚拟内存,分为3G的用户空间和1G 的内核空间,内核空间是所有进程公有的,无名管道就是创建在内核空间的,多个进程知道 同一个无名管道的空间,就可以利用他来进行通信。

无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行 读操作,一个负责执行写操作

     

管道是最古老的UNIX IPC方式,其特点是:

1、半双工,数据在同一时刻只能在一个方向上流动。

2、数据只能从管道的一端写入,从另一端读出。

3、写入管道中的数据遵循先入先出的规则。

4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格 式,如多少字节算一个消息等。

5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。

6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。

7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写 更多的数据。

8、管道没有名字,只能在具有公共祖先的进程之间使用

  1.2 无名管道的创建 ---pipe函数

 #include <unistd.h> 

 int pipe(int pipefd[2]);

 功能:创建一个有名管道,返回两个文件描述符负责对管道进行读写操作

参数:

 pipefd:int型数组的首地址,里面有两个元素

 pipefd[0] 负责对管道执行读操作

pipefd[1] 负责对管道执行写操作

返回值: 成功:0 

失败:‐1

基础案例:

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

int main()
{

    // 使用pipe创建一个无名管道
    int fd_pipe[2];

    if (pipe(fd_pipe) == -1)
    {

        perror("fail to pipe");
        exit(1);
    }

printf("fd_pipe[0]=%d\n",fd_pipe[0]);
printf("fd_pipe[1]=%d\n",fd_pipe[1]);


//描述符就可以操作无名管道,所以通过文件IO中的read和write函数对无名管道进行操作
//通过write函数向无名管道中写入数据
 //fd_pipe[1]负责执行写操作
if(write(fd_pipe[1],"hello world",11) == -1){
    perror("fail to write");
    exit(1);
}
printf("测试一下sizeof(hello) =%ld\n",sizeof("hello"));

//写完之后,再写
//如果管道中有数据,再次写入的数据会放在之前数据的后面,不会把之前的数据替换(注意,前面的数据并没有字符串结束符\0)
write(fd_pipe[1],"write agin",sizeof("write agin")); //11,strlen 不会将\0记入长度


char buf[32]="kkk";
ssize_t bytes;

//fd_pipe[0]负责执行读操作
if(bytes = read(fd_pipe[0],buf,20)==-1){
    perror("fail  to read");
    exit(1);
}

printf("buf = [%s]\n", buf);
printf("bytes = %ld\n", bytes);



//读完后再读,如果管道中没有数据了 发生阻塞
read(fd_pipe[0],buf,sizeof(buf));

printf("buf = [%s]\n", buf);
printf("bytes = %ld\n", bytes);

read(fd_pipe[0],buf,sizeof(buf));
printf("buf = [%s]\n", buf);
printf("bytes = %ld\n", bytes);



}

补充案例:

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


int main(){


    int pip_fd[2];

    pipe(pip_fd);


    // write(pip_fd[1],"hello world\0",12); //如果使用结束符,发现就没的数据不能连续的写入,    
    //只能读到 hello world
    //write(pip_fd[1],"hello world",12);  效果同上,多出的一位,自动补全了结束符
    write(pip_fd[1],"hello world",11);//如果前面写的字符串没有结束符,才会在管道中继续写

    write(pip_fd[1],"write agin!",12);



    char buf[32]="";
    read(pip_fd[0],buf,32);
    printf("buf =[%s]\n",buf);



    return 1;
}

 1.3 无名管道实现进程间通信

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

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

    int fd_set[2];
    int ret;
    ret = pipe(fd_set);
    if (ret == -1)
    {
        perror("fail to create pipe");
        exit(1);
    }

    int pid = fork();

    if (pid > 0)
    { // 父进程代码 :从标准输入IO获取字符串,写入管道
        char buf[128] = "";
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0'; // 封尾

            // 写入管道
            if (write(fd_set[1], buf, sizeof(buf)) == -1)
            {
                perror("fail to write");
                exit(1);
            }
        }
    }
    else if (pid == 0)
    {
        // 子进程代码:去管道里读
        char buf[128] = "";
        while (1)
        {
            if (read(fd_set[0], buf, sizeof(buf)) == -1)
            {
                perror("fail to read");
                exit(1);
            }
            printf("buf=[%s]\n", buf);
        }
    }
    else if (pid == -1)
    {
        perror("创建进程失败");
        exit(1);
    }

    return 0;
}

注意:

利用无名管道实现进程间的通信,都是父进程创建无名管道,然后再创建子进程,子进 程继承父进程的无名管道的文件描述符,然后父子进程通过读写无名管道实现通信

  1.4 无名管道的读写规律

  1.4.1 读写端都存在,只读不写

读写端都存在,只读不写,
   如果管道中有数据,会正常读取数据
   如果管道中没有数据,则读操作会阻塞等待,直到有数据为止

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

int main(void)
{
    int pip_fd[2];
    if(pipe(pip_fd)==-1){

        perror("fail to pipe");
        exit(-1);
    }

    //读写端都存在,只读不写,
    //如果管道中有数据,会正常读取数据
    //如果管道中没有数据,则读操作会阻塞等待,直到有数据为止
    write(pip_fd[1],"hello_world",11);

    char buf[128]="";
    if(read(pip_fd[0],buf,sizeof(buf))==-1){
        perror("fail to read");
        exit(-1);
    }

    printf("buf = %s\n", buf);
    printf("before fork pid %d\n",getpid());


    int pid;
    pid = fork();

    if(pid<0){
        perror("fail to fork");
        exit(-1);
    }
    if(pid>0){
        //父进程
        printf("father pid %d\n",getpid());
        //父进程继续读
           strcpy(buf,""); //清空buf
            if(read(pip_fd[0],buf,sizeof(buf))==-1){
                perror("fail to read");
                exit(-1);
            }
            printf("buf = %s\n", buf);

    }else{
         printf("son pid %d ,子进程5秒后写入\n",getpid());
        sleep(5);
        //测试让子进程写入
        write(pip_fd[1],"I am son\0",9);
        printf("son pid %d\n",getpid());
    }


    return 0;
}

 1.4.2 读写端都存在,只写不读

如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数也 会阻塞等待,

管道缓冲默认为64k;

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


//读写段都存在,只写不读
int main(){

    int pipe_fd[2];
    pipe(pipe_fd);
    int count=0;
    for(;;){
        write(pipe_fd[1],"6666",1024);
        count++;
        printf("count = %d\n",count);
    }
    return 1;

}

 1.4.3 只有读端,没有写端

关闭写文件描述符,只有读端

//如果原本管道中有数据,则读操作正常读取数据

 //如果管道中没有数据,则read函数会返回0

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


//只有读端
int main(){


    int pipe_fd[2];
    pipe(pipe_fd);

    //管道中原本有数据
write(pipe_fd[1],"hello world",12);

    //关闭写端
    close(pipe_fd[1]);

    ssize_t bytes;
    char buf[32]="";
    bytes =read(pipe_fd[0],buf,32);
    printf("the bytes is %d\n",bytes);
    printf("buf=[%s]\n",buf);

    strcpy(buf,"");
    bytes =read(pipe_fd[0],buf,32);
    printf("the bytes is %d\n",bytes);
    printf("buf=[%s]\n",buf);


    return 1;
}

 1.4.4 只有写端,没有读端

管道会破裂

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

void handler(int sig)
{
    printf("SIGPIPE信号产生了,管道破裂\n");
}

int main(int argc, char const *argv[])
{
    signal(SIGPIPE, handler);

    int pipe_fd[2];

    if (pipe(pipe_fd) == -1)
    {
        perror("fail to pipe");
        exit(1);
    }

    // 关闭读文件描述符
    close(pipe_fd[0]);

    // 发出破裂信号
    write(pipe_fd[1], "6666", 10);

    return 0;
}

关于信号 :SIGPIPE

        1.5 通过fcntl 函数设置文件阻塞特性

fcntl - manipulate file descriptor

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

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

    int pip_fd[2];

    char buf[] = "hello world";
    pid_t pid;
    if (pipe(pip_fd) == -1)
    {
        perror("fail to pipe");
        exit(-1);
    }
    pid = fork();

    if (pid < 0)
    {
        perror("fail to fork");
        exit(-1);
    }

    if (pid == 0)
    {
        // 子进程
        while (1)
        {
            sleep(5);
            write(pip_fd[1], buf, sizeof(buf));
        }
    }
    else
    {
        // 将pip_fd[0] 设置为阻塞
        // fcntl(pip_fd[0],F_SETFL,0);
        // 非阻塞
        fcntl(pip_fd[0], F_SETFL, O_NONBLOCK);
        while (1)
        {
            memset(buf, 0, sizeof(buf)); // memset - fill memory with a constant byte
            read(pip_fd[0], buf, sizeof(buf));
            printf("buf=[%s]\n", buf);
        }
    }

    return 0;
}

二、文件描述符

2.1 文件描述符的概述

文件描述符是非负整数,是文件的标识。
用户使用文件描述符(file descriptor)来访问文件。
利用open打开一个文件时,内核会返回一个文件描述符。
每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出
设备文件被打开,对应的文件描述符0、1、2 记录在表中。
在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件
描述符记录在表中。

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

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

#if 1
    // 在进程中打开其他文件时,
    // 系统会返回文件描述符表中最小可用的文件描述符,
    // 并将此文件描述符记录在进程的文件描述符表中。
    // 注意:新创建的文件描述符的值不一定是最大的
    int fd1, fd2, fd3;
    fd1 = open("file.txt", O_RDONLY | O_CREAT, 0664); // 3
    fd2 = open("file.txt", O_RDONLY | O_CREAT, 0664); // 4
    fd3 = open("file.txt", O_RDONLY | O_CREAT, 0664); // 5
    printf("fd=[%d]\n", fd1);
    printf("fd=[%d]\n", fd2);
    printf("fd=[%d]\n", fd3);

    int fd;
    close(fd1);
    fd = open("file.txt", O_RDONLY | O_CREAT, 0664);
    printf("fd=[%d]\n", fd);

#endif

    return 0;
}

注意:
Linux中一个进程最多只能打开NR_OPEN_DEFAULT
(即1024)个文件,故当文件不再使用时应及时调用close函数
//实验2
int count;
while(1){
    int fd;
    fd = open("file.txt", O_RDONLY | O_CREAT, 0664);
    if(fd==-1){
        perror("fail to open");
        printf("the count is %d\n",count);
        exit(1);
    }
    count++;
}

注:还有 stdin,stdout, stderr,分别是 0,1,2

2.2 文件描述符的复制

1、dup函数

  # include <unistd.h>
  int dup ( int oldfd );
  功能:复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文
件描述符表中最小可用的文件描述符。
  参数:
          要复制的文件描述符 oldfd
返回值:
        成功:新文件描述符。
        失败:返回- 1 ,错误代码存于 errno 中。
案例1:使用dup函数复制文件描述符
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    // 通过dup函数复制一个文件描述符
    int fd;

//dup执行后给返回值文件描述符分配的值是文件描述符表中最小可用的文件描述符
    fd = dup(1); // 3

    printf("fd =%d\n", fd);
//由于通过dup函数将1这个文件描述符复制了一份为fd,所以fd现在就相当于1,
//所以写数据就是想终端写入数据
    write(fd, "测试dup函数!!\n", strlen("测试dup函数!!\n")+1);

    return 0;
}

案例2:实现输出重定向的功能

dup 复制旧的fd 到当前进程最小可用 文件描述符(一般为3)

如果,把标准输出的文件描述符关闭,则当前进程最小可用的描述符就变成了1,此时旧fd就被重定向到标准输出

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


int main(int argc, char const *argv[])
{
    
int fd_file;
fd_file =open("file.txt",O_RDWR|O_CREAT|O_APPEND,0664);

//关闭标准输出
close(1);

 int fd =  dup(fd_file); //dup之后 一定是1

printf("hello world\n");
printf("nihao\n");

printf("fd = %d\n", fd);


    return 0;
}

看一下文件内容:

2、dup2

  # include <unistd.h>
  int dup2 ( int oldfd , int newfd )
  功能:复制一份打开的文件描述符 oldfd
  并分配新的文件描述符 newfd newfd 也标识 oldfd 所标识的文件。
  注意:
  newfd 是小于文件描述符最大允许值的非负整数,
  如果 newfd 是一个已经打开的文件描述符,则首先关闭该文件,然后再复制。
  参数:
  oldfd :要复制的文件描述符
  newfd :分配的新的文件描述符
  返回值:
  成功:返回 newfd
  失败:返回 1 ,错误代码存于 errno

三、有名管道

3.1 有名管道概述

命名管道(FIFO) 和管道(pipe)基本相同,但也有一些显著的不同,
其特点是 :
1、半双工,数据在同一时刻只能在一个方向上流动。
2、写入FIFO中的数据遵循先入先出的规则。
3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格
式,如多少字节算一个消息等。
4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可
以实现不相关进程间通信,但FIFO中的内容却存放在内存中。
5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
6、从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更
多的数据。
7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。

3.2 有名管道的创建

方法 1 :用过 shell 命令 mkfifo 创建有名管道
  mkfifo 文件名
方法 2 :使用函数 mkfifo       库函数 man 3 mkfifo

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

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

功能:创建一个有名管道,产生一个本地文件系统可见的文件 pathname
参数:
  pathname :有名管道创建后生成的文件,可以带路径
  mode :管道文件的权限,一般通过八进制数设置即可,例如 0664
  返回值:
          成功: 0
          失败: 1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
    

    if(mkfifo("fifo_file",0664)==-1){

        printf("errno =%d\n",errno);
        //如果管道文件已经存在,不需要报错退出,直接使用即可,所以需要在错误输出之前把//因为文件存在的错误排除
        if(errno!=EEXIST){
            perror("fail to mkfifo");
            exit(1);
        }


    }

    return 0;
}

关于错误码对照,在

/usr/include/asm-generic/errno-base.h文件中

3.3 有名管道的基本读写操作

由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道
进行操作,
但是不能使用lseek修改管道文件的偏移量
注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在
内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的
操作实质就是对内核空间的操作

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


#define FIFONAME "fifo_file"

int main(int argc, char const *argv[])
{
    if((mkfifo(FIFONAME,0664))==-1){

        //17
        if(errno!=EEXIST){
            perror("fail to create");
            exit(-1);
        }

    }

//对有名管道进行操作
//管道后写入的数据会保存在之前写入数据的后面,不会替换
//如果管道中没有数据了,读操作会阻塞
//通过open函数打开管道文件并得到文件描述符
int fd;
fd =open(FIFONAME,O_RDWR); //可读可写
if(fd==-1){
    perror("fail to open"); //打开失败
    exit(-2);
}    

//通过write函数向管道中写入数据
if(write(fd,"nihao testFIFO",strlen("nihao testFIFO"))==-1){
    perror("fail to write"); //打开失败
    exit(-3);
}

write(fd, "nihao beijing", strlen("nihao beijing"));


//通过read函数读取管道中的数据
char buf[32]="";
if(read(fd,buf,sizeof(buf))==-1){
    perror("fail to read");
    exit(1);
}
printf("buf = [%s]\n", buf);



//上面已经读完,此处被阻塞
if(read(fd,buf,sizeof(buf))==-1){
    perror("fail to read");
    exit(1);
}
printf("buf = [%s]\n", buf);

close(fd);


    return 0;
}

这里关于write  和 read 补充一句:

这两个系统调用,不会因为写入,读到 \0,\n而被截断。

真正被 \0截断的,其实是  char buf[] ,参考下面这个代码

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


#define FIFONAME "fifo_file"

int main(int argc, char const *argv[])
{
    if((mkfifo(FIFONAME,0664))==-1){

        //17
        if(errno!=EEXIST){
            perror("fail to create");
            exit(-1);
        }

    }

//对有名管道进行操作
//管道后写入的数据会保存在之前写入数据的后面,不会替换
//如果管道中没有数据了,读操作会阻塞
//通过open函数打开管道文件并得到文件描述符
int fd;
fd =open(FIFONAME,O_RDWR); //可读可写
if(fd==-1){
    perror("fail to open"); //打开失败
    exit(-2);
}    

//通过write函数向管道中写入数据
if(write(fd,"hell\0o\nworld",sizeof("hell\0o\nworld"))==-1){
    perror("fail to write"); //打开失败
    exit(-3);
}

write(fd, "beijing", strlen("beijing"));


//通过read函数读取管道中的数据
char buf[32]="";
int len;
if((len=read(fd,buf,sizeof(buf)))==-1){
    perror("fail to read");
    exit(1);
}
printf("len =%d\n",len);
printf("buf = [%s]\n", buf); //\0是字符串结束符。打印buf输出时候就被截断了,write写入,read读 遇到\0是没有任何影响的


//可以通过打印buf 查看 完整字符内容 包含\0
int i;

for(i=0;i<len;i++){
    printf("buf = [%c]\n", (char)buf[i]);
}

off_t pos;
 pos = lseek(fd,-3,SEEK_CUR);
 //注意 管道读写是一次性的
 printf("当前文件读写位置为 %ld\n",pos);



//上面已经读完,此处被阻塞  

if(read(fd,buf,sizeof(buf))==-1){
    perror("fail to read");
    exit(1);
}
printf("buf = [%s]\n", buf);



 close(fd);


    return 0;
}

思考一下,为什么调用Iseek 返回结果仍然是-1

3.4 有名管道实现进程间通信

由于有名管道在本地创建了一个管道文件,所以不相关的进程间也可以实现通信
send.c
// 两个管道
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO1 "fifo_1"
#define FIFO2 "fifo_2"

int main(int argc, char const *argv[])
{
    printf("This is sender \n");

    // 创建两个fifo文件
    int fd_r, fd_w;
    if ((mkfifo(FIFO1, 0664)) == -1)
    {

        if (errno != EEXIST)
        {
            perror("fail to create!");
            exit(1);
        }
        printf("errno = %d\n", errno);
    }

    if ((mkfifo(FIFO2, 0664)) == -1)
    {

        if (errno != EEXIST)
        {
            perror("fail to create!");
            exit(1);
        }
        printf("errno = %d\n", errno);
    }
    // 以只读的方式打开 fifo2
    // 以只写的方式打开fifo1  
    fd_w = open(FIFO1, O_WRONLY);
    printf("打开fd_w =%d\n", fd_w);

    if ((fd_w) == -1)
    {

        perror("fail to open");
        exit(1);
    }

    fd_r = open(FIFO2, O_RDONLY); 
    printf("打开fd_r =%d\n", fd_r);
    if (fd_r == -1)
    {
        perror("fail to open");
        exit(1);
    }



    char buf[128] = "";
    ssize_t bytes;

    while (1)
    {
        // 从标准输入中获取,遇到换行结束,验证,fgets 会不会自动加上\0

        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0'; // 封尾
        // 发送操作
        if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(-1);
        }
        else
        {
            printf("写入 --%s\n", buf);
        }

        // 读操作
        if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(-1);
        }
        printf("from recv: %s\n", buf);
    }

    return 0;
}

recv.c

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

#define FIFO1 "fifo_1"
#define FIFO2 "fifo_2"

int main(int argc, char const *argv[])
{
    printf("This is recv \n");

    // 创建两个fifo文件
    int fd_r, fd_w;
    if ((mkfifo(FIFO1, 0664)) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to create!");
            exit(-1);
        }
    }

    if ((mkfifo(FIFO2, 0664)) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to create!");
            exit(-1);
        }
    }

    //以只读打开 fifo1
    if ((fd_r = open(FIFO1, O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(-2);
    }
    //只写方式,打开 fifo2
    if ((fd_w = open(FIFO2, O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(-2);
    }

    char buf[128] = "";
    ssize_t bytes;

    for (;;)
    {
     
        // 读操作
        if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(-1);
        }
        printf("from send: %s\n",buf);


           // 从标准输入中获取,遇到换行结束,验证,fgets 会不会自动加上\0
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0'; // 封尾
        // 发送操作
        if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(-1);
        }
    }

   
    return 0;
}

3.5 有名管道的读写规律(阻塞)

 1 读写端都存在,只读不写
读写端都存在,只读不写
  如果原本管道中有数据,则正常读取
  如果管道中没有数据,则 read 函数会阻塞等待

//读写都存在,只读不写

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

#define FIFO "fifo_file"

int main(int argc, char const *argv[])
{
    
    //1、创建管道文件
  if( mkfifo(FIFO,0664)==-1){
    if(errno!=EEXIST){
        perror("fail to create");
        exit(-1);
    }
  }


    //2、以可读可写方式打开(让读写端都存在)
int fd;
if((fd=open(FIFO,O_RDWR))==-1){
    perror("fail to open");
    exit(-1);
}



write(fd,"测试",sizeof("测试"));

//读
char buf[32]="";
int len;
len = read(fd,buf,sizeof(buf));
printf("buf=[%s] ,len=%d\n",buf,len);

len = read(fd,buf,sizeof(buf));
printf("buf=[%s] ,len=%d\n",buf,len);

    return 0;
}

2 读写端都存在,只写不读

// 读写都存在,只写不读

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

#define FIFO "fifo_file"

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

  // 1、创建管道文件
  if (mkfifo(FIFO, 0664) == -1)
  {
    if (errno != EEXIST)
    {
      perror("fail to create");
      exit(-1);
    }
  }

  // 2、以可读可写方式打开(让读写端都存在)
  int fd;
  if ((fd = open(FIFO, O_RDWR)) == -1)
  {
    perror("fail to open");
    exit(-1);
  }

  int num=0;
  while (1)
  {
    //写满阻塞,默认64k
    write(fd, "666", 1024);
    num++;
    printf("num = %d\n",num);
  }

  return 0;
}

3 在一个进程中,只有读端,没有写端
// 一个进程 只有读端

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

#define FIFO "fifo_file"

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

  // 1、创建管道文件
  if (mkfifo(FIFO, 0664) == -1)
  {
    if (errno != EEXIST)
    {
      perror("fail to create");
      exit(-1);
    }
  }

  // 2、以只读方式打开(让读写端都存在),阻塞在open处
  int fd;
  if ((fd = open(FIFO, O_RDONLY)) == -1)
  {
    perror("fail to open");
    exit(-1);
  }

//不执行
printf("*************************\n");

  return 0;
}
4 在一个进程中,只有写端,没有读端
// 一个进程 只有读端

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

#define FIFO "fifo_file"

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

  // 1、创建管道文件
  if (mkfifo(FIFO, 0664) == -1)
  {
    if (errno != EEXIST)
    {
      perror("fail to create");
      exit(-1);
    }
  }

  // 2、以只写方式打开(让读写端都存在),阻塞在open处
  int fd;
  if ((fd = open(FIFO, O_WRONLY)) == -1)
  {
    perror("fail to open");
    exit(-1);
  }

//不执行
printf("*************************\n");

  return 0;
}

5.一个进程读,一个进程写

将上面3和4两个代码一起运行,保证有名管道读写端都存在
规律:
只要保证有名管道的读写端都存在,不管是几个进程,都不会再open这阻塞了
如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0
如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生
SIGPIPE信号,默认的处理方式是退出进程

reader.c

// 一个进程 只有读端

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

#define FIFO "fifo_file"



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

  // 1、创建管道文件
  if (mkfifo(FIFO, 0664) == -1)
  {
    if (errno != EEXIST)
    {
      perror("fail to create");
      exit(-1);
    }
  }

  // 2、以只写方式打开(让读写端都存在),阻塞在open处
  int fd;
  if ((fd = open(FIFO, O_RDONLY)) == -1)
  {
    perror("fail to open");
    exit(-1);
  }


printf("*************************\n");
char buf[32]="";
int len;
while (1)
{
  len = read(fd,buf,sizeof(buf));
  printf("buf=[%s],len=%d\n ",buf,len);
}


  return 0;
}

writer.c

// 一个进程 只有xie端

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

#define FIFO "fifo_file"

void handler(){
  printf("触发了 SIGPIPE\n");
}

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

signal(SIGPIPE,handler);
  // 1、创建管道文件
  if (mkfifo(FIFO, 0664) == -1)
  {
    if (errno != EEXIST)
    {
      perror("fail to create");
      exit(-1);
    }
  }

  // 2、以只写方式打开
  int fd;
  if ((fd = open(FIFO, O_WRONLY)) == -1)
  {
    perror("fail to open");
    exit(-1);
  }


printf("*************************\n");

while (1)
{
  write(fd,"hello",sizeof("hello"));
  sleep(3);
  printf("666\n");
}



  return 0;
}

如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0

(read直接返回0,不阻塞)

如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生
SIGPIPE信号,默认的处理方式是退出进程
​​​​​​​

3.6 有名管道的读写规律(非阻塞)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值