资讯专栏INFORMATION COLUMN

命名管道的阻塞和非阻塞模式的初步探讨

cgspine / 1396人阅读

摘要:前言进程间通信是指在不同进程之间传播或交换信息。其中可以用于不同主机上的进程间通信。命名管道有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。共享内存是最快的方式,它是针对其他进程间通信方式运行效率低而专门设计的。

前言

进程间通信(IPC, InterProcess Communication)是指在不同进程之间传播或交换信息。
主要的方式有管道(包括无名管道,高级管道和命名管道),消息队列, 信号量, 共享内存, Socket等。 其中Socket可以用于不同主机上的进程间通信。
进程通信的主要目的如下:

数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间

共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

IPC方式

匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

命名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

命名管道

本文主要讲解命令管道。
命名管道是在文件系统中作为一个特殊的设备文件存在的, 不同祖先的进程之间可以通过管道共享数据,当所有访问管道的进程执行结束后,命名管道将继续存在以便以后使用。

命名管道的创建
#include 
#include 
int mkfifo(const char *pathname, mode_t mode);

mkfifo用于创建一个FIFO文件, 参数mode决定文件权限。
The file creation mask of the process (see umask()) modifies the file permission bits of mode. The owner ID of the FIFO is set to the effective user ID of the process. The FIFO group ID is set to the effective group ID of the process.

如果成功,那么mkfifo()返回0;如果失败,那么返回-1, 并且errno被赋为下面的值:

EACCES: 某级路径无访问权限,或pathname指向的目录无写权限

EEXIST: 同名文件已存在

EINVAL: 在NuTCRCKER平台上描述路径不在fifos文录下

ENAMETOOLONG: pathname名太长

ENOENT: 路径中某一级目录不存在

ENOSPC: 磁盘满

ENOTDIR: 路径中某一级不是目录

EROFS:文件系统只读

详细信息可以查看: [这里](http://man7.org/linux/man-pag...)

file mode如下:

S_IRWXU: 00700 read, write, execute/search by owner

S_IRUSR: 00400 read permission, owner

S_IWUSR: 00200 write permission, owner

S_IXUSR: 00100 execute/search permission, owner

S_IRWXG: 00070 read, write, execute/search by group

S_IRGRP: 00040 read permission, group

S_IWGRP: 00020 write permission, group

S_IXGRP: 00010 execute/search permission, group

S_IRWXO: 00007 read, write, execute/search by others

S_IROTH: 00004 read permission, others

S_IWOTH: 00002 write permission, others

S_IXOTH: 00001 execute/search permission, others

S_ISUID: 0004000 set-user-ID on execution

S_ISGID: 0002000 set-group-ID on execution

S_ISVTX: 0001000 on directories, restricted deletion flag

系统I/O函数如open, close, read, write都可以操作FIFO文件

读/写,阻塞/非阻塞 阻塞

默认不指定O_NONBLOCK时即为阻塞模式

open以只读方式打开FIFO时, 要阻塞到某个进程为写而打开此FIFO

open以只写方式打开FIFO时,要阻塞到某个进程为读而打开此FIFO

open以只读+只写方式打开FIFO时,调用read函数时read会阻塞

调用write函数向FIFO写数据时,当缓冲区已满时write会阻塞

通信过程中若写进程先退出了,则调用read函数从FIFO里读数据时不阻塞;若写进程又重新运行,则调用read函数从FIFO里读数据时又恢复阻塞

通信过程中若读进程先退出, 则写进程向FIFO写数据,会收到SIGPIPE信号而退出

open以读写方式打开FIFO时,open不阻塞

非阻塞

open以只读方式打开FIFO,如果没有进程为写而打开FIFO, 只读open成功,并且open不阻塞

open以只写方式打开FIFO, 如果没有进程为读而打开FIFO,只写open将出错返回-1 (ENXIO: No Such device or address)

read, write读写FIFO中数据时不阻塞

通信过程中, 读进程退出后, 写进程向命名管道内写数据时,写进程会收到SIGPIPE信号而退出

Examples Non-Blocking

reader

//
// Created by         : Harris Zhu
// Filename           : fifo_read.cpp
// Author             : Harris Zhu
// Created On         : 2017-08-17 16:46
// Last Modified      :
// Update Count       : 2017-08-17 16:46
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FIFO_SERVER "myfifo"
#define OPEN_MODE O_RDONLY | O_NONBLOCK
#define FIFO_MODE O_CREAT|O_RDWR|O_NONBLOCK

int main(int argc, char** argv) {
    char buf_r[100];
    int fd;
    int nread;
    int res;
    if (((res=mkfifo(FIFO_SERVER, FIFO_MODE)) < 0) && (errno != EEXIST))
    {
        printf("can not creat fifoserver %d :
", res, errno);
        exit(1);
    }
    printf("preparing for reading bytes...
");
    char cmd[100];
    sprintf(cmd, "chmod 704 %s", FIFO_SERVER);
    system(cmd);
    fd = open(FIFO_SERVER, OPEN_MODE);
    if (fd == -1) {
        perror("error in openning fifo server");
        exit(1);
    }
    int i=0;
    int len;
    while (i++<21)
    {
        memset(buf_r, 0, sizeof(buf_r));
        if ((nread = read(fd, buf_r, sizeof(buf_r))) < 0) {
            if (errno == EAGAIN)
            {
                printf("no data yet
");
                sleep(1);
            }
        } else {
            if(nread > 0)
            {
                printf("read %s from FIFO %d 
", buf_r, i);
            }
            sleep(1);
        }
    }
//    pause();
    close(fd);
    unlink(FIFO_SERVER);
    return 0;
}

writer

//
// Created by         : Harris Zhu
// Filename           : fifo_write.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 18:04
// Last Modified      :
// Update Count       : 2017-08-17 18:04
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FIFO_SERVER "myfifo"
#define FIFO_MODE O_CREAT|O_NONBLOCK|O_RDWR
#define FILE_MODE O_WRONLY | O_NONBLOCK

int main(int argc, char** argv)
{
    int fd;
    char w_buf[100];
    char w_t_buf[50];
    const char *hstr = "hello world";
    if(mkfifo(FIFO_SERVER, FIFO_MODE<0)&& (errno != EEXIST))
    {
        perror("failed to create fifo server");
        exit(1);
    }

    char cmd[100];
    sprintf(cmd, "chmod 704 %s", FIFO_SERVER);
    system(cmd);

    int nwrite;
    fd = open(FIFO_SERVER, FILE_MODE);
    if (fd == -1)
    {
        if (errno == ENXIO)
        {
            printf("open errop;no reading process
");
        } else {
            perror("open error");
            exit(EXIT_FAILURE);
        }
    }

    if (argc >= 2)
    {
        strcpy(w_t_buf, argv[1]);
    } else {
        strcpy(w_t_buf, hstr);
    }
    int i=0;
    int n;
    time_t tp;
    while(i++<20)
    {
        time(&tp);
        n=sprintf(w_buf, "Process %d is sending %s at %s", getpid(), w_t_buf, ctime(&tp));
        if ((nwrite = write(fd, w_buf, n)) < 0)
        {
            if (errno == EAGAIN)
            {
                printf("the fifo has not been read yet.Please try later
");
            } else {
                exit(1);
            }
        }
        printf("Send Message to FIFO: %s 
", w_buf);
        sleep(1);
    }
    close(fd);
    return 0;
}

先运行reader, 然后运行writer
Makefile内容如下

build:
    gcc -g read.c -o read
    gcc -g write.c -o write

run:
    xterm -e ./read &
    sleep 1
    xterm -e ./write &

clean:
    rm -rf read write

因为reader先开读,当writer开始的时候,reader已经在while里走过和个循环了,所以reader会先结束,这时writer还想向FIFO里写数据,就会遇到“Broken pipe”问题
如果把writer的等待时间改小, 那么收端比较慢,所以fifo里的数据会比较多,reader收到的数据就不是一行一行

harriszh Fri 17:30@ ~/trunk/cpp/fifo1$ ./fifo_read
preparing for reading bytes...
read Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello worl from FIFO
read d at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Proce from FIFO
read ss 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at  from FIFO
read Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11 from FIFO
read 863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri A from FIFO
read ug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 i from FIFO
read s sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 from FIFO
read  17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sen from FIFO
read ding hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:3 from FIFO
read 1:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending  from FIFO
read hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57  from FIFO
read 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello from FIFO
read  world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
 from FIFO
Blocking

看完非阻塞再看阻塞就简单点了
代码如下
Reader

//
// Created by         : Harris Zhu
// Filename           : write.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 22:05
// Last Modified      :
// Update Count       : 2017-08-17 22:05
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) 
    do { 
        perror(m); 
        exit(EXIT_FAILURE); 
    } while(0)

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

    int outfd = open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1)
        ERR_EXIT("open Makefile2 error");

    int infd;
    infd = open("tp", O_RDONLY );
    if (infd == -1)
        ERR_EXIT("open tp error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);

    close(infd);
    close(outfd);
    unlink("tp"); // delete a name and possibly the file it refers to
    return 0;
}

Writer

//
// Created by         : Harris Zhu
// Filename           : read.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 22:05
// Last Modified      :
// Update Count       : 2017-08-17 22:05
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) 
    do { 
        perror(m); 
        exit(EXIT_FAILURE); 
    } while(0)

int main(int argc, char *argv[])
{
    mkfifo("tp", 0644);
    int infd = open("Makefile", O_RDONLY );
    if (infd == -1)
        ERR_EXIT("open Makefile error");

    int outfd;
    outfd = open("tp", O_WRONLY );
    if (outfd == -1)
        ERR_EXIT("open tp error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);
    printf("Makefile2 is created successfully!
")

    close(infd);
    close(outfd);

    return 0;
}

先运行writer, 再运行reader, 原因是FIFO由writer创建
Makefile

build:
    gcc -g read.c -o read
    gcc -g write.c -o write

clean:
    rm -rf Makefile2
    rm -rf ./read ./write

run:
    xterm -e ./write &
    sleep 0.1
    xterm -e ./read &

.PHONY: clean

运行后,reader端会把从writer写入的内容保存到Makefile2里

后言

非阻塞模式的理解有点绕,在编写程序时也要注意两者的通讯机制以及关闭条件。
可以自己先写个例子,看看能不能正确工作。一般第一个例子是非常难以正确工作的,需要在调试的过程中,来加深了解。
如果有任何疑问,欢迎发邮件给我。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/10233.html

相关文章

  • 从0到1优雅实现PHP多进程管理

    摘要:目的综上所述,我的目标就是实现基于模式实现的多进程管理工具。备注下文中,父进程统称为子进程统称为。最后我们通过下图来简单的总结和描述这个多进程实现的过程控制上面实现了多进程和多进程的常驻内存,那如何去管理呢答案多进程通信。 _ | | _ __ __ _ _ __...

    lakeside 评论0 收藏0
  • Linux 进程间通信:管道

    摘要:微博微信公众号系统技术前言管道是环境中历史最悠久的进程间通信方式。用一个图来说明这个程序的状态就是这样的一个进程自己给自己发送消息这当然不叫进程间通信,所以实际情况中我们不会在单个进程中使用管道。子进程关闭管道的写端,只读管道。 本文由云+社区发表作者:邹立巍 版权声明: 本文章内容在非商业使用前提下可无需授权任意转载、发布。 转载、发布请务必注明作者和其微博、微信公众号地址,以...

    shuibo 评论0 收藏0
  • Node.js中流使用

    摘要:流是基于事件的用于管理和处理数据而且有不错的效率借助事件和非阻塞库流模块允许在其可用的时候动态处理在其不需要的时候释放掉使用流的好处举一个读取文件的例子使用同步读取一个文件程序会被阻塞所有的数据都会被读取到内存中换用读取文件程序不会被阻塞但 流是基于事件的API,用于管理和处理数据,而且有不错的效率.借助事件和非阻塞I/O库,流模块允许在其可用的时候动态处理,在其不需要的时候释放掉. ...

    h9911 评论0 收藏0
  • Linux: linux 匿名管道

    摘要:当管道满的时候未设置调用阻塞,直到有进程读走数据设置调用返回,值为如果所有管道写端对应的文件描述符被关闭,则返回如果所有管道读端对应的文件描述符被关闭,则操作会产生信号当要写入的数据量不大于时,将保证写入的原子性。 相信很多在linux平台工作的童鞋, 都很熟悉管道符 |, 通过它, 我们能够很灵活的将几种不同的命令协同起来完成一件任务.就好像下面的命令: echo 123 | awk...

    李义 评论0 收藏0
  • I/O Multiplexing -- Linux I/O 多路复用

    摘要:上图中,进程调用了,系统函数在有数据报到达并已经拷贝到应用程序缓冲区时,或者有错误发生时才会返回最常见的错误是被信号中断。多路复用在多路复用模型,我们会阻塞在这些系统函数中,而不是阻塞在真正的调用上。 文章还会涉及到同步 I/O,异步 I/O,阻塞 I/O 和非阻塞 I/O 首先我们需要理解以下概念: Linux用户态和内核态 在现在操作系统中,CPU通常会在两种不同的模式下...

    dingda 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<