当前位置: 首页 > article >正文

C++基础:muduo库学习记录

2024/11/26-2024/11/2 :
  记录一下在自己学习muduo库时对一些概念的理解和实现。
reference:
[1]C++Muduo网络库:简介及使用
[2]Linux-C网络编程之epoll函数
[3]Linux平台下muduo网络库源码编译安装

目录

  • 一、基础概念
    • 1.1 阻塞 or 非阻塞 同步 or 异步
    • 1.2 IO模型
  • 二、muduo库的简单使用
    • 2.1muduo 网络库简述

一、基础概念

1.1 阻塞 or 非阻塞 同步 or 异步

从IO角度来看这几个概念,可以从recv这个函数入手。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

详细的参数介绍就此略过。总之,recv函数或者说几乎所有IO系统调用做的事情我们可以将其分为两个阶段,一个是数据准备,一个是进行数据读写

Created with Raphaël 2.3.0 recv开始 数据准备:查看接收缓冲区是否有数据 数据读写:将接收缓冲区的数据拷贝到指定位置 recv结束

阻塞和非阻塞在数据准备状态起作用,表现出的状态是阻塞当前线程 or 直接返回;
而同步和异步则决定着数据读写的方式,在同步中,数据的读写由执行指令的线程本身从头到尾完成,耗费的是当前线程的执行时间,而异步意味着将数据的读写交给其他线程处理,自己去执行其他命令,不增加本线程的执行时间,并与线程进行完成任务后通信方式的约定。可以类比一下DMA技术的思想。
两两组合便有4种:

  • 同步阻塞(blocking IO 模型)
    数据准备阶段查看接收缓冲区,无数据或者数据没发完则阻塞等待,数据发完则接收数据并返回,数据读写由本进程执行。
  • 同步非阻塞(non-blocking IO 模型)
    recv工作在非阻塞状态下时,不管缓冲区是否有数据、数据是否完整都会立即返回,通过返回值来判断是否拿到数据,通常需要轮询执行。数据读写由本进程执行。
  • 异步阻塞
    将事务递交给操作系统或者其他线程完成,自己也同时阻塞等待完成信号。没有存在的必要,效率过低,只有理论上存在这种模型。
  • 异步非阻塞(异步模型)
    线程将读写缓冲区的事务操作系统或者其他线程来操作,自己去执行别的任务,执行任务者使用信号或者回调函数通知进程传输完成,注意通知的方式需要提前约定。这种IO方式效率较高,需要操作系统给予支持,实现真正意义上的异步,可以将数据准备和读写操作都由另外的线程完成。但是,这种方式的编程比较复杂,出错调试也最麻烦。

1.2 IO模型

除了上面四种组合方式包括的三种IO模型,另有两种IO模型:

  • IO 复用模型
    IO复用模型中,进程在select/poll/epoll等函数处阻塞并等待返回可读套接字,之后根据返回套接字的不同自己进行数据的读写,因此这同样也是一种同步的模型,通常需要轮询执行。至于该模型的数据准备阶段是否阻塞,则取决于函数的具体参数和设置,但通常使用非阻塞方式。之后的数据读写同步阻塞。
  • 信号驱动模型
    应用进程注册信号处理程序,立即得到返回的信号状态后进程继续执行,直到得到数据处理完成的信号——这是一个异步的过程,而信号通知机制避免了轮询查询操作,提高了效率。

注意信号驱动和IO 复用只是在数据准备阶段有所区别,之后的数据读写系统调用依然是一个同步阻塞的过程。
信号驱动跟异步模型的区别是,信号驱动在数据准备阶段结束时便返回信号,让主进程自己进行数据读写,而异步模型在数据读写后返回信号。

二、muduo库的简单使用

2.1muduo 网络库简述

muduo 网络库为用户提供了两个主要的类:

  • TcpServer:用于编写服务器程序。
  • TcpClient:用于编写客户端程序。

muduo 基于 epoll 事件驱动模型,并结合线程池机制,能够高效地处理网络 I/O 操作。epoll + 线程池的优势使其能够把线程池IO的代码和业务代码区分开,让我们可以只负责编写连接和断开和用户的读写事件相关的函数(即下文setConnectionCallbacksetMessageCallback函数)。

muduo的使用则比较模板化,如上所说,我们只需要关注网络连接的处理(相当于处理epoll中的listenfd事件)以及业务处理相关代码,具体如下:

/*
 * 基于muduo网络库开发服务器程序
 * 1.组合TcpServer对象
 * 2.创建EventLoop事件循环对象的指针
 * 3.明确TcpServer构造函数需要什么参数,输出ChatServer的构造函数
 * 4.在当前服务器类的构造函数中,注册处理连接的回调函数和处理IO事件的回调函数
 * 5.设置合适的服务端线程数量,muduo库会自己分配IO线程和worker线程
 */
 
// 基于事件驱动的IO复用服务器
class ChatServer {
public:
    ChatServer(EventLoop* loop,                 // 事件循环
               const InetAddress& listenAddr,   // IP + port
               const string& nameArg)           // 服务器的名字
               :_server(loop, listenAddr, nameArg)
               ,_loop(loop) {
        // 给服务器注册用户连接的创建和断开回调
        _server.setConnectionCallback(bind(&ChatServer::onConnection, this, _1));

        // 给服务器注册用户读写事件回调
        _server.setMessageCallback(bind(&ChatServer::onMessage, this, _1, _2, _3));

        // 设置服务器端的线程数量  muduo库自动分配:1个IO线程  4个worker线程
        _server.setThreadNum(5);
    }

    // 开启事件循环
    void start() {
        _server.start();
    }
private:
    // 专门处理用户连接的创建和断开epoll listen accept
    void onConnection(const TcpConnectionPtr &conn) {
        if (conn->connected()) {
            cout << conn->peerAddress().toIpPort() << "->" <<
            conn->localAddress().toIpPort() << "state:online" << endl;
        }
        else {
            cout << conn->peerAddress().toIpPort() << "->" <<
            conn->localAddress().toIpPort() << "state:offline" << endl;
            conn->shutdown();   // close(fd);
            // _loop->quit();   // 退出epoll
        }


    }
    void onMessage(const TcpConnectionPtr &conn,    // 连接
                   Buffer *buffer,                  // 缓冲区
                   Timestamp time) {                // 接收到数据的时间信息
        string buf = buffer->retrieveAllAsString();
        cout << "recv data: " << buf << " time: " << time.toString() << endl;
        conn->send(buf);
        // 可以根据用户发送的buffer中的信息来定制不同的业务处理
    }
    TcpServer _server;  // #1
    EventLoop *_loop;   // #2 epoll
};

int main() {
    EventLoop loop; // epoll
    InetAddress addr("127.0.0.1", 6000);
    ChatServer server(&loop, addr, "ChatServer");

    server.start(); // listen 和 epoll_ctl 添加到 epoll上
    loop.loop();    // epoll_wait——以阻塞方式等待新用户连接,已连接用户的读写事件等

    return 0;
}

可以看到muduo基本上帮我们处理完了关于连接和接收数据的操作,我们只需要根据用户发送的信息进行不同的业务处理即可。


http://www.kler.cn/a/416541.html

相关文章:

  • QuantLib-python使用心得(持续更新)
  • 小迪安全第四十二天笔记 简单的mysql注入 mysql的基础知识 用户管理数据库模式 mysql 写入与读取 跨库查询
  • 【Canvas与雷达】点鼠标可暂停金边蓝屏雷达显示屏
  • 哈希表,哈希桶的实现
  • Linux下的wlan0控制
  • 深度学习——激活函数
  • 格网法计算平面点云面积(matlab版本)
  • 考试排名(一)(结构体专题)
  • 2024年11月一区SCI-Alpha evolution-附Matlab免费代码
  • javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version
  • DM-VIO(ROS)+t265配置运行记录(ubuntu18.04+ros melodic)
  • Maven - 优雅的管理多模块应用的统一版本号
  • 利用Java爬虫精准获取淘宝商品详情的探索之旅
  • Mac 环境下类Xshell 的客户端介绍
  • 周期性移动模式地铁乘客流量预测
  • git 本地同步远端分支
  • 记录一次 k8s 节点内存不足的排查过程
  • requests文件上传和表单参数
  • Golang 构建学习
  • Java爬虫:深入探索1688接口的奥秘
  • Elasticsearch实战:从搜索到数据分析的全面应用指南
  • Vue封装组件
  • 详解collections库常用的数据结构
  • hive和spark读写分区表NULL列
  • 哈希表算法题
  • Oracle系列---【关闭归档日志】