【Muduo】TcpServer类

TcpServer统领之前所有的类,是用户直接使用的类。它通过ThreadPool管理所有的loopthread,保存所有的TcpConnection,保存用户提供的各种回调函数并向TcpConnection的Channel中注册回调。它负责监听指定的端口,并接受来自客户端的连接请求,为每个连接创建一个TcpConnection对象进行管理。它的业务逻辑较先前的三大组件来说,还是比较简明的。

主要成员变量

  • EventLoop* loop_:指向事件循环的指针,这是由用户创建并传入的,是mainLoop。事件循环是Muduo网络库的核心组件之一,负责监听文件描述符上的事件(如可读、可写、错误等),并调度相应的事件处理函数。
  • InetAddress listenAddr_:表示服务器监听的地址和端口。
  • Acceptor acceptor_Acceptor对象用于监听指定的端口,并接受来自客户端的连接请求。当有新的连接请求到达时,Acceptor会调用相应的回调函数进行处理。
  • 回调函数TcpServer类允许用户注册多个回调函数,以便在特定事件发生时执行自定义逻辑。这些回调函数包括新连接到来时的回调、连接关闭时的回调等。

主要功能

  1. 监听端口TcpServer类通过Acceptor对象监听指定的端口,等待客户端的连接请求。当有新的连接请求到达时,Acceptor会触发相应的事件,通知TcpServer进行处理。
  2. 接受连接:当Acceptor接收到客户端的连接请求时上报,TcpServer会创建一个新的TcpConnection对象来表示这个连接,并分配subLoop将其与客户端进行关联。同时,TcpServer会将新创建的TcpConnection对象添加到内部的管理列表中,以便后续进行管理和操作。
  3. 管理连接TcpServer类负责管理与其关联的所有TcpConnection对象。这包括连接的建立、保持和关闭等操作。当客户端断开连接时,TcpServer会从管理列表中移除相应的TcpConnection对象,并释放相关资源。
  4. 事件处理:当与TcpServer关联的事件发生时(如新连接到来、连接关闭等),TcpServer会调用相应的回调函数进行处理。这些回调函数可以是Muduo网络库预定义的,也可以是用户自定义的。通过回调函数,用户可以编写自己的业务逻辑来处理各种事件。
  5. 线程安全:由于Muduo网络库是多线程的,因此TcpServer类需要是线程安全的。这意味着在多线程环境下,多个线程可以同时访问同一个TcpServer对象,而不会导致数据竞争或其他并发问题。Muduo网络库通过EventLoopThreadPool来管理所有EventLoopThread,使用互斥锁和信号量等同步机制来保证TcpServer类的线程安全性。

使用方式

用户通常只需要创建一个TcpServer对象,并设置相应的监听地址和端口,然后调用其start()方法来启动服务器。在服务器运行过程中,用户可以通过注册回调函数来处理各种事件。当有新的连接请求到达时,Muduo网络库会自动为每个连接创建一个TcpConnection对象,并将其与TcpServer进行关联。用户可以通过访问与TcpServer关联的TcpConnection对象来与客户端进行通信和交互。当客户端断开连接时,Muduo网络库会自动从管理列表中移除相应的TcpConnection对象,并释放相关资源。

源码

TcpServer.h

#pragma once#include "noncopyable.h"
#include "LogStream.h"
#include "EventLoop.h"
#include "EventLoopThreadPool.h"
#include "Acceptor.h"
#include "Callbacks.h"
#include "InetAddress.h"
#include "Buffer.h"
#include "Timestamp.h"
#include "TcpConnection.h"#include <functional>
#include <string>
#include <memory>
#include <unordered_map>
#include <atomic>// 对外的服务器编程使用的类
class TcpServer : noncopyable
{
public:using ThreadInitCallback = std::function<void(EventLoop *)>;enum Option{kNoReusePort,kReusePort,};TcpServer(EventLoop *loop,const InetAddress &listenAddr,const std::string &nameArg,Option option = kNoReusePort);~TcpServer();const std::string &ipPort() const { return ipPort_; }const std::string &name() const { return name_; }EventLoop *getLoop() const { return loop_; }void setThreadNum(int numThreads);//std::shared_ptr<EventLoopThreadPool> threadPool(){return threadPool_;}/// Starts the server if it's not listening.void start();void setThreadInitCallback(const ThreadInitCallback &cb){threadInitCallback_ = cb;}void setConnectionCallback(const ConnectionCallback &cb){connectionCallback_ = cb;}void setMessageCallback(const MessageCallback &cb){messageCallback_ = cb;}void setWriteCompleteCallback(const WriteCompleteCallback &cb){writeCompleteCallback_ = cb;}private:void newConnection(int sockfd, const InetAddress &peerAddr);void removeConnection(const TcpConnectionPtr &conn);void removeConnectionInLoop(const TcpConnectionPtr &conn);using ConnectionMap = std::unordered_map<std::string, TcpConnectionPtr>;EventLoop *loop_; // the acceptor loop, 用户定义的loopconst std::string ipPort_;const std::string name_;std::unique_ptr<Acceptor> acceptor_;              // 运行在mainLoop,监听新连接事件std::shared_ptr<EventLoopThreadPool> threadPool_; // one loop pre threadConnectionCallback connectionCallback_;       // 有新链接时的回调MessageCallback messageCallback_;             // 有读写消息时的回调WriteCompleteCallback writeCompleteCallback_; // 消息发送完成以后的回调ThreadInitCallback threadInitCallback_;       // loop线程初始化的回调std::atomic_int started_;int nextConnId_;ConnectionMap connections_; // 保存所有的连接
};

TcpServer.cc

#include "TcpServer.h"#include <sys/socket.h>
#include <string.h>static EventLoop *checkLoopNotNull(EventLoop *loop)
{if (loop == nullptr){LOG_FATAL << "mainLoop is null!";}return loop;
}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)), // create sokect, listenthreadPool_(new EventLoopThreadPool(loop, name_)),connectionCallback_(),messageCallback_(),nextConnId_(1),started_(0)
{// 当有新用户链接时,将执行TcpServer::newConnection回调:// 根据轮训算法选择一个subLoop并唤醒,把当前connfd封装成channel分发给subloopacceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this,std::placeholders::_1, std::placeholders::_2));
}TcpServer::~TcpServer()
{LOG_INFO << "TcpServer::~TcpServer [" << name_ << "] destructing";for (auto &item : connections_){// 强智能指针不会再指向原来的对象,放手了,出了作用域,可以自动释放资源TcpConnectionPtr conn(item.second);item.second.reset(); // 销毁连接conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));}
}void TcpServer::setThreadNum(int numThreads)
{threadPool_->setThreadNum(numThreads);
}void TcpServer::start()
{LOG_DEBUG << "TcpServer::start() started_ = " << started_ << "loop_=" << loop_;if (started_++ == 0)// 防止一个sever对象被start多次{ threadPool_->start(threadInitCallback_); // 启动底层的线程池loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); // 注册AcceptorChannel到baseLoopLOG_DEBUG << "TcpServer::start success!";} 
}// 有新客户端连接,运行在主线程mainLoop的acceptor会执行这个回调操作
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{// 轮询选择一个subloop(子线程),正在epoll上阻塞,需要queueInLoopEventLoop *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 [" << name_<< "] - new connection [" << connName<< "] from " << peerAddr.toIpPort();// 通过sockfd获取其绑定的本机的ip地址和端口信息sockaddr_in localaddr;memset(&localaddr, 0, sizeof localaddr);// ::bzero(&localaddr, sizeof localaddr);socklen_t addrlen = sizeof localaddr;if (::getsockname(sockfd, (sockaddr *)&localaddr, &addrlen) < 0){LOG_ERROR << "sockets::getLocalAddr error, errno=" << errno;}InetAddress localAddr(localaddr);// 根据连接成功的sockfd,创建TcpConnectionTcpConnectionPtr conn(new TcpConnection(ioLoop,connName,sockfd,localAddr,peerAddr));connections_[connName] = conn;// 下面的回调都是用户来设置给TcpServer=》TcpConnection=》Channel注册=》Poller=》notify Channel回调conn->setConnectionCallback(connectionCallback_);conn->setMessageCallback(messageCallback_);conn->setWriteCompleteCallback(writeCompleteCallback_);conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));// 直接调用TcpConnection::connectEstablished// 本函数运行在baseLoop,而ioloop在子线程阻塞在epoll_wait,// 此时在baseLoop中进行函数调用runInLoop、queueInLoop进而保存到ioloop的成员变量pendingFunctors_,// 进而一直在baseLoop中调用到ioloop的weakup函数,向ioloop的wakeupfd中写一个数据// 在子线程中的epoll_wait监听到写事件,就可以返回,才能够执行这里的回调函数ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_<< "] - connection " << conn->name();connections_.erase(conn->name());EventLoop *ioLoop = conn->getLoop(); // 获取conn所在的loop ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/14688.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ZeRO-3、模型并行、流水线并行适用情况

ZeRO-3 适用场景&#xff1a;参数量大但计算量相对均衡的情况。 主要特点&#xff1a; 参数分片&#xff1a;将模型参数、优化器状态和梯度在多个 GPU 上进行分片。显存优化&#xff1a;显著减少每个 GPU 上的显存占用&#xff0c;使得可以在较小的 GPU 上训练更大的模型。 …

思科模拟器--06.单臂路由升级版--多端路由互连实验--24.5.20

实验图纸如下: 第0步: 先放置六台个人电脑,一台交换机和一台2911路由器(千兆路由器(G0开头的)) 接着,用直通线将 PC0的F0,PC1的F0分别和交换机的F0/0, F0/1连接 交换机的F0/3和路由器的G0/0连接 PC2的F0,PC3的F0分别和交换机的F0/4, F0/5连接 交换机的F0/6和路由器的G0/1…

电脑连接爱快iKuai软路由之后,网卡没有正常获取到IP,无法访问爱快路由管理页?

前言 上一次咱们说到在爱快控制台上设置/辨认lan口&#xff0c;设置完成之后&#xff0c;其他的一些设置就需要在爱快iKuai软路由的管理页面上设置。 有些小伙伴会发现&#xff0c;当电脑连接上爱快软路由的lan口之后&#xff0c;电脑并没有正常获取到ip&#xff0c;导致无法访…

JavaScript表达式和运算符

表达式 表达式一般由常量、变量、运算符、子表达式构成。最简单的表达式可以是一个简单的值。常量或变量。例&#xff1a;var a10 运算符 运算符一般用符号来表示&#xff0c;也有些使用关键字表示。运算符由3中类型 1.一元运算符&#xff1a;一个运算符能够结合一个操作数&…

【Arthas】阿里的线上jvm监控诊断工具的基本使用

关于对运行中的项目做java监测的需求下&#xff0c;Arthas则是一个很好的解决方案。 我们可以用来 1.监控cpu 现成、内存、堆栈 2.排查cpu飚高 造成原因 3.接口没反应 是否死锁 4.接口慢优化 5.代码未按预期执行 是分支不对 还是没提交&#xff1f; 6.线上低级错误 能不能不重启…

STL--set和multiset集合

set和multiset会根据特定的排序准则&#xff0c;自动将元素排序。两者不同之处在于multiset 允许元素重复而 set 不允许。如下图: 使用set或multiset&#xff0c;必须先包含头文件: #include <set>上述两个类型都被定义为命名空间std内的class template: namespace std…

亚马逊自养号测评:深入解析与搭建要求

在亚马逊这电商平台上&#xff0c;商品的评价对于卖家来说至关重要。为了提升商品的曝光率、排名、权重和销量&#xff0c;卖家们纷纷采用各种推广方式&#xff0c;其中&#xff0c;亚马逊自养号测评成为了越来越多卖家选择的一种有效方式。 亚马逊自养号测评&#xff0c;顾名…

Android Retrofit 封装模版

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、加上网络访问的权限二、引入依赖三、由API生成JavaBean四、封装Retrofit五、调用 一、加上网络访问的权限 <uses-permission android:name"android.p…

分布式事务——9种解决方案的原理与分类

目录 一、概要1. 分布式事务的概念2. 分布式事务解决方案分类 二、常见的分布式事务解决方案1. 基础的 2PC&#xff08;二阶段提交&#xff09;1.1 核心思想1.2 简介1.3 主要特点1.3.1 优点1.3.2 缺点 2. 基础的 3PC&#xff08;三阶段提交&#xff09;2.1 核心思想2.2 简介2.3…

C语言/数据结构——每日一题(有效的括号)

一.前言 如果想要使用C语言来解决这道题——有效的括号&#xff1a;https://leetcode.cn/problems/valid-parentheses/description/我们必须要借用上一篇我们所讲的内容——栈的实现&#xff1a;https://blog.csdn.net/yiqingaa/article/details/138923750?spm1001.2014.3001.…

go routing 之 gorilla/mux

1. 背景 继续学习 go 2. 关于 routing 的学习 上一篇 go 用的库是&#xff1a;net/http &#xff0c;这次我们使用官方的库 github.com/gorilla/mux 来实现 routing。 3. demo示例 package mainimport ("fmt""net/http""github.com/gorilla/mux&…

react实现把pc网站快捷添加到桌面快捷方式

文章目录 1. 需求2. 实现效果3. 核心逻辑4. 完整react代码 1. 需求 这种需求其实在国外一些游戏网站和推广网站中经常会用到&#xff0c;目的是为了让客户 快捷方便的保存网站到桌面 &#xff0c;网站主动尽量避免下次找不到网站地址了&#xff0c;当然精确的客户自己也可以使…

Python 字符串中运算符号可运行

使用eval() re {\n "path": "/sms/sendMsg",\n "data": {\n "mobile": "12345678901",\n "signCode": "短信签名",\n "templateCode": "SMS_yyyy…

Oracle递归查询笔记

目录 一、创建表结构和插入数据 二、查询所有子节点 三、查询所有父节点 四、查询指定节点的根节点 五、查询指定节点的递归路径 六、递归子类 七、递归父类 一、创建表结构和插入数据 CREATE TABLE "REGION" ( "ID" VARCHAR2(36) DEFAULT SYS_GUI…

解析Oracle文件头内容

保存在Oracle数据文件头中的信息很丰富&#xff0c;通常只要查询DATAFILE_HEADER视图就可以获得数据文件头中的信息。但其在数据文件头中的具体位置&#xff0c;Oracle一直未公开过。所幸的是DBA们对数据文件头的研究孜孜不倦&#xff0c;其研究成果在网上也是随处可见。虽然这…

[前端|vue] 验证器validator使用笔记 (笔记)

文档 validator.js文档地址 规则编写示例 element-plus 使用示例 const captchaLoginRules {phoneNumber: [{ required: true, message: 手机号不能为空, trigger: blur },{validator: (_rule: any, value: string, _callback: any): boolean > {return isMobilePhone(…

vue-quill-editor 富文本编辑器使用出现的样式问题

使用富文本类型&#xff1a; vue-quill-editor 注意&#xff1a; 富文本导出 html 我们使用的时候&#xff0c; 样式凸显不出来 DOM 结构 <p><sub class"ql-size-large">测试内容</sub><sup class"ql-size-large">222222</su…

6步:用NGINX部署ASP.NET Core,轻松上云

1. 准备工作在开始部署之前&#xff0c;确保你已经完成了以下准备工作&#xff1a;- 安装.NET Core&#xff1a;确保你的Linux系统上安装了.NET Core运行时。你可以从.NET官网下载。- 安装NGINX&#xff1a;通过你的Linux发行版的包管理器安装NGINX。例如&#xff0c;在Ubuntu上…

GPT提示词技巧,使用教程,国内版官网直达,非套壳

GPT提示词技巧&#xff0c;使用教程&#xff0c;国内版官网直达&#xff0c;非套壳 主站点&#xff1a;https://chatgpt-plus.top&#xff08;江苏福建地区打不开&#xff0c;需要魔法&#xff09; 店铺地址&#xff1a;https://buy.chatgpt-plus.top/ 选择plus账号进入&…

鸿蒙开发ArkUI-X基础知识:【ArkUI代码工程及构建介绍】

代码工程及构建介绍 背景 ArkUI作为OpenHarmony的默认开发框架&#xff0c;在本项目&#xff08;ArkUI-X&#xff09;中需要做到一套代码同时支持多平台构建&#xff0c;所以会采取共仓开发的方式&#xff0c;部分仓直接指向OpenHarmony相关开源仓。 代码结构及仓库结构 代…