muduo网络库源码详解(2) —— 以EchoServer为例子,从TcpServer的初始化说起

muduo网络库源码详解(2) 以EchoServer为例子,从TcpServer的初始化说起,分析ConnectionCallback回调函数是如何一步步设置到Channel里的

主要参考了陈硕的书《Linux多线程服务端编程》,结合自己的逻辑摘了一些重要的的句子和函数、数据成员进行分析。

如下一个简单的EchoServer的启动流程,以它为例子讲解muduo的流程

  • 构造一个EventLoop —— 作为mainloop

  • 构造一个InetAddress —— sockaddr_in的封装

  • EchoServer 的初始化 —— (TcpServer的初始化、构造函数里 手动绑定回调函数)

  • server.start(); —— 调用内部TcpServer的start(),开启 监听 listen

  • 开启mainloop(上一篇文章已经讲过) —— pool()、fillActiveChannels、handleEvent、userCallback

    注册监听事件

    监听事件(可读事件)到来 将如下图所示执行 分发给subLoop执行 accept等后续操作

代码如下

int main()
{
  muduo::net::EventLoop loop; // 构造一个loop
  muduo::net::InetAddress listenAddr(8888); // sockaddr_in的封装,服务地址未填写默认本地
  EchoServer server(&loop, listenAddr); // TcpServer的初始化、构造函数里 手动绑定回调函数
  server.start();
  loop.loop();
}

本文从TcpServer的构造看起,看看有什么东西

文章目录

    • 总览图
    • TcpServer的构造函数
      • 1、构造Acceptor —— 用于main loop : socket (create + bind + listen)
        • Acceptor的构造函数
      • 2、构造EventLoopThreadPool 用于main loop:管理EventLoopThread
      • 3、acceptor_->setNewConnectionCallback后,有事件到来执行回调newConnectionCallback_
    • TcpServer::start()

总览图

黄色的线表名了 EchoServer注册的创建Connection回调函数是怎么一步步进入到channel的,
loop的函数未分析是因为在上篇文章已经分析过了

muduo网络库源码详解(2) —— 以EchoServer为例子,从TcpServer的初始化说起_第1张图片
图片单机放大

TcpServer的构造函数

TcpServer 有一个Acceptor、一个 EventLoopThreadPool、 一个 ConnectionMap connections_

端口复用选项详解

TcpServer::TcpServer(EventLoop *loop, // 绑定的loop,作为mainLoop
                const InetAddress &listenAddr, // 端口信息
                const std::string &nameArg, // 名字
                Option option) // 端口是否复用选项 	默认不开启  
                : loop_(CheckLoopNotNull(loop))
                , ipPort_(listenAddr.toIpPort())
                , name_(nameArg)
                , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)) // 构造acceptor,主要干的事情是socket 的
                , threadPool_(new EventLoopThreadPool(loop, name_)) // 构造线程池
                , connectionCallback_(defaultConnectionCallback) // 设置默认回调
                , messageCallback_(defaultMessageCallback) // 设置默认回调
                , nextConnId_(1)
                , started_(0)
{
    // 当有先用户连接时,会执行TcpServer::newConnection回调
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, 
        std::placeholders::_1, std::placeholders::_2));
}




using NewConnectionCallback = std::function;
void Acceptor::setNewConnectionCallback(const NewConnectionCallback &cb)
    {
        newConnectionCallback_ = cb; // 设置回调
    }

总结一下TcpServer的构造函数干的事情

  1. 绑定loop —— mainloop
  2. 填写 端口信息的字符串形式、名字,初始化一些参数
  3. 构造Acceptor —— socket + bind + listen
  4. 构造EventLoopThreadPool
  5. 设置TcpServer::newConnection回调 —— handleread函数(里调用),acceptor构造将handlread()地址赋值给了channel的read回调

针对后面3个事件,分别进行分析

1、构造Acceptor —— 用于main loop : socket (create + bind + listen)

Acceptor class,用于accept4()新TCP连接,并通过回调通知使用者。它是内部class,供TcpServer使用,生命期由后者控制。

Acceptor的数据成员包括Socket、Channel等。其中Socket是一个RAIIhandle,封装了socket文件描述符的生命期。Acceptor的socket是listening socket,即server socket。Channel用于观察此socket上的readable
事件,并回调Acceptor:: handleRead(),后者会调用accept(2)来接受新连接,并回调用户callback。

  • 主要封装了listenfd相关的操作: 创建(create)、 绑定(bind)、监听(listen) ,用于baseLoop 负责 监听

Acceptor的构造函数和Acceptor::listen()成员函数执行创建TCP服务端的传统步骤,即调用socket(2)、bind(2)、listen(2)等Sockets API

Acceptor的构造函数

Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
    : loop_(loop) // 所属的loop,注意这个loop为mainLoop
    , acceptSocket_(createNonblocking()) // socket
    , acceptChannel_(loop, acceptSocket_.fd()) // 将监听套接字封装进channel
    , listenning_(false) // 设置监听状态
{
    acceptSocket_.setReuseAddr(true); // 允许复用地址
    acceptSocket_.setReusePort(reuseport); // 允许复用端口
    acceptSocket_.bindAddress(listenAddr); // bind
    acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); // 设置监听套接字的 read回调函数
}

总结一下Acceptor的构造函数干了啥

  • 绑定loop
  • 创建非阻塞套接字 —— 监听fd,设置 fd 的一些参数
  • 将 监听fd 封装进 监听channel
  • 设置 监听channel 的 read回调函数

setsocket函数详解 —— socket对象使用的函数

Acceptor的重要数据成员

private:
    void handleRead();

    EventLoop *loop_;
    Socket acceptSocket_;
    Channel acceptChannel_;
    NewConnectionCallback newConnectionCallback_; // TcpServer初始化时就设置好的回调函数,当连接建立时就会调用
    bool listenning_;

2、构造EventLoopThreadPool 用于main loop:管理EventLoopThread

EventLoopThreadPool构造函数

  • 绑定 mainLoop
  • 填写名字 (从TcpServer的构造函数看出,与TcpServer的名字一致)
  • 初始化参数 started、numThreads_、next_
EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg)
    : baseLoop_(baseLoop)
    , name_(nameArg)
    , started_(false) // 标志是开启
    , numThreads_(0)
    , next_(0)
{}

3、acceptor_->setNewConnectionCallback后,有事件到来执行回调newConnectionCallback_

针对这个newConnectionCallback_ 的函数,主要分析的时什么时候 这个回调函数 具体什么时候开始执行

    void setNewConnectionCallback(const NewConnectionCallback &cb)
    {
        newConnectionCallback_ = cb;
    }

这个回调函数具体在handleRead()中执行

// listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
    InetAddress peerAddr;
    int connfd = acceptSocket_.accept(&peerAddr);
    if (connfd >= 0)
    {
        if (newConnectionCallback_)
        {
            newConnectionCallback_(connfd, peerAddr); // 轮询找到subLoop,唤醒,分发当前的新客户端的Channel
        }
        else
        {
            ::close(connfd);
        }
    }
    else
    {
        LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__, __FUNCTION__, __LINE__, errno);
        if (errno == EMFILE)
        {
            LOG_ERROR("%s:%s:%d sockfd reached limit! \n", __FILE__, __FUNCTION__, __LINE__);
        }
    }
}

TcpServer::start()

using ThreadInitCallback = std::function<void(EventLoop*)>;

// 开启服务器监听   loop.loop()
void TcpServer::start()
{
    if (started_++ == 0) // 防止一个TcpServer对象被start多次
    {
        // 用户设置的回调函数threadInitCallback_,没设置为空
        threadPool_->start(threadInitCallback_); // 启动threadPool
        
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); // 在当前线程开启监听
    }
}
void Acceptor::listen()
{
    listenning_ = true;
    acceptSocket_.listen(); // listen
    acceptChannel_.enableReading(); // acceptChannel_ => Poller
}

// 设置fd相应的事件状态
    void enableReading() { events_ |= kReadEvent; update(); }

// enableReading 为channel添加 感兴趣的事件events_,并将events注册进入poller

总结一下 TcpServer::star()

  • 调用或初始化用户注册的threadInitCallback
    启动threadPool
  • 为channel添加感兴趣的事件events_,并将events注册进入poller,为后面loop.loop()的epoll_wait提前注册好了事件。

你可能感兴趣的:(muduo,net库源码分析,网络,linux)