进程间通信(IPC)---- 命名管道以及信号SIGPIPE的捕捉

命名管道的特点

命名管道也叫有名管道(FIFO),他有以下特点:

  1. 能够用于非亲缘进程之间的通信,当然亲缘进程肯定也可以;

  2. 命名管道和无名管道原理类似,都是通过在进程之间开辟一点中间介质,不同的进程通过往这个介质中读写内容,即完成了信息的收发;
    在这里插入图片描述

  3. 顾名思义,命名管道同无名管道相比就是因为他有名,那就是文件名,文件件都可以使用open() 函数打开并返回一个文件描述符,所以,我们就不用指定必须要亲缘进程才能通信,非亲缘进程之间只要使用open() 函数打开同一个文件描述符,就可完成不同进程通信了;

  4. 同样,在命名管道中,如果一个管道的读端全部关闭,但是还有进程尝试往里面写内容,该进程就会收到内核发来的一个终止进程的信号(SIGPIPE),我们同样可以使用signal() 函数来捕捉该信号;可参考无名管道这篇博客;

命名管道的使用

函数原型
 #include <sys/types.h>
 #include <sys/stat.h>

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

成功返回 0 ;失败返回 -1,且错误 errno 的值会被设置为以下值:

EACCES:路径名中的一个目录不允许搜索(执行)权限。
EDQUOT:用户在文件系统上的磁盘块或信息节点配额已用完。
EEXIST:路径名已经存在。这包括路径名是符号链接的情况,不管是否悬空。

参数
  1. pathname:被创建管道文件的文件路径名;
  2. mode:指定被创建时的原始权限,一般为0664,要包含读写权限;
命名管道单向通信

程序要点:当我们使用mkfifo() 创建文件时,如果失败或者该管道文件已存在都会返回 -1;但是如果存在这一条件,我们是可以继续执行读写的,所以在判断函数是否失败时,要除开已存在这一条件,通过上面所说的,errno的值,判断
errno != EEXITS即可;单向通信发送端代码:

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

#define FIFO_NAME "./fifo1"
#define SEND_MSG "Hello World"

void err_quit(char *estr)
{
    perror(estr);
    exit(-1);
}

void signal_func1(int signo)
{
    remove(FIFO_NAME);
    exit(-1);
}

void signal_func2(int signo)
{
    printf("因为向没有读端的管道中发写数据,所以接收到进程终止信号!\n");
    exit(-1);
}

int main(int argc,char *argv[])
{
    char         buf[1024];
    int          rv = -1;
    int          fd = -1;

    signal(SIGINT,signal_func1);  /*若接收到退出的信号,则先删除创建的管道文件,再退出 */
    signal(SIGPIPE,signal_func2);

    rv = mkfifo(FIFO_NAME,0664);
    if((rv < 0) && (errno != EEXIST))
    /* 创建fifo,如果错误或者该管道文件已存在,则rv 会返回-1,但他存在的这种情况我们是可以继续操作的,所以不能退出,要将他忽略 */
    {
        err_quit("mkfifo failure");
    }

    if((fd = open(FIFO_NAME,O_WRONLY)) < 0)
    {
        err_quit("open faliure");
    }

    int i = 1;
    while(1)
    {
        write(fd,SEND_MSG,12);
        printf("第 %d 次写入成功\n",i);
        i++;
        sleep(1);
    }

    return 0;
    

}

    要注意使用open() 打开的权限,为了避免一些不当的操作,例如在只有一个管道的的时候,发送方尝试自己读管道中的信息造成抢读(接收方无法获取数据),在使用open() 函数打开时就设置 只写 的权限,接收方使用 只读 的权限;

接收端代码:

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

#define FIFO_NAME "./fifo1"

void err_quit(char *estr)
{
    perror(estr);
    exit(-1);
}

void signal_func(int signo)
{
    remove(FIFO_NAME);
    exit(-1);
}

int main(int argc,char *argv[])
{
    char         buf[1024];
    int          rv = -1;
    int          fd = -1;

    signal(SIGINT,signal_func);  /*若接收到退出的信号,则先删除创建的管道文件,再退出 */

    rv = mkfifo(FIFO_NAME,0664);
    if((rv < 0) && (errno != EEXIST))
    /* 创建fifo,如果错误或者该管道文件已存在,则rv 会返回-1,但他存在的这种情况我们是可以继续操作的,所以不能退出,要将他忽略 */
    {
        err_quit("mkfifo failure");
    }
        

    if((fd = open(FIFO_NAME,O_RDONLY)) < 0)
    {
        err_quit("open faliure");
    }
    
    int i = 1;
    while(1)
    {
        bzero(buf,sizeof(buf));
        read(fd,buf,sizeof(buf));
        printf("第 %d 次接收成功:%s\n",i,buf);
        i++;
    }

    return 0;
    

}

需要注意的是,当管道的读端还没有被打开过时,尝试往里面写会被阻塞,若打开过,但又被关闭了,尝试往里面写的程序就会被发送终止进程的信号;
下面验证:

代码演示与验证

编译发送方代码,编译接收方代码:
在这里插入图片描述
首先,运行发送方代码:
在这里插入图片描述
会发现,当首先运行发送方时,因为管道的读端还没有被打开过,所以写端将被阻塞于此,并没有像管道内写内容;此时在其他终端使用 ls 命令可以看到,在当前路径下,创建了一个 fifo1 的文件:
在这里插入图片描述
该文件在程序结束后,就会被删除,因为程序中使用了remove()命令;

运行接收端:
在这里插入图片描述
这时接收端可以接收到数据了,那此时此刻,发送端又是什么情况呢:
在这里插入图片描述
我们发现,发送端也成功执行了;
按照我之前所说的,如果这时断开接收方,则管道的读端在打开后均被关闭,
那么,发送方在接下来的第一次发送数据时,就会收内核发送的SIGPIPE信号,于是我们捕捉该信号,并加入打印信息,验证:

记住,一定是先结束接收方才满足条件哦:
运行程序,先断开接收方,就可以看到:
在这里插入图片描述
程序退出,且退出原因是向没有读端的管道中写;

命名管道双向通信

命名管道的双向通信与无名管道一样,那就是使用两次mkfifo() 函数,创建两个管道文件,一个是A进程 —> B进程,另一个是B进程 —> A进程;
代码:两个进程几乎一样;不要同时都先读就可以了;
进程1:

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

#define FIFO_NAME1 "./fifo1"
#define FIFO_NAME2 "./fifo2"
#define SEND_MSG "Hello World"

void err_quit(char *estr)
{
    perror(estr);
    exit(-1);
}

void signal_func(int signo)
{
    remove(FIFO_NAME1);
    remove(FIFO_NAME2);
    exit(-1);
}

void signal_func2(int signo)
{
    printf("因为向没有读端的管道中发写数据,所以接收到进程终止信号!\n");
    exit(-1);
}
int main(int argc,char *argv[])
{
    char         buf[1024];
    int          rv = -1;
    int          fd1 = -1;
    int          fd2 = -1;

    signal(SIGINT,signal_func);  /*若接收到退出的信号,则先删除创建的管道文件,再退出 */
    signal(SIGPIPE,signal_func2);

    rv = mkfifo(FIFO_NAME1,0664);
    if((rv < 0) && (errno != EEXIST))
    /* 创建fifo,如果错误或者该管道文件已存在,则rv 会返回-1,但他存在的这种情况我们是可以继续操作的,所以不能退出,要将他忽略 */
    {
        err_quit("mkfifo failure");
    }

    rv = mkfifo(FIFO_NAME2,0664);
    if((rv < 0) && (errno != EEXIST))
    {
       err_quit("mkfifo failure");
                     
    }

    if((fd1 = open(FIFO_NAME1,O_WRONLY)) < 0)
    {
       err_quit("open faliure");
    }

    if((fd2 = open(FIFO_NAME2,O_RDONLY)) < 0)
    {             
        err_quit("open faliure");
    }

    while(1)
    {
        bzero(buf,sizeof(buf));
        write(fd1,SEND_MSG,12);
        read(fd2,buf,sizeof(buf));
        printf("recv:%s\n",buf);   
        sleep(1);
    }

    return 0;
    

}

进程2:

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

#define FIFO_NAME1 "./fifo1"
#define FIFO_NAME2 "./fifo2"
#define SEND_MSG "Hello World"

void err_quit(char *estr)
{
    perror(estr);
    exit(-1);
}

void signal_func(int signo)
{
    remove(FIFO_NAME1);
    remove(FIFO_NAME2);
    exit(-1);
}

void signal_func2(int signo)
{
    printf("因为向没有读端的管道中发写数据,所以接收到进程终止信号!\n");
    exit(-1);
}
int main(int argc,char *argv[])
{
    char         buf[1024];
    int          rv = -1;
    int          fd1 = -1;
    int          fd2 = -1;

    signal(SIGINT,signal_func);  /*若接收到退出的信号,则先删除创建的管道文件,再退出 */
    signal(SIGPIPE,signal_func2);

    rv = mkfifo(FIFO_NAME1,0664);
    if((rv < 0) && (errno != EEXIST))
    /* 创建fifo,如果错误或者该管道文件已存在,则rv 会返回-1,但他存在的这种情况我们是可以继续操作的,所以不能退出,要将他忽略 */
    {
        err_quit("mkfifo failure");
    }

    rv = mkfifo(FIFO_NAME2,0664);
    if((rv < 0) && (errno != EEXIST))
    {
       err_quit("mkfifo failure");
                     
    }

    if((fd1 = open(FIFO_NAME1,O_RDONLY)) < 0)
    {
       err_quit("open faliure");
    }

    if((fd2 = open(FIFO_NAME2,O_WRONLY)) < 0)
    {             
        err_quit("open faliure");
    }

    while(1)
    {
        bzero(buf,sizeof(buf));
        write(fd2,SEND_MSG,12);
        read(fd1,buf,sizeof(buf));
        printf("recv:%s\n",buf);
        
        sleep(1);
    }

    return 0;
    

}

这样,两个进程都可以在发送消息给另一个进程的同时,接收另一个进程的消息了

进程1:在这里插入图片描述
进程2:
在这里插入图片描述
命名管道的缺点:
虽然命名管道解决了无名管道只能亲缘进程间通信的局限,但是,还是只能两个进程之间通信,无法形成网状通信结构,所以后来便引出了:消息队列,信号量与共享内存!

发布了18 篇原创文章 · 获赞 37 · 访问量 4999

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/104873544