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

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

首先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 不包括baseloopserver_.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();         // listenacceptChannel_.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,创建TcpConnectionTcpConnectionPtr conn(new TcpConnection(ioLoop,connName,sockfd,   // Socket ChannellocalAddr,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));// 直接调用connectEstablishedioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

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

自己的网址:www.shicoder.top 欢迎加群聊天 452380935 本文由博客一文多发平台 OpenWrite 发布!

muduo源码分析之TcpServer模块相关推荐

  1. 【muduo源码分析】TcpServer服务架构

    1.muduo整体类图 2.服务器TcpServer (1)TcpServer由用户直接使用,生命周期由用户控制,用户设置好相应的回调MessageCallback.ConnectionCallbac ...

  2. muduo源码分析之回调模块

    这次我们主要来说说muduo库中大量使用的回调机制.muduo主要使用的是利用Callback的方式来实现回调,首先我们在自己的EchoServer构造函数中有这样几行代码 EchoServer(Ev ...

  3. elasticsearch源码分析之search模块(server端)

    elasticsearch源码分析之search模块(server端) 继续接着上一篇的来说啊,当client端将search的请求发送到某一个node之后,剩下的事情就是server端来处理了,具体 ...

  4. elasticsearch源码分析之search模块(client端)

    elasticsearch源码分析之search模块(client端) 注意,我这里所说的都是通过rest api来做的搜索,所以对于接收到请求的节点,我姑且将之称之为client端,其主要的功能我们 ...

  5. FreeCAD源码分析:FreeCADGui模块

    FreeCAD源码分析:FreeCADGui模块 济南友泉软件有限公司 FreeCADGui项目实现了界面操作.模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库. Fre ...

  6. Python3.5源码分析-内建模块builtins初始化

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...

  7. dubbo源码分析系列——dubbo-cluster模块源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 模块功能介绍 该模块的使用介绍请参考dubbo官方用户手册如下章节内容. 集群容错 负载均衡 路由规则 配置规则 注册中心参考 ...

  8. 【转】Spark源码分析之-scheduler模块

    原文地址:http://jerryshao.me/architecture/2013/04/21/Spark%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B- ...

  9. LiteOS内核源码分析:位操作模块

    摘要:本文带领大家一起剖析了LiteOS位操作模块的源代码,代码非常简单,参考官方示例程序代码,实际编译运行一下,加深理解. 本文分享自华为云社区<LiteOS内核源码分析系列五 LiteOS内 ...

最新文章

  1. Swift 面向对象
  2. R语言禁止数值表示为科学计数法实战(Turn Off Scientific Notation ):全局设置或者单变量设置
  3. SaltStack状态导入(include)
  4. mysql utf8 乱码_MySql UTF8乱码问题应这样修改
  5. Github大热论文 | U-GAT-IT:基于GAN的新型无监督图像转换
  6. matlab app designer制作软件_gif制作软件app
  7. Axure RP Pro 6.0 原型设计工具(产品经理必备)
  8. 建站手册-语义网:语义网
  9. WebBrowser 控件 内存溢出 补丁 From Microsoft
  10. javafx 自定义控件_JavaFX自定义控件– Nest Thermostat第3部分
  11. PANIC: Unreachable code reached.
  12. springboot系列(三) 启动类中关键注解作用解析
  13. python 正则表达式集合-抄的
  14. shell 日期格式化输出
  15. Redis高可用群集——主从复制+哨兵模式
  16. 连接共享打印机时提示无法访问计算机,共享打印机无法连接,教您共享打印机无法连接怎么办...
  17. 组合最优化——线性规划基本定理
  18. 手机测试属于硬件测试还是软件测试6,红米手机的硬件测试的2种基本操作
  19. js函数arguments的使用
  20. php电子商务网站案例,基于PHP的B2C电子商务网站开发

热门文章

  1. LaTex做PPT、图片放置、公式输入等经验贴
  2. 戴尔Alienware x17R2原厂win11系统带F12 Support Assist OS Recovery一键恢复功能
  3. 你可以跟 ChatGPT 视频聊天了!
  4. 租用服务器选择大带宽租用具备哪些优势
  5. Android手机蓝牙互联,并传递数据。
  6. 要对象存储做什么?我有女朋友就够了!!!
  7. mysql怎么进行组内排序_MySQL 组内排序
  8. 定量预测方法总结及案例实践
  9. 苹果公司注册成立 | 历史上的今天
  10. 为什么前端都要学Vue?