Linux下的管道通信

无名管道通信

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

#include <unistd.h>
int pipe(int fd[2])

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

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

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]);   //关闭管道写描述符
}

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

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

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);
    }
}

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


有名管道通信(FIFO)

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

#include <sys/types.h>
#include <sys/state.h>
int mkfifo(const char *filename,mode_t mode)

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

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

write.c文件的内容如下。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

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

猜你喜欢

转载自blog.csdn.net/weixin_42570192/article/details/133499898
今日推荐