1.命名管道
命名管道FIFO是一种通过文件路径标识的特殊文件,能够为不相关进程提供流式通信能力,任意进程只需要通过统一路径打开该文件即可实现跨进程数据交换其内核缓冲区独立于创建者生命周期存在,但本质仍是无消息边界的单向字节流通道。
1.1创建命名管道
命名管道可以从命令上创建
mkfifo filename
从程序上创建
//int mkfifo(const char* filename,mode_t mode);
int main()
{
mkfifo("h1",0644);
return 0;
}
mode_t mode是文件掩码用于指定管道权限,规则同open/chmod,实际权限=mode&~umask,能够支持标准宏或自定义八进制值
常用权限
0644所有者读写,其余只读
0600仅所有者读写
0666读写权限最终=0666&~umask
注意mode只控制新建管道的访问权限不含执行权限,
mkfifo函数成功返回0,失败返回-1同时设置errno
1.2匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开
命名管道由mkfifo创建,用open打开
FIFO命名管道与pipe匿名管道之间的唯一区别在于它们创建与打开的方式不同,一旦这些工作完成后就具有相同的语义了
且它们都是单向通道
1.3命名管道的打开方式
当前打开操作为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有进程为写而打开FIFO
O_NONBLOCK enable:立即返回成功
当前打开操作为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有进程为读而打开FIFO
O_NONBLOCK enable:立即返回失败,错误码为ENXIO
2.用命名管道实现文件拷贝
linux内核中规定,FIFO管道必须有读和写两端同时打开,才能正常通信
通过两个进程分别运行不同的程序实现使用命名管道进行文件拷贝
进程A,进行创建命名管道"tp",并以只读的模式打开在当前目录下的普通文件"abc",作为进行拷贝的源文件。
以只写模式打开刚创建的tp管道,后会被阻塞挂起,等待另一个进程B以只读模式打开同一个tp管道,随后将源文件的数据写入tp中。
cpp
#include<cstdio>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<cstdlib>
#include<cerrno>
#include<unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(int argc,char* argv[])
{
if(mkfifo("tp",0644)==-1&&errno!=EEXIST)
ERR_EXIT("mkfifo");
int infd;
infd=open("abc",O_RDONLY);
if(infd==-1)
ERR_EXIT("open");
int outfd;
outfd=open("tp",O_WRONLY);
if(outfd==-1)
ERR_EXIT("open");
char buf[1024];
ssize_t n;
while((n=read(infd,buf,1024))>0)
{
write(outfd,buf,n);
}
close(infd);
close(outfd);
return 0;
}
进程B以只写的形式打开当前目录下普通文件abc.bak,O_CREAT|O_TRUNC指的是文件若不存在则创建,若已存在则清空文件内容。
以只读的形式打开已存在的管道tp,此时写端进程A的阻塞会被解除,两端开始进行连接
随后循环不断读取tp管道内的内容写入文件abc.bak,直到管道数据读取完毕也就是收到EOF时,关闭文件描述符,调用unlink删除管道文件tp,清理资源
cpp
#include<cstdio>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<cstdlib>
#include<cerrno>
#include<unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(int argc,char* argv[])
{
int outfd;
outfd=open("abc.bak",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(outfd==-1)
ERR_EXIT("open");
int infd;
infd=open("tp",O_RDONLY);
if(infd==-1)
ERR_EXIT("open");
char buf[1024];
ssize_t n;
while((n=read(infd,buf,1024))>0)
{
write(outfd,buf,n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}
3.使用命名管道实现客户端与服务器端通信
这是一个基于命名管道FIFO单向通信服务端,其设计为单客户端模式,单次运行仅负责接收一个客户端所发的消息,并且引入了信号处理机制,通过捕捉信号SIGINT信号也就是ctrl+c,可以确保用户强行中断进程时,能够自动清理残留的管道文件tp
客户端
cpp
#include<iostream>
#include<cstring>
#include<fcntl.h>
#include<cerrno>
#include<unistd.h>
using namespace std;
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
int main()
{
int wfd=open("tp",O_WRONLY);
if(wfd==-1)
ERR_EXIT("open");
char buf[1025];
cout<<"please wait..."<<endl;
while(1)
{
fflush(stdout);
ssize_t n=read(0,buf,sizeof(buf));//read会从buf[0]开始写
if(n>0)
{
buf[n]=0;
ssize_t s=write(wfd,buf,n);
if(s<0)
ERR_EXIT("write");
}
else if(n==0)
{
break;
}
else
{
ERR_EXIT("read");
}
}
if(close(wfd)==-1)
ERR_EXIT("close");
return 0;
}
服务器端
cpp
#include<iostream>
#include<cstring>
#include<fcntl.h>
#include<cerrno>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/stat.h>
using namespace std;
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
const char* file_name="tp";
void clean_and_exit(int sig)
{
unlink(file_name);
_exit(0);
}
int main()
{
umask(0);
unlink(file_name);//删除同名文件
if(mkfifo(file_name,0644)==-1)
ERR_EXIT("mkfifo");
//注册信号处理函数,处理使用ctrl+c发送结束进行的信号
signal(SIGINT,clean_and_exit);
int rfd=open(file_name,O_RDONLY);
if(rfd<0)
ERR_EXIT("open");
char buf[1024];
cout<<"please wait..."<<endl;
while(1)
{
ssize_t n=read(rfd,buf,sizeof(buf));
if(n>0)
{
cout<<"client says# ";
cout.write(buf,n);//按精度长度输出,避免越界
}
else if(n==0)
{
cout<<"client disconnected"<<endl;
break;
}
else
{
if(errno==EINTR)
continue;
ERR_EXIT("read");
}
}
close(rfd);
unlink(file_name);
return 0;
}
