管道的作用
如果想让进程间进行通信,那么前提是让两个进程看见同一份数据。而管道可以实现让不同进程看见同一份数据。
什么是管道
管道本质上就是一个可读写的文件,一个进程向文件中写入数据,另一个进程从文件中读取数据,那么此时就实现了通信。因为这种数据的传输是单向的,所以才被称作管道。
管道的实现就是两个不同的进程打开同一个文件。
匿名管道
匿名管道仅用于父子进程或有血缘关系的进程(即有一个共同的祖先进程)
原理
在通信开始前父进程先以读和写两种权限分别打开同一个文件,再利用fork()函数形成父子进程,因为子进程继承了父进程的数据,所以打开的文件描述符表中也有以读和写两种权限分别打开同一个文件的文件描述符。此时父子进程分别关闭自己不需要的文件描述符,一方剩下写一方剩下读。然后父子进程间就可以进行通信了。
pipe函数
头文件:#include<unistd.h>
定义:int pipe(int fd[2])
返回值为0时函数正常,为-1时出错。传入一个大小为2的整型数组指针,函数执行成功后fd[0]代表读打开的文件描述符,fd[1]则代表写打开的文件描述符。
示例代码
#include <iostream>
#include <string>
#include <cerrno> // errno.h
#include <cstring> // string.h
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
const int size = 1024;
//获取其他信息
std::string getOtherMessage()
{
static int cnt = 0;
std::string messageid = std::to_string(cnt); // stoi -> string -> int
cnt++;
pid_t self_id = getpid();
std::string stringpid = std::to_string(self_id);
std::string message = "messageid: ";
message += messageid;
message += " my pid is : ";
message += stringpid;
return message;
}
//子进程写
void SubProcessWrite(int wfd)
{
std::string message = "father, I am your son prcess!";
while (true)
{
std::cout << "+++++++++++++++++++++++++++++++++++++++++++" << std::endl;
std::string info = message + getOtherMessage();
write(wfd, info.c_str(), info.size());
std::cerr << info << std::endl;
sleep(1);
}
std::cout << "child quit..." << std::endl;
}
//父进程读
void FatherProcessRead(int rfd)
{
char inbuffer[size];
while (true)
{
sleep(2);
std::cout << "-------------------------------------------" << std::endl;
ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0)
{
inbuffer[n] = 0;
std::cout << inbuffer << std::endl;
}
else if (n == 0)
{
std::cout << "client quit, father get return val: " << n << " father quit too!" << std::endl;
break;
}
else
{
std::cerr << "read error" << std::endl;
break;
}
}
std::cout << "father quit..." << std::endl;
}
int main()
{
int pipefd[2];
int n = pipe(pipefd);
if (n != 0)
{
std::cerr << "error:" << errno << ":" << "errstring:" << strerror(errno) << std::endl;
}
pid_t id = fork();
//判断是否是子进程
if (id == 0)
{
//关闭不需要的文件描述符
close(pipefd[0]);
// 进行写操作
SubProcessWrite(pipefd[1]);
// 写完成,正常退出
close(pipefd[1]);
exit(0);
}
std::cout << "父进程关闭不需要的fd了, 准备收消息了" << std::endl;
sleep(1);
close(pipefd[1]);
FatherProcessRead(pipefd[0]);
close(pipefd[0]);
// 父进程等待子进程
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
std::cout << "wait child process done, exit sig: " << (status & 0x7f) << std::endl;
std::cout << "wait child process done, exit code(ign): " << ((status >> 8) & 0xff) << std::endl;
}
return 0;
}
命名管道
命名管道与匿名管道不同的地方在于命名管道还可以用于没有血缘关系的进程进行通信。
原理
mkfifo函数
头文件:#include<sys/types.h> #include <sys/stat.h>
定义:int mkfifo(const char* filename,mode_t mode)
filename:要创建的文件名(指定位置要带路径)
mode:用于规定fifo的读写权限
作用:创建命名管道,成功返回0反之返回-1。
unlink函数
头文件:#include <unistd.h>
定义:int unlink(const char * pathname)
filename:要关闭的文件名(指定位置要带路径)
作用:关闭参数pathname指定的文件,即关闭指定的命名管道,成功返回0反之返回-1。
命名管道的封装
#pragma once
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096
class namePipe
{
public:
namePipe(const std::string &path, int who)
: _fifo_path(path), _id(who), _fd(DefaultFd)
{
if (_id = Creater)
{
int res = mkfifo(_fifo_path.c_str(), 0666);
if (res != 0)
{
perror("mkfifo error");
}
}
}
~namePipe()
{
if (_id = Creater)
{
int res = unlink(_fifo_path.c_str());
if (res != 0)
{
perror("unlink error");
}
}
if (_fd != DefaultFd)
close(_fd);
}
//对命名管道进行读操作
int ReadNamePipe(std::string* out)
{
char buffer[BaseSize];
int n = read(_fd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
*out = buffer;
}
return n;
}
//对命名管道进行写操作
int WriteNamePipe(const std::string &in)
{
return write(_fd,in.c_str(),in.size());
}
//以读方式打开命名管道
bool OpenForRead()
{
return OpenNamePipe(Read);
}
//以写方式打开命名管道
bool OpenForWrite()
{
return OpenNamePipe(Write);
}
private:
//打开命名管道
bool OpenNamePipe(int mode)
{
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
return false;
return true;
}
private:
const std::string _fifo_path;//打开文件的路径
int _id;//表示身份(是用户还是创建者)
int _fd;//文件描述符(标识命名管道)
};