🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见
创建命名管道的方法
进程间通信的本质是让不同的进程看到同一份资源。由于匿名管道只适用于具有血缘关系(具有共同祖先)的进程间进行通信,那如果是两个不相关的进程交换数据呢?该怎么实现呢?
我们可以使用FIFO文件实现不相关的进程的通信需求,这个文件常被称为命名管道。下面我们来看看如何创建命名管道↓↓↓
命令行创建命名管道
mkfifo [命名管道文件名]
下面示例中,在当前目录创建了一个名为connect-file的文件,通过ls -l
可以看到,connect-file的类型为p(管道)。↓↓↓
现在我们打开两个终端,左侧终端向connect-file写入,右侧终端从connect-file读取,这样就可以实现跨终端通信了↓↓↓
函数接口创建命名管道
由于FIFO(命名管道)也是文件,第一个参数pathname表示要在哪个目录下创建命名管道,第二个参数表示文件创建时的权限。
下面程序演示了如何使用mkfifo函数创建命名管道↓↓↓
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
mkfifo("connect-fifo", 0666);
return 0;
}
★ps:创建命名管道本质也是创建文件,文件的最终权限=mode&~umask。注意,命名管道创建时必须指定文件权限,不能省略。
那为什么不直接创建一个文件,让通信进程访问该文件呢?由于文件每次写入和删除内容,它都会更新到磁盘上,磁盘是一个慢速外设,故速度较慢。而命名管道是一个内存级文件,该文件创建后,在该文件执行通信任务时,任何读、写、修改操作都不会使用到磁盘(即不会落盘、刷盘),这样可以大大提高通信效率。
★ps:命名管道通信方式如何保证两个进程看到同一份资源?由于文件名=路径+具体文件名,同一个路径下不可能存在两个同名文件,故可以保证两个进程看到同一份资源。
命名管道的打开方式
如果想实现两个进程的通信,则需要让双方打开同一个文件,一个读,一个写。在没有特别指定的情况下,当读进程读取命名管道中的内容时,如果内部为空(写进程还没有写),则会阻塞等待;写进程写入命名管道的内容时,如果读进程还没有读,则会阻塞等待。
下面程序中,让写进程向已经存在的管道文件中写入,待写进程写入完毕后,再启动读进程↓↓↓
写进程代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
printf("write start:%u\n", time(NULL));
int fp = open("connect-fifo", O_WRONLY);
char* msg = "Jammingpro";
write(fp, msg, strlen(msg));
printf("write success!\n");
printf("write:%u\n", time(NULL));
}
读进程代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
printf("read start:%u\n", time(NULL));
int fp = open("connect-fifo", O_RDONLY);
char buffer[1024];
ssize_t n = read(fp, buffer, sizeof(buffer));
buffer[n] = '\0';
printf("read success! -> %s\n", buffer);
printf("read:%u\n", time(NULL));
return 0;
}
从上面程序执行可以知道,执行写的进程在读进程启动前将阻塞等待,待读进程启动后,读进程将命名管道中的数据读取走后,才会停止阻塞。
★ps:在写进程执行open系统调用时,就会开始阻塞,而不是等到执行write系统调用才阻塞
那如果让读进程先执行,写进程再执行呢?↓↓↓
读进程会阻塞等待,直到写进程进行写入。
从上面的程序执行结果可知,命名管道提供了同步机制,即必须先写入再读取。违背这个条件的话,则会出现上面的读端或写端阻塞的情况。如果我们不希望阻塞,则可以在open时,以O_NONBLOCK模式打开↓↓↓
int fd = open("connect-fifo", O_WDOBLY | O_NONBLOCK);
下面我们编写以O_NONBLOCK方式打开的读写端程序↓↓↓
wblock.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int fd = open("connect-fifo", O_WRONLY | O_NONBLOCK);
if(fd < 0)
{
perror("open error");
exit(1);
}
while(1)
{
char* msg = "Jammingpro";
int ret = write(fd, msg, strlen(msg));
if(ret == -1)
{
if(errno == EAGAIN)
{
printf("EAGAIN\n");
}
else
{
perror("write error");
exit(2);
}
}
else
{
printf("write success\n");
}
usleep(10000);
}
return 0;
}
rblock.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("connect-fifo", O_RDONLY | O_NONBLOCK);
int cnt = 0;
char buffer[1024];
while(1)
{
int n = read(fd, buffer, sizeof(buffer) - 1);
buffer[n] = '\0';
printf("%d->%s\n",cnt, buffer);
cnt++;
sleep(1);
}
return 0;
}
即使对应的命名管道文件已经存在,但如果读进程未执行(即没有进程以读方式打开命名管道文件),此时执行写进程,则会出现如下错误(6号错误)↓↓↓
如果读进程打开,写进程再打开,此时若命名管道已经被写满,则写进程会得到一个EAGAIN的错误码,该错误码可以通过errno.h头文件中的errno变量获取↓↓↓
对于读端来说,如果没有写端(或写端没有写入)时,读端可以从命名管道中读数据,只是这个数据为空而已。一旦有写端写入,则读端可以正常读取。
从上面可以得出,在设置以O_NONBLOCK方式打开命名管道时,一定要保证读进程先打开,否则写端进程无法写入。若读写均打开时,读端中途关闭,则写端随之关闭,无法正常写入。
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开;而命名管道使用mkfifo创建,用open打开(创建和打开接口分离);
- 匿名管道只能实现具有血缘关系的进程间进行通信;命名管道可以实现任意进程之间的通信。
命名管道的应用——使用Client&Server通信
由于命名管道可以让不同的进程进行通信,我们可以创建客户端Client.cc和服务器端Server.cc,让客户端发送消息给Server端,Server端从命名管道中读取消息并显式↓↓↓
Com.hpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "sc-fifo"
#define MODE 0666
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
Init()
{
int n = mkfifo(FILE_NAME, MODE);
if(n != 0)
{
perror("create error");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
int n = unlink("sc-fifo");
if(n != 0)
{
perror("delete error");
exit(FIFO_DELETE_ERR);
}
}
};
Server.cc
#include "Com.hpp"
int main()
{
Init init;
char buffer[1024];
int fd = open(FILE_NAME, O_RDONLY);
if(fd < 0)
{
perror("open error");
exit(FIFO_OPEN_ERR);
}
while(true)
{
read(fd, buffer, sizeof(buffer));
std::cout << "Client say # " << buffer << std::endl;
}
return 0;
}
Client.cc
#include "Com.hpp"
int main()
{
int fd = open(FILE_NAME, O_WRONLY);
if(fd < 0)
{
perror("create error");
exit(FIFO_OPEN_ERR);
}
std::string s;
while(true)
{
std::cout << "send # ";
std::cin >> s;
write(fd, s.c_str(), s.size());
}
return 0;
}
🎈欢迎进入从浅学到熟知Linux专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d