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遍历所有key的两个命令(KEYS 和 SCAN)
Apr 12 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
浅谈Redis位图(Bitmap)及Redis二进制中的问题
Jul 15 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
Feb 12 Redis
redis数据结构之压缩列表
Mar 21 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
Grafana可视化监控系统结合SpringBoot使用
Apr 19 Redis
Redis基本数据类型Set常用操作命令
Jun 01 Redis
使用Redis实现分布式锁的方法
Jun 16 Redis
Redis主从复制操作和配置详情
Sep 23 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编程之高级技巧——利用Mysql函数
2006/10/09 PHP
PHP命名空间(Namespace)简明教程
2014/06/11 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
php实现的单一入口应用程序实例分析
2015/09/23 PHP
些很实用且必用的小脚本代码
2006/06/26 Javascript
基于jQuery的动态增删改查表格信息,可左键/右键提示(原创自Zjmainstay)
2012/07/31 Javascript
图片延迟加载的实现代码(模仿懒惰)
2013/03/29 Javascript
window.open 以post方式传递参数示例代码
2014/02/27 Javascript
JQuery获取与设置HTML元素的内容或文本的实现代码
2014/06/20 Javascript
浅析jQuery中使用$所引发的问题
2016/05/29 Javascript
Bootstrap基本插件学习笔记之Popover提示框(19)
2016/12/08 Javascript
利用JS制作万年历的方法
2017/08/16 Javascript
浅谈vue中数据双向绑定的实现原理
2017/09/14 Javascript
Angular实现可删除并计算总金额的购物车功能示例
2017/12/26 Javascript
简单的Vue异步组件实例Demo
2017/12/27 Javascript
Vue 框架之动态绑定 css 样式实例分析
2018/11/14 Javascript
JavaScript遍历查找数组中最大值与最小值的方法示例
2019/05/24 Javascript
Vue-cli3.X使用px2 rem遇到的问题及解决方法
2019/08/08 Javascript
mpvue 页面预加载新增preLoad生命周期的两种方式
2019/10/17 Javascript
jQuery实现二级导航菜单的示例
2020/09/30 jQuery
[03:58]2014DOTA2国际邀请赛 龙宝赛后解密DK获胜之道
2014/07/14 DOTA
闭包在python中的应用之translate和maketrans用法详解
2014/08/27 Python
Python中使用第三方库xlrd来写入Excel文件示例
2015/04/05 Python
用ReactJS和Python的Flask框架编写留言板的代码示例
2015/12/19 Python
python selenium 对浏览器标签页进行关闭和切换的方法
2018/05/21 Python
Python实现的逻辑回归算法示例【附测试csv文件下载】
2018/12/28 Python
在Python中构建增广矩阵的实现方法
2019/07/01 Python
面向游戏玩家和书呆子的极客订阅盒:Loot Crate
2020/11/25 全球购物
英语感恩演讲稿
2014/01/14 职场文书
写好自荐信需做到的5要点
2014/03/07 职场文书
学生手册评语
2014/05/05 职场文书
金融系毕业生自荐书
2014/07/08 职场文书
2015大学生党员自我评价范文
2015/03/03 职场文书
2019年教师入党申请书
2019/06/27 职场文书
告别网页搜索!教你用python实现一款属于自己的翻译词典软件
2021/06/03 Python
一小时学会TensorFlow2之基本操作2实例代码
2021/09/04 Python