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安装启动及常见数据类型
Apr 14 Redis
redis 查看所有的key方式
May 07 Redis
Redis延迟队列和分布式延迟队列的简答实现
May 13 Redis
为Java项目添加Redis缓存的方法
May 18 Redis
详解Redis集群搭建的三种方式
May 31 Redis
浅谈redis整数集为什么不能降级
Jul 25 Redis
关于redisson缓存序列化几枚大坑说明
Aug 04 Redis
Redis如何实现分布式锁
Aug 23 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
redis复制有可能碰到的问题汇总
Apr 03 Redis
Redis高并发缓存架构性能优化
May 15 Redis
Redis 限流器
May 15 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
php使用百度ping服务代码实例
2014/06/19 PHP
基于yaf框架和uploadify插件,做的一个导入excel文件,查看并保存数据的功能
2017/01/24 PHP
PHP基于IMAP收取邮件的方法示例
2017/08/07 PHP
Aster vs Newbee BO5 第二场2.19
2021/03/10 DOTA
jquery $.ajax()取xml数据的小问题解决方法
2010/11/20 Javascript
jquery中的mouseleave和mouseout的区别 模仿下拉框效果
2012/02/07 Javascript
uploadify在Firefox下丢失session问题的解决方法
2013/08/07 Javascript
网页广告中JS代码的信息监听示例
2014/04/02 Javascript
简单谈谈Vue 模板各类数据绑定
2016/09/25 Javascript
jQuery post数据至ashx实例详解
2016/11/18 Javascript
详解能在多种前端框架下使用的表格控件
2017/01/11 Javascript
javascript实现Emrips反质数枚举的示例代码
2017/12/06 Javascript
微信web端后退强制刷新功能的实现代码
2018/03/04 Javascript
webpack公共组件引用路径简化小技巧
2018/06/15 Javascript
MVVM 双向绑定的实现代码
2018/06/21 Javascript
layer实现登录弹框,登录成功后关闭弹框并调用父窗口的例子
2019/09/11 Javascript
uni-app自定义导航栏按钮|uniapp仿微信顶部导航条功能
2019/11/12 Javascript
Python实现的端口扫描功能示例
2018/04/08 Python
Python 实现引用其他.py文件中的类和类的方法
2018/04/29 Python
使用python爬取B站千万级数据
2018/06/08 Python
python3调用百度翻译API实现实时翻译
2018/08/16 Python
python日志logging模块使用方法分析
2019/05/23 Python
浅析python 中大括号中括号小括号的区分
2019/07/29 Python
python实现截取屏幕保存文件,删除N天前截图的例子
2019/08/27 Python
python 线性回归分析模型检验标准--拟合优度详解
2020/02/24 Python
python matplotlib.pyplot.plot()参数用法
2020/04/14 Python
俄罗斯外国汽车和国产汽车配件网上商店:Движком
2020/04/19 全球购物
Linux开机引导的步骤是什么
2014/02/26 面试题
高级人员简历的自我评价分享
2013/11/03 职场文书
电子信息工程专业推荐信
2014/02/14 职场文书
食堂采购员岗位职责
2014/03/17 职场文书
一分钟演讲稿
2014/04/30 职场文书
2014年应急工作总结
2014/12/11 职场文书
干部考核工作总结2015
2015/07/24 职场文书
基于CSS3画一个iPhone
2021/04/21 HTML / CSS
mybatis 解决从列名到属性名的自动映射失败问题
2021/06/30 Java/Android