[evpp/muduo/reactor] evpp事件驱动网络库 整体架构梳理 2

上一篇博文中,我们从用户使用的角度入手,对evpp中TCPServer、TCPClient两大类进行了梳理。接下来,我们再来分析梳理下Listener、Connector两个类的细节。当然,这让我联想起了杨宗纬的那首歌“如果你愿意一层一层一层地剥开我的心,你会发现 你会讶异…”。扯得有点远,让我们进入正题。

Listener

Listener正如其名,主要是封装和TCPServer Accept连接相关的细节。
因此,从逻辑上来说,Listener附属于TCPServer,多少有点公司前台的感觉;
而从实现的角度来看,TCPServer类对象包含了一个私有成员

std::unique_ptr listener_

理清上述这层关系,有助于我们从整体上把握evpp的框架设计。
接下来还是老规矩,让我们来看下Listener结构体中相关的数据成员。

    std::string addr_;  // 监听的地址以及端口,在对象初始化时指定;
    evpp_socket_t fd_ = -1; // 监听套接字,在listen()函数中初始化;
    std::unique_ptr chan_; // 封装套接字以及事件处理函数,方便注册到事件驱动循环中,这个类我们稍后再讲; 

    // 我们刚刚还说Listener附属于TCPServer,那么 new_conn_fn_就又是一个体现的点;
    // TCPServer()在其启动函数start()中,会调用Listener的设置函数将new_conn_fn_
    // 赋值为TCPServer::HandleNewConn();
    NewConnectionCallback new_conn_fn_; // 当有新连接建立时,被执行的回调函数;
    EventLoop* loop_; // 事件驱动循环,其实就是TCPServer自己在用的那个事件循环;
// listen中要做的无非是:创建套接字(非阻塞)、绑定地址以及切换至listen状态;
void Listener::Listen(int backlog) {
    fd_ = sock::CreateNonblockingSocket();
    int ret = ::bind(fd_, sock::sockaddr_cast(&addr), static_cast(sizeof(struct sockaddr)));
    ret = ::listen(fd_, backlog);
}
// 到目前为止,前面的一些函数还都是在做准备工作,因此接下来就是要正式开门迎客;
// 在Listener::Accept()当中,又出现了一个新的类FdChannel;
// 这个还是老规矩,我们后面再做更加细致的分析;目前,我们只需要知道
// chan这个类对象封装了套接字fd_以及事件处理函数HandleAccept;
// 因此,总结起来,Listener::Accept()这个函数主要的作用,就是
// 将套接字fd_注册到事件驱动循环loop_中,同时指定当有“可读”事件发生时
// (即当有新链接到来时)调用回调函数Listener::HandleAccept();
void Listener::Accept() {
    chan_.reset(new FdChannel(loop_, fd_, true, false));
    chan_->SetReadCallback(std::bind(&Listener::HandleAccept, this));
    loop_->RunInLoop(std::bind(&FdChannel::AttachToLoop, chan_.get()));
}

// 当有新连接请求到来时,Listener::HandleAccept()这个函数就会被调用;
// 在这个函数中,我们会 1) accept这个连接请求; 2) 设置成非阻塞模式; 3) 设置KeepAlive保活; 
// 经过上述三步操作之后,从TCP的角度来看,一个连接就算成功建立了;
// 这里需要提醒下,Listener正如其名只会负责Listen相关的细节,
// 并不关心上层用户(TCPServer)到底准备如何使用这个TCP连接;用户想咋搞,他们自己说了算;
// 相关的用户逻辑,都被封装在了new_conn_fn_这个回调注册函数中;
// 因此,Listener从设计上提供了SetNewConnectionCallback()函数,
// 相应用户逻辑由上层在初始化Listener时调用SetNewConnectionCallback()进行注册;
// 绕了这么一大圈,当然在我们这个例子中,new_conn_fn_就是TCPServer::HandleNewConn()
// Listener::HandleAccept()的最后一步,就是调用这个回调函数;
void Listener::HandleAccept() {
    nfd = ::accept(fd_, sock::sockaddr_cast(&ss), &addrlen)
    evutil_make_socket_nonblocking(nfd)
    sock::SetKeepAlive(nfd, true);

    if (new_conn_fn_) {
        new_conn_fn_(nfd, raddr, sock::sockaddr_in_cast(&ss));
    }
}

虽然TCPServer::HandleNewConn()在上一篇博客中已经分析过,但是为了论述上的连贯性以及结合上下文更好的理解,我们这里再重新分析下。

// 到了这一步,我们就已经手握一条TCP连接了;接下来,自然是希望对这个TCP链接读读写写了;
// 但是,且慢;我们好歹是基于事件驱动模型的,总不能光着膀子就上;
// 因此,接下来,我们要把TCP连接给注册到事件驱动框架中;那到底该怎么做呢?
// 首先,我们要获取一个事件循环,让它帮我们监管这个TCP链接;
// 如果没有开启线程池,那就是TCPServer本身初始化时带的那个事件循环;
// 如果开启了,则从线程池中拿一个新的事件循环出来;(需要提醒的一点是,一个事件循环对应至一个线程)
// 其次是,在evpp中TCP连接被抽象成了TCPConn()类,既然我们现在手握一个TCP连接就分配一个对象进行表示;
// 此外,我们还针对该TCP链接上发生的事件,设置相应的事件处理函数;
// 这里,我再三强调下,事件处理回调函数的注册流程:
// 数据如何读写本是业务逻辑,因此需要用户设置 -》 用户将事件处理函数注册到TCPServer中
// 但是,实际的数据交换是发生在每个TCP连接上的,因此,当有新的TCPConn出现时,本函数进一步将事件处理函数注册到每个TCPConn对象中;
// 最后,在设置好事件处理回调函数后,我们就可以正式将TCPConn注册到事件循环中了,毕竟现在相应的“对接人”都齐了;
void TCPServer::HandleNewConn(evpp_socket_t sockfd,
                              const std::string& remote_addr/*ip:port*/,
                              const struct sockaddr_in* raddr) {
    EventLoop* io_loop = GetNextLoop(); // 获取一个事件循环;如果没有开启线程池,则就是TCPServer本身初始化时带的那个事件循环;
                                        // 如果开启了,则从线程池中拿一个新的事件循环出来;(需要补充的一点是,一个事件循环对应至一个线程)

    // 在evpp中, TCPConn正是用来表示一条TCP连接;
    TCPConnPtr conn(new TCPConn(io_loop, conn_name, sockfd, 
                    listen_addr_, remote_addr, ++next_conn_id_));

    conn->SetMessageCallback(msg_fn_);
    conn->SetConnectionCallback(conn_fn_);
    conn->SetCloseCallback(std::bind(&TCPServer::RemoveConnection, this, std::placeholders::_1));

    // 在设置好相应事件的事件回调函数后,将TCPConn注册到事件循环中;
    io_loop->RunInLoop(std::bind(&TCPConn::OnAttachedToLoop, conn));

    connections_[conn->id()] = conn;
}

这样,Listener这部分的内容就算是暂告一段落。

Connector类

接下来,我们来讲讲Connector类;
在熟悉了Listener的套路之后,Connector类应该算是轻车熟路的。

持续更新中

你可能感兴趣的:(c++,linux)