muduo TcpServer模块源码分析


Posted in Redis onApril 26, 2022

这次我们开始muduo源代码的实际编写,首先我们知道muduoLT模式,Reactor模式,下图为Reactor模式的流程图[来源1]

muduo TcpServer模块源码分析

然后我们来看下muduo的整体架构[来源1]

muduo TcpServer模块源码分析

首先muduo有一个主反应堆mainReactor以及几个子反应堆subReactor,其中子反应堆的个数由用户使用setThreadNum函数设置,mainReactor中主要有一个Acceptor,当用户建立新的连接的时候,Acceptor会将connfd和对应的事件打包为一个channel然后采用轮询的算法,指定将该channel给所选择的subReactor,以后该subReactor就负责该channel的所有工作。

TcpServer类

我们按照从上到下的思路进行讲解,以下内容我们按照一个简单的EchoServer的实现思路来讲解,我们知道当我们自己实现一个Server的时候,会在构造函数中实例化一个TcpServer

EchoServer(EventLoop *loop,
           const InetAddress &addr, 
           const std::string &name)
    : server_(loop, addr, name)
        , loop_(loop)
    {
        // 注册回调函数
        server_.setConnectionCallback(
            std::bind(&EchoServer::onConnection, this, std::placeholders::_1)
        );

        server_.setMessageCallback(
            std::bind(&EchoServer::onMessage, this,
                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
        // 设置合适的loop线程数量 loopthread 不包括baseloop
        server_.setThreadNum(3);
    }

于是我们去看下TcpServer的构造函数是在干什么

TcpServer::TcpServer(EventLoop *loop,
                const InetAddress &listenAddr,
                const std::string &nameArg,
                Option option)
                : loop_(CheckLoopNotNull(loop))
                , ipPort_(listenAddr.toIpPort())
                , name_(nameArg)
                , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))
                , threadPool_(new EventLoopThreadPool(loop, name_))
                , connectionCallback_()
                , messageCallback_()
                , nextConnId_(1)
                , started_(0)
{
    // 当有新用户连接时候,会执行该回调函数
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, 
        std::placeholders::_1, std::placeholders::_2));
}

我们只需要关注acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))threadPool_(new EventLoopThreadPool(loop, name_))
首先很明确的一点,构造了一个Acceptor,我们首先要知道Acceptor主要就是连接新用户并打包为一个Channel,所以我们就应该知道Acceptor按道理应该实现socketbindlistenaccept这四个函数。

Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
    : loop_(loop), acceptSocket_(createNonblocking()) // socket
      ,
      acceptChannel_(loop, acceptSocket_.fd()), listenning_(false)
{
    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(true);
    acceptSocket_.bindAddress(listenAddr); // 绑定套接字
    // 有新用户的连接,执行一个回调(打包为channel)
    acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

其中Acceptor中有个acceptSocket_,其实就是我们平时所用的listenfd,构造函数中实现了socketbind,而其余的两个函数的使用在其余代码

// 开启服务器监听
void TcpServer::start()
{
	// 防止一个TcpServer被start多次
    if (started_++ == 0) 
    {
        threadPool_->start(threadInitCallback_); // 启动底层的loop线程池,这里会按照设定了threadnum设置pool的数量
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
    }
}

我们知道,当我们设置了threadnum之后,就会有一个mainloop,那么这个loop_就是那个mainloop,其中可以看见这个loop_就只做一个事情Acceptor::listen

void Acceptor::listen()
{
    listenning_ = true;
    acceptSocket_.listen();         // listen
    acceptChannel_.enableReading(); // acceptChannel_ => Poller
}

这里就实现了listen函数,还有最后一个函数accept,我们慢慢向下分析,从代码可以知道acceptChannel_.enableReading()之后就会使得这个listenfd所在的channel对读事件感兴趣,那什么时候会有读事件呢,就是当用户建立新连接的时候,那么我们应该想一下,那当感兴趣的事件发生之后,listenfd应该干什么呢,应该执行一个回调函数呀。注意Acceptor构造函数中有这样一行代码acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));这就是那个回调,我们去看下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);
        }
    }
    ...
}

这里是不是就实现了accept函数,至此当用户建立一个新的连接时候,Acceptor就会得到一个connfd和其对应的peerAddr返回给mainloop,这时候我们在注意到TcpServer构造函数中有这样一行代码acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this,std::placeholders::_1, std::placeholders::_2));我们给acceptor_设置了一个newConnectionCallback_,于是由上面的代码就可以知道,if (newConnectionCallback_)为真,就会执行这个回调函数,于是就会执行TcpServer::newConnection,我们去看下这个函数是在干嘛。

void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    // 轮询算法选择一个subloop来管理对应的这个新连接
    EventLoop *ioLoop = threadPool_->getNextLoop(); 
    char buf[64] = {0};
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;
    std::string connName = name_ + buf;

    LOG_INFO("TcpServer::newConnection [%s] - new connection [%s] from %s \n",
        name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());
    // 通过sockfd获取其绑定的本地ip和端口
    sockaddr_in local;
    ::bzero(&local, sizeof local);
    socklen_t addrlen = sizeof local;
    if (::getsockname(sockfd, (sockaddr*)&local, &addrlen) < 0)
    {
        LOG_ERROR("sockets::getLocalAddr");
    }
    InetAddress localAddr(local);
    // 根据连接成功的sockfd,创建TcpConnection
    TcpConnectionPtr conn(new TcpConnection(
                            ioLoop,
                            connName,
                            sockfd,   // Socket Channel
                            localAddr,
                            peerAddr));
    connections_[connName] = conn;
	// 下面的回调时用户设置给TcpServer,TcpServer又设置给TcpConnection,TcpConnetion又设置给Channel,Channel又设置给Poller,Poller通知channel调用这个回调
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    // 设置了如何关闭连接的回调
    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)
    );
    // 直接调用connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

这里就比较长了,我先说下大概他干了啥事情:首先通过轮询找到下一个subloop,然后将刚刚返回的connfd和对应的peerAddr以及localAddr构造为一个TcpConnectionsubloop,然后给这个conn设置了一系列的回调函数,比如读回调,写回调,断开回调等等。下一章我们来说下上面的代码最后几行在干嘛。

到此这篇关于muduo源码分析之TcpServer模块的文章就介绍到这了!


Tags in this post...

Redis 相关文章推荐
Redis数据结构之链表与字典的使用
May 11 Redis
为Java项目添加Redis缓存的方法
May 18 Redis
分布式架构Redis中有哪些数据结构及底层实现原理
Mar 13 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
Redis高并发缓存架构性能优化
May 15 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
Redis实现短信验证码登录的示例代码
Jun 14 Redis
关于Redis的主从复制及哨兵问题
Jun 16 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 Redis
Redis数据同步之redis shake的实现方法
Apr 21 #Redis
Grafana可视化监控系统结合SpringBoot使用
Redis官方可视化工具RedisInsight安装使用教程
Redis实现一个账号只能登录一个设备
Apr 19 #Redis
Redis如何实现验证码发送 以及限制每日发送次数
Apr 18 #Redis
Redis实战高并发之扣减库存项目
Redis中key的过期删除策略和内存淘汰机制
You might like
Thinkphp5 微信公众号token验证不成功的原因及解决方法
2017/11/12 PHP
javascript 动态添加表格行
2006/06/22 Javascript
event.keyCode键码值表 附只能输入特定的字符串代码
2009/05/15 Javascript
这些年、我收集的JQuery代码小结
2012/08/01 Javascript
javascript中的onkeyup和onkeydown区别介绍
2013/04/28 Javascript
页面装载js及性能分析方法介绍
2014/03/21 Javascript
如何用jquery控制表格奇偶行及活动行颜色
2014/04/20 Javascript
jQuery中replaceWith()方法用法实例
2014/12/25 Javascript
JavaScript静态类型检查工具FLOW简介
2015/01/06 Javascript
深入理解JavaScript系列(45):代码复用模式(避免篇)详解
2015/03/04 Javascript
Javascript变量的作用域和作用域链详解
2015/04/02 Javascript
基于JavaScript代码实现微信扫一扫下载APP
2015/12/30 Javascript
js只执行1次的函数示例
2016/07/20 Javascript
微信小程序 教程之注册程序
2016/10/17 Javascript
ng-options和ng-checked在表单中的高级运用(推荐)
2017/01/21 Javascript
JavaScript之filter_动力节点Java学院整理
2017/06/28 Javascript
vue-cli3.0如何使用CDN区分开发、生产、预发布环境
2018/11/22 Javascript
node(koa2) web应用模块介绍详解
2019/03/29 Javascript
浅谈Node 异步IO和事件循环
2019/05/05 Javascript
jQuery实现判断滚动条滚动到document底部的方法分析
2019/08/27 jQuery
[47:02]2018DOTA2亚洲邀请赛3月29日 小组赛B组 VP VS paiN
2018/03/30 DOTA
用python登录Dr.com思路以及代码分享
2014/06/25 Python
TF-IDF与余弦相似性的应用(二) 找出相似文章
2017/12/21 Python
python装饰器-限制函数调用次数的方法(10s调用一次)
2018/04/21 Python
Django集成CAS单点登录的方法示例
2019/06/10 Python
Python学习笔记之文件的读写操作实例分析
2019/08/07 Python
解决Tensorflow sess.run导致的内存溢出问题
2020/02/05 Python
为您搜罗全球潮流時尚品牌:HBX
2019/12/04 全球购物
计算机专业学生的自我评价
2013/12/15 职场文书
职业生涯规划怎么写
2013/12/29 职场文书
公务员试用期满考核材料
2014/05/22 职场文书
法人授权委托书样本
2014/09/19 职场文书
县政府班子个人对照检查材料
2014/10/05 职场文书
信息合作协议书
2014/10/09 职场文书
党员学习新党章思想汇报
2014/10/25 职场文书
HTML+JS实现在线朗读器
2022/02/15 Javascript