The world is more malleable than you think, and it is waiting for you ti hammer it into shape.--------这个世界的可塑性比你想的还要大,而它正等着你去敲打成型
文章目录
今天我们将要使用命名管道实现小黑与小白的聊天功能哦。
1,管道以及无名管道回顾
在这之前,小黑写了一篇管道还有无名管道的博客:
https://blog.csdn.net/weixin_46027505/article/details/105151719
2,为什么叫做命名管道?
-
它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),
-
它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
-
因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
3,FIFO文件和普通文件区别
有名管道又称为FIFO 文件,我们对有名管道的操作可以采用操作文件的方法,如使用 open, read,write 等,有名管道适用于任何两个进程间通信。
FIFO 文件
和普通文件
的区别
-
读取FIFO文件的进程只能以“O_RDONLY”方式打开FIFO 文件。
-
写FIFO文件的进程只能以“O_WRONLY”的方式打开FIFO文件。
-
FIFO文件里面的内容被读取后,就消失了,但是普通文件的内容读取后还存在。
4,有名管道相关操作API函数
mkfifo()创建管道(fifo 文件)
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname,mode_t mode)
函数功能
:创建一个FIFO文件(有名管道)
返回值
:成功 :0
失败:-1
参数说明
:
pathname:创建的fifo文件名字(带路径)
mode_t :文件的操作权限
删除管道unlink()
#include<unistd.h>
int unlink(const char * pathname);
函数功能
:删除文件(包括fifo文件)
返回值
:
成功 :0
失败 :-1
参数
:
pathname:要删除的文件名字(带路径)
其他API函数
下面的就是文件i/o的函数,一样的用法,就不阐明。
- 打开管道 open()
- 关闭管道 close()
- 读/写管道 read/write
5,简单代码,用于理解(使用命名管道实现单向通信)
下面实现两个任意进程间通信,创建两个进程,一个是读进程,另一个写进程;
写进程:主要是创建fifio 文件;并将数据写入文件, 退出;
读进程: 主要读取fifo 文件的数据,并打印出来,退出
- 写进程代码:a.c
#include <sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include <unistd.h>
#define FIFO_PATH "/home/xuxiaohei/FIFO/.file"
int main()
{
int fd;
char buf[1024]="HELLO, B proccss. I am A proccess";
mkfifo(FIFO_PATH , 0777);
fd=open(FIFO_PATH , O_WRONLY);//写进程打开方式只能以O_WRONLY方式
write(fd, buf, sizeof(buf));
close(fd);
}
- 读进程范例代码:b.c
#include<sys/types.h>
#include<sys/fcntl.h>
#include<unistd.h>
#include<stdio.h>
#define FIFO_PATH "/home/xuxiaohei/FIFO/.file"
int main()
{
int fd;
char buf[1024]={0};
fd =open(FIFO_PATH ,O_RDONLY);//读进程只能以O_RDONLY方式打开fifo文件
read(fd,buf,1024);
printf("read data from A proccess:%s\n",buf);
close(fd);
unlink(FIFO_PATH );
}
运行程序:
首先运行 a 程序,会处于阻塞状态,直到运行b程序读取fife文件数据;
这个容易理解:水管的另一端都没打开,你又怎么可能往里面注水呢? 所以你写端只能阻塞等待读端打开,才能连接
- 然后我们打开另外一个终端,ls 后发现多了一个文件,这个文件就是管道文件。
6,复杂代码,实现双向通信(实现类似QQ聊天的小应用)
6.1 简单介绍
其实这个小应用就是上面代码的升级版本,只不过上面的代码只能单向通信,其实如何实现双向通信,之前在无名管道的博客中有提到过,就是建立两根管道。同时加入select(),因为我们需要判断是标准输入(就是我们输入到屏幕,然后发送给对方的信息),还是对方发来的信息。
- 如果是对方发来的信息,我们就打印到屏幕,我们就知道了对方发来什么信息。
- 如果是标准输入发来的我们就写入管道发送给对方。
下面是小黑写的关于select() 的博客,忘记的人可以看看:
https://blog.csdn.net/weixin_46027505/article/details/104836269
6.2 写这个小应用之前有2点需要注意:
6.2.1 关闭管道会产生信号
关闭管道的一端
(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;
(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wirte返回-1.
我们可以利用信号这点,实现如果对方断开,也退出自己进程。
如果忘记信号可以阅读:
https://blog.csdn.net/weixin_46027505/article/details/104530022
6.2.2 死锁问题
所以,如果小白在代码里先打开只写端,那么小黑必须先打开只读端
6.3 具体代码实现
小黑和小白的原理是一样的。
- 下面是小白进程的代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <libgen.h>
#include <stdlib.h>
#define FIFO_FILE1 ".fifo_chat1"
#define FIFO_FILE2 ".fifo_chat2"
int g_stop = 0;
void sig_pipe(int signum)
{
if (SIGPIPE == signum)
{
printf("get pipe broken signal and let programe exit\n");
g_stop = 1;
}
}
int main(int argc, char **argv)
{
int fdr_fifo;
int fdw_fifo;
int rv;
fd_set rdset;
char buf[1024];
if (access(FIFO_FILE1, F_OK))
{
printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE1);
mkfifo(FIFO_FILE1, 0666);
}
if (access(FIFO_FILE2, F_OK))
{
printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE2);
mkfifo(FIFO_FILE2, 0666);
}
signal(SIGPIPE, sig_pipe);
printf("start open '%s' for read and it will blocked untill write endpoint opened...\n", FIFO_FILE1);
if ((fdr_fifo = open(FIFO_FILE1, O_RDONLY)) < 0 )
{
printf("Open fifo[%s] for chat read endpoint failure: %s\n", FIFO_FILE1,
strerror(errno));
return -1;
}
printf("start open '%s' for write...\n", FIFO_FILE2);
if ((fdw_fifo = open(FIFO_FILE2, O_WRONLY)) < 0)
{
printf("Open fifo[%s] for chat write endpoint failure: %s\n", FIFO_FILE2,
strerror(errno));
return -1;
}
printf("\nxiaobai have done all preparetion job and be ready to chat with xiaohei\n");
printf("start chating with xiaohei now, please input message now: \n");
while (!g_stop)
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr_fifo, &rdset);
rv = select(fdr_fifo + 1, &rdset, NULL, NULL, NULL);
if (rv <= 0)
{
printf("Select get timeout or error: %s\n", strerror(errno));
continue;
}
if (FD_ISSET(fdr_fifo, &rdset))
{
memset(buf, 0, sizeof(buf));
rv = read(fdr_fifo, buf, sizeof(buf));
if (rv < 0)
{
printf("read data from FIFO get errorr: %s\n", strerror(errno));
break;
}
else if (0 == rv)
{
printf("Another side of FIFO get closed and program will exit now\n");
break;
}
printf("xiaohei: %s", buf);
}
if (FD_ISSET(STDIN_FILENO, &rdset))
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(fdw_fifo, buf, strlen(buf));
}
}
}
- 下面是小黑的代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <libgen.h>
#include <stdlib.h>
#define FIFO_FILE1 ".fifo_chat1"
#define FIFO_FILE2 ".fifo_chat2"
int g_stop = 0;
void sig_pipe(int signum)
{
if (SIGPIPE == signum)
{
printf("get pipe broken signal and let programe exit\n");
g_stop = 1;
}
}
int main(int argc, char **argv)
{
int fdr_fifo;
int fdw_fifo;
int rv;
fd_set rdset;
char buf[1024];
if (access(FIFO_FILE1, F_OK))
{
printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE1);
mkfifo(FIFO_FILE1, 0666);
}
if (access(FIFO_FILE2, F_OK))
{
printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE2);
mkfifo(FIFO_FILE2, 0666);
}
signal(SIGPIPE, sig_pipe);
{
printf("start open '%s' for write and it will blocked untill read endpoint opened...\n",
FIFO_FILE1);
if ((fdw_fifo = open(FIFO_FILE1, O_WRONLY)) < 0)
{
printf("Open fifo[%s] for chat write endpoint failure: %s\n", FIFO_FILE1,
strerror(errno));
return -1;
}
printf("start open '%s' for read...\n", FIFO_FILE2);
if ((fdr_fifo = open(FIFO_FILE2, O_RDONLY)) < 0)
{
printf("Open fifo[%s] for chat read endpoint failure: %s\n", FIFO_FILE2,
strerror(errno));
return -1;
}
}
printf("\nxiaohei have done all preparation job and be ready to chat with xiaobai......\n");
printf("start chating with xiaobai now, please input message now: \n");
while (!g_stop)
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr_fifo, &rdset);
rv = select(fdr_fifo + 1, &rdset, NULL, NULL, NULL);
if (rv <= 0)
{
printf("Select get timeout or error: %s\n", strerror(errno));
continue;
}
if (FD_ISSET(fdr_fifo, &rdset))
{
memset(buf, 0, sizeof(buf));
rv = read(fdr_fifo, buf, sizeof(buf));
if (rv < 0)
{
printf("read data from FIFO get errorr: %s\n", strerror(errno));
break;
}
else if (0 == rv)
{
printf("Another side of FIFO get closed and program will exit now\n");
break;
}
printf("xiaobai: %s", buf);
}
if (FD_ISSET(STDIN_FILENO, &rdset))
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(fdw_fifo, buf, strlen(buf));
}
}
}
6.4 代码运行结果演示
- 下面我们先完成准备工作,编译
-
然后先运行小白的QQ,发现阻塞,正在等待对方打开读端。
-
然后我们运行小黑的QQ,就可以通信了
- 因为我们在程序中调用unlink(),所以程序退出后两个管道文件依旧存在。