ZLToolKit的管道使用主要是在EventPoller中,用于事件轮询器内部事件处理。管道的封装类是PipeWrap,在linux中,直接用pipe函数实现,在windows下,作者使用了tcp socket模拟管道的实现
下面我们仅看linux实现
PipeWrap
class PipeWrap {
public:
PipeWrap();
~PipeWrap();
int write(const void *buf, int n);
int read(void *buf, int n);
int readFD() const {
return _pipe_fd[0];
}
int writeFD() const {
return _pipe_fd[1];
}
private:
void clearFD();
private:
int _pipe_fd[2] = {-1, -1};
};
- 构造函数:
- 读端是阻塞的,这样如果没有数据可读就会一直阻塞
- 写端是非阻塞的,这样如果管道满了没有位置写是会直接返回,而不是阻塞等到写成功
PipeWrap::PipeWrap() {
if (pipe(_pipe_fd) == -1) {
throw runtime_error(StrPrinter << "create posix pipe failed:" << get_uv_errmsg());
}
SockUtil::setNoBlocked(_pipe_fd[0], true); //阻塞
SockUtil::setNoBlocked(_pipe_fd[1], false); // 非阻塞
SockUtil::setCloExec(_pipe_fd[0]);
SockUtil::setCloExec(_pipe_fd[1]);
}
- 析构函数:
- 没啥好说的,就是关闭读端和写端
#define closeFD(fd) \
if (fd != -1) { \
close(fd);\
fd = -1;\
}
void PipeWrap::clearFD() {
closeFD(_pipe_fd[0]);
closeFD(_pipe_fd[1]);
}
PipeWrap::~PipeWrap() {
clearFD();
}
- 下面两个函数是查询函数,分别返回读端和写端的描述符
int readFD() const {
return _pipe_fd[0];
}
int writeFD() const {
return _pipe_fd[1];
}
- 下面两个函数分别是从管道读取n字节到buf中,写n字节buf到管道中
int PipeWrap::write(const void *buf, int n) {
int ret;
do {
ret = ::write(_pipe_fd[1], buf, n);
} while (-1 == ret && UV_EINTR == get_uv_error(true));
return ret;
}
int PipeWrap::read(void *buf, int n) {
int ret;
do {
ret = ::read(_pipe_fd[0], buf, n);
} while (-1 == ret && UV_EINTR == get_uv_error(true));
return ret;
}
get_uv_error(true),暂时无需在意,直接将它理解如果不是因为中断的错误,那么就跳出循环,表示操作失败
Pipe
Pipe类比较简单,主要是将其两个成员变量关联起来,即PipeWrap和EventPoller。
- PipeWrap:对管道的封装,直接理解为管道即可
- EventPoller:事件轮询器,可以用来监听管道上是否有数据可读/可写。本质上是一个线程,把它理解为有一个线程,不断地在程序管道上是否可以操作就可以了
class Pipe {
public:
using Ptr = std::shared_ptr<EventPoller>;
using onRead = std::function<void(int size, const char *buf)>;
Pipe(const onRead &cb = nullptr, const EventPoller::Ptr &poller = nullptr);
~Pipe();
void send(const char *send, int size = 0);
private:
std::shared_ptr<PipeWrap> _pipe;
EventPoller::Ptr _poller;
};
看下构造函数
-
它要求传递两个参数:
- const onRead &cb:回调函数,当管道可读时的回调函数
- const EventPoller::Ptr &poller:事件轮询器,用来初始化成员变量
_poller
,如果传入一个NULL,那么它会自动从EventPollerPool中获取一个poller
-
它干了些什么事情:
- 初始化_poller,可以理解为创建了一个线程
- 初始化_pipe,可以理解为创建了一个管道(感觉这里的读端应该是非阻塞的,但是为什么作者要将他设置为阻塞的呢?真不会出问题吗?)
- _poller监听_pipe的读端,如果管道上有数据可读时,就会自动调用lambda表达式[cb, pipe](int event):
- 先获取管道中有多少个直接可读: 在linux上可以通过调用ioctl(fd, FIONREAD, &cnt)来获取文件描述符fd引用的流socket中可用的未读字节数
- 然后从pipe读取字节到buf中
- 如果cb不为NULL,那么调用cb
Pipe::Pipe(const onRead &cb, const EventPoller::Ptr &poller) {
// 初始化事件轮询器
_poller = poller;
if (!_poller) {
_poller = EventPollerPool::Instance().getPoller();
}
// 初始化管道
_pipe = std::make_shared<PipeWrap>();
auto pipe = _pipe;
// 监听管道的读端
_poller->addEvent(_pipe->readFD(), EventPoller::Event_Read, [cb, pipe](int event) {
int nread = 1024;
ioctl(pipe->readFD(), FIONREAD, &nread);
char buf[nread + 1];
buf[nread] = '\0';
nread = pipe->read(buf, sizeof(buf));
if (cb) {
cb(nread, buf);
}
});
}
看下它的析构函数:
- 就是销毁资源:不再监听管道的读端了
Pipe::~Pipe() {
if (_pipe) {
auto pipe = _pipe;
_poller->delEvent(pipe->readFD(), [pipe](bool success) {});
}
}
最后它还提供了一个send函数,往管道中写数据(写端是非阻塞的,因此有可能写失败)
void Pipe::send(const char *buf, int size) {
_pipe->write(buf, size);
}
ps:没有封装好,工业中不建议这么写