• Linux下的管道通信


    无名管道通信

    无名管道只能用于具有亲缘关系的进程之间的通信,即父子进程或者兄弟进程之间,它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数,但它不是普通文件,并不属于其他任何文件系统,只存在于内存中。
    管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1],其中fd[0]固定用于读管道,而fd[1]固定用于写管道。管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close函数逐个关闭各个文件描述符。
    一个管道共享了多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。
    创建管道调用函数pipe来实现,所需要的头文件和函数原型如下。

    #include
    int pipe(int fd[2])

    创建成功,函数返回0,创建失败返回-1。
    创建无名管道的一个简单例子如下。

    #include 
    #include 
    
    int main()
    {
    	int pipe_fd[2];
    	if(pipe(pipe_fd) < 0)  //创建无名管道
    	{
    		printf("Pipe create error.\n");
    		return -1;
    	}
    	else
    		printf("Pipe create success.\n");
    	close(pipe_fd[0]);   //关闭管道读描述符
    	close(pipe_fd[1]);   //关闭管道写描述符
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    程序运行后先成功创建一个无名管道,打印创建成功的信息,然后再将其关闭。
    用pipe函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。通常先创建一个管道,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道。因此,父子进程分别拥有自己的读写的通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。
    下面的例子是子进程写,父进程读,完整的代码如下。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        int pipe_fd[2];
        pid_t pid;   
        char buf_r[100];
        char* p_wbuf;
        int r_num;
        memset(buf_r,0,sizeof(buf_r));  //初始化内存
        if(pipe(pipe_fd) < 0)      //创建管道
        {
            printf("Pipe create error.\n");
            return -1;
        }
        pid = fork();  //创建一子进程
     
        if(pid == 0)   //子进程
        {
            close(pipe_fd[0]);   //关闭子进程读描述符
            sleep(1);     //确保父进程关掉了写描述符
            if(write(pipe_fd[1],"Hello pipe!",11)!= -1)   //写入管道的是"Hello pipe!"
                printf("Process write success!\n");
            close(pipe_fd[1]);  //关闭子进程写描述符
            exit(0);
        }
        else if(pid > 0)  //父进程
        {
            close(pipe_fd[1]);    //关闭父进程写描述符
            if((r_num = read(pipe_fd[0],buf_r,100)) > 0)
            {
                printf("length : %d   string : %s\n",r_num,buf_r);
            } 
            close(pipe_fd[0]);  //关闭父进程读描述符 
            exit(0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    程序运行后的结果如下图所示。
    在这里插入图片描述
    父子进程在运行时,它们的先后次序并不能保证,因此,为了保证父进程已经关闭了读描述符,可在子进程中调用sleep函数。


    有名管道通信(FIFO)

    无名管道只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限制,它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO是严格地遵循先进先出规则的,对管道及FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
    有名管道的创建可以使用函数mkfifo(),在创建管道成功之后,就可以使用open、read、write这些函数了。对于为读而打开的管道可在open中设置O_RDONLY,对于为写而打开的管道可在open中设置O_WRONLY,这里与普通文件不同的是涉及阻塞的问题。由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在open函数中设定为O_NONBLOCK。
    对于读进程,若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞直到有数据写入。若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。
    对于写进程,若该管道是阻塞打开,写进程将一直阻塞直到有读进程读出数据。若该管道是非阻塞打开,即使当前FIFO内没有读操作,写进程都会立即执行读操作。
    简单来说,阻塞方式就是要等另一方先执行,非阻塞方式则不会考虑另一方的状态。
    创建有名管道调用函数mkfifo来实现,所需要的头文件和函数原型如下。

    #include
    #include
    int mkfifo(const char *filename,mode_t mode)

    创建成功,函数返回0,创建失败返回-1。
    下面的例子包含一个读管道和一个写管道,在读管道里面创建管道,用户输入的内容以写管道里main函数的参数传入,然后读管道读出管道的内容。
    read.c文件的内容如下。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define FIFO "/tmp/myfifo"   //定义路径
    
    int main(int argc,char** argv)
    {
        char buf_r[100];
        int fd;
        int nread;
        if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST))  //创建有名管道,O_CREAT文件不存在时创建,O_EXCL如果使用O_CREAT时文件存在,那么可返回错误消息
            printf("Create FIFO failed.\n");
        printf("Preparing for reading bytes...\n");
        memset(buf_r,0,sizeof(buf_r));    //初始化内存
        fd = open(FIFO,O_RDONLY|O_NONBLOCK,0);    //打开有名管道,非阻塞方式
        //fd = open(FIFO,O_RDONLY,0);        //打开有名管道,阻塞方式
        if(fd == -1)
        {
            perror("Open");
            exit(1);
        }
        while(1)
        {
            memset(buf_r,0,sizeof(buf_r));
            if((nread = read(fd,buf_r,100)) == -1)
            {
                if(errno == EAGAIN)
                    printf("No data yet.\n");
            }
            printf("String read from FIFO is %s\n",buf_r);
            sleep(1);  //设置读管道的速度
        }
        pause();  //将调用进程挂起直至捕捉到信号为止,通常可以用于判断信号是否已到
        unlink(FIFO);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    write.c文件的内容如下。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define FIFO "/tmp/myfifo"
    
    int main(int argc,char** argv)  //argc是传入参数的个数,argv[0]为自身运行目录路径和程序名,argv[1]存放的是用户输入
    {
        int fd;
        char w_buf[100];
        int nwrite;
        if(fd == -1)
            if(errno == ENXIO)
                printf("Open error, no reading process.\n");
        fd = open(FIFO,O_WRONLY|O_NONBLOCK,0);  //打开FIFO管道,并设置非阻塞标志
        //fd = open(FIFO,O_WRONLY,0);  //打开FIFO管道,并设置阻塞标志
        if(argc == 1)
            printf("Please send string.\n");
        strcpy(w_buf,argv[1]);
        if((nwrite=write(fd,w_buf,100))==-1)   //向管道中写入字符串
        {
            if(errno==EAGAIN)
                printf("The FIFO has not been read yet, please try later.\n");
        }
        else
            printf("String write to the FIFO is %s\n",w_buf);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    打开两个终端,编译后先执行read,后执行write,因为read要先创建管道,read执行时如果没有权限就加sudo执行,如下图所示。
    在这里插入图片描述
    然后在执行write时需要带上用户的输入,也就是需要写入的内容。
    在这里插入图片描述
    这样就实现了有名管道的通信。
    需要注意的是,如果一开始就使用sudo执行read的话,所创建的管道在write的时候也需要加sudo,否则无法通信。因为 sudo ./read执行时创建的管道文件的所有者和组都是root,write执行时无权访问,要不就加sudo,要不就使用chown和chgrp命令修改管道文件的所有者。

  • 相关阅读:
    PAT(Advanced Level) Practice(with python)——1118 Birds in Forest
    MES如何做好生产过程监控,本文给出了详细解答
    uboot启动流程-uboot代码重定位说明一
    JDBC 连接数据库的四种方式
    【杂记】Windows首页挟持病毒查杀过程记录
    Compose LazyColumn 对比 RecyclerView ,谁的性能更好?
    【iOS】—— 循环引用问题
    2024华为OD机试真题-攀登者1-C++(C卷D卷)
    使用labelme自制coco格式数据集
    计算机应用专业,报软考应该选什么?
  • 原文地址:https://blog.csdn.net/weixin_42570192/article/details/133499898