ZLToolKit源码阅读:管道的简单封装

1059 篇文章 278 订阅

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:没有封装好,工业中不建议这么写

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值