Linux进程之间的通讯(IPC)--管道

进程间通讯介绍(IPC):
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

一、管道

1.无名管道(pipe)

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1.1 概述:

特点:
(1)管道建立内核的内存中的,父进程与子进程退出后,这个管道就消失了,不会在磁盘中存在;
(2)它是半双工的,数据只能向一个方向流动;双方需要互相通信时,需要建立起两个管道;
(3)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间);
(4)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中;
(5)管道中的数据被读走就没了;
(6)进程被确认下来哪端是读还是写的话,在这个进程在这个程序的读写端基本就被定死了。

管道的实质是内核利用 环形队列 的数据结构在 内核缓冲区 中的一个实现,默认设置大小为4K,可以通过ulimit -a命令查看。由于利用 环形队列 进行实现,读和写的位置都是自动增长的,不能随意改变,一个数据只能被读取一次,读取后数据就会从缓冲区中移除。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程进入等待队列,当空的缓冲区有新数据写入或者满的缓冲区有数据读出来时,就唤醒等待队列中的进程继续读写。

使用步骤:
(1)父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
(2)父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
(3)父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

管道的读写行为
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
(1)如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
(2)如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
(3)如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。
如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

1.2 原型:

1 #include <unistd.h>
2 int pipe(int fd[2]);
返回值:若成功返回0,失败返回-1;
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开;

要关闭管道只需将这两个文件描述符关闭即可。

**注意点:读端读取写端数据时,如果写端没有数据,那么读端的进程会被阻塞;

1.3 管道编程实战
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
        int fd[2];
        int pid;
        char buf[128];
//      int pipe(int pipefd[2]);
        if(pipe(fd) == -1){//创建管道
                printf("creat pipe failed\n");
        }
        pid = fork();//创建进程

        if(pid<0){
                printf("creat child failed\n");
        }
        else if(pid > 0){
                sleep(3);//这里父进程先睡3秒,让子进程先运行。
                printf("this is father\n");
                close(fd[0]);//父进程关闭读功能(无名管道就是要确认读写端然后关闭不要的功能(就是fd[i]某一个))
                write(fd[1],"hello from father",strlen("hello from father"));
                wait(NULL);
        }else{
                printf("this is child\n");
                close(fd[1]);//子进程关闭写功能
                read(fd[0],buf,128);
                printf("read from father:%s\n",buf);
                exit(1);
        }
        return 0;
}

2 命名管道(fifo)

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

命名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以命名管道的文件形式存在于文件系统中,这样,即使与命名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过命名管道相互通信,因此,通过命名管道不相关的进程也能交换数据。值的注意的是,命名管道严格遵循先进先出(first in first out),对匿名管道及有名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。命名管道的名字存在于文件系统中,内容存放在内存中。

2.1 特点

(1)与无名管道都是把数据存到管道流,一端读一端写;
(2)FIFO可以在无关的进程之间交换数据,与无名管道不同;
(3)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

匿名管道和命名管道总结
(1)管道是特殊类型的文件,在满足先入先出的原则条件下可以进行读写,但不能进行定位读写。
(2)无名管道是单向的,只能在有亲缘关系的进程间通信;有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
(3)无名管道阻塞问题:无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出。
(4)命名管道阻塞问题:命名管道在打开时需要确实对方的存在,否则将阻塞。即以读方式打开某管道,在此之前必须一个进程以写方式打开管道,否则阻塞。此外,可以以读写(O_RDWR)模式打开命名管道,即当前进程读,当前进程写,不会阻塞。

2.2 原型:

(1) #include <sys/stat.h>
(2) 返回值:成功返回0,出错返回-1
(3) int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同,表示文件权限。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志O_NONBLOCK的区别:
(1)若没有指定O_NONBLOCK(默认),命名管道在打开时需要确实对方的存在,否则将阻塞。只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。可以以读写(O_RDWR)模式打开命名管道,即当前进程读,当前进程写,不会阻塞。
(2)若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 ,如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

2.3 命名管道的数据通信编程实现

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。
以下代码是通过FIFO来读写数据,分两个进程(open管道为读端与open管道为写段),首先先看看读端的代码(read.c)

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

#define MAX_BUF_SIZE 1024

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr,"Usage: %s argv[1]\n",argv[0]);
        return -1;
    }

    if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
    {
        fprintf(stderr,"Fail to mkfifo %s : %s",argv[1],strerror(errno));
        return -1;
    }
    int fd;
    if((fd = open(argv[1],O_RDONLY)) < 0)
    {

        fprintf(stderr,"Fail to open mkfifo %s : %s",argv[1],strerror(errno));
        return -1;
    }
    
    int n;
    char buf[MAX_BUF_SIZE];
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        n = read(fd, buf, sizeof(buf));
        printf("Read %d bytes\nRECV MSG:%s\n",n, buf);
    }
    return 0;
}

以下是open(管道)写端的代码:

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

#define MAX_BUF_SIZE 1024

int main(int argc, char* argv[])
{
    if(argc < 2)
    {
        fprintf(stderr,"usage: %s argv[1].\n",argv[0]);
        return -1;
    }
    if(mkfifo(argv[1], 0666) < 0 && errno != EEXIST)
    {
        fprintf(stderr,"Fail to mkfifo %s : %s.",argv[1],strerror(errno));
        return -1;
    }
    int fd;
    if((fd = open(argv[1],O_WRONLY)) < 0)
    {
        fprintf(stderr,"Fail to open mkfifo %s : %s.",argv[1],strerror(errno));
        return -1;
    }
    printf("open for write success\n");
    
    int n;
    char buf[MAX_BUF_SIZE];
    while(1)
    {
        memset(buf, 0, sizeof(buf));       
        printf(">");
        scanf("%s",buf);
        
        n = write(fd, buf, strlen(buf) + 1);// 将\0也写入
        printf("Write %d bytes.\nSend MSG:%s\n",n, buf);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值