《muduo——网络库》

一、什么是 muduo 库?

muduo 是由陈硕大神开发的一款开源 C++ 网络库,它的核心设计理念是 “one loop per thread”(每个线程一个事件循环),通过事件驱动模型实现高并发处理。相比直接使用 socket API 进行网络编程,muduo 库封装了复杂的底层细节,让我们可以更专注于业务逻辑的实现。

准备工作:安装 muduo 库
在开始之前,我们需要先安装 muduo 库。最简单的方法是从 GitHub 克隆源码并编译:

# 克隆源码
git clone https://github.com/chenshuo/muduo.git

# 进入目录
cd muduo

# 创建编译目录
mkdir build && cd build

# 生成 Makefile
cmake ..

# 编译安装
make && sudo make install

安装完成后,muduo 库的头文件会被安装到 /usr/local/include/muduo 目录,库文件会被安装到 /usr/local/lib 目录。

二、muduo 核心组件简介

在开始编写代码之前,让我们先了解几个 muduo 库的核心组件:

  • EventLoop:事件循环,每个线程最多有一个 EventLoop 实例,负责监听和处理各种事件
  • TcpServer:TCP服务器核心类,用于管理连接和处理网络事件
  • TcpConnection:表示一个 TCP 连接,封装了连接的生命周期和数据读写
  • Buffer:高效的缓冲区类,处理 TCP 粘包 / 拆包问题
  • InetAddress:用于封装 IP 地址和端口号的工具类,主要作用是简化网络地址的处理

这些组件协同工作,构成了 muduo 库的核心骨架。

因为我们这里主要目的是快速上手muduo库的使用,所以只介绍了常用的类和功能,帮助你快速了解其运行思路

1 TcpServer 类

函数作用
void start();// 启动服务器
setConnectionCallback();// 设置连接建立/关闭时的回调函数
setMessageCallback();// 设置消息处理回调函数

ConnectionCallback 与 MessageCallback 类型

ConnectionCallback :
是用于处理 TCP 连接建立和关闭事件的回调函数类型。当有新的客户端连接到服务器,或者已有的连接被关闭(正常关闭或异常断开)时,与之绑定的 ConnectionCallback 回调函数就会被调用。

函数作用
ConnectionCallback : connectionCb(TcpConnectionPtr &conn)// 连接回调函数类型,参数为 TcpConnectionPtr 类型的连接对象

其中 TcpConnectionPtr 是一个智能指针,指向代表 TCP 连接的 TcpConnection 对象。通过这个指针,可以获取连接的各种属性,例如连接的对端地址(peerAddress())、判断连接是否处于连接状态(connected()) 等。

MessageCallback
是用于处理接收到客户端数据消息的回调函数类型。当服务器从客户端接收到数据时,与之绑定的 MessageCallback 回调函数会被触发,开发者可以在这个回调函数中对收到的数据进行解析、处理和响应。

函数作用
MessageCallback: onMessageCb(TcpConnectionPtr &conn, Buffer *buf) { }// 消息回调函数类型,参数为 TcpConnectionPtr 连接对象和 Buffer 缓冲区。// 针对 conn 连接所接收到 buf 中的数据的处理…

const TcpConnectionPtr& conn:同样是指向代表 TCP 连接的 TcpConnection 对象的智能指针,用于标识数据是从哪个连接发送过来的,以及可以通过它进行数据发送等操作。
Buffer* buf:指向一个 Buffer 对象,Buffer 是 muduo 库中用于高效管理和操作网络数据的缓冲区,通过它可以方便地读取、写入数据,处理 TCP 粘包和拆包等问题。
Timestamp time:表示数据到达的时间戳,开发者可以根据需求使用这个时间信息,例如进行性能分析等。

总结:通过set建立ConnectionCallback 与 MessageCallback 类型的回调函数,当我们要增加其他扩展功能是,都是在其回调函数中实现,保证了在不修改源代码的基础上新增实现功能。

2 EventLoop 类

函数作用
void loop();// 开始事件监控循环
void quit();// 停止循环
TimerId runAfter(delay, cb);// 定时任务

3 InetAddress 类

函数作用
InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);// 从 IP 字符串和端口构造(如 “127.0.0.1”, 8888)
explicit InetAddress(const struct sockaddr_in& addr) : addr_(addr) {}// 从 sockaddr 结构体构造
uint16_t port() const;// 获取端口号(主机字节序)
void setPort(uint16_t port);// 设置端口号(主机字节序)
std::string toIp() const;// 获取 IP 地址字符串(如 “127.0.0.1”)
std::string toIpPort() const;// 获取 IP:端口 字符串(如 “127.0.0.1:8888”)

5 TcpConnection 类

函数作用
void send(std::string &msg);// 发送数据
bool connected();// 判断当前的连接是否连接正常
void shutdown();// 关闭连接

6 Buffer 类

函数作用
size_t readableBytes()// 获取缓冲区可读数据大小
const char* peek()// 获取缓冲区中数据的起始地址
int32_t peekInt32() const;// 尝试从缓冲区获取4字节数据,进行网络字节序转为整形,但是数据并不从缓冲区删除
void retrieveInt32();// 数据读取位置向后偏移4字节,本质上就是删除起始位置的4字节数据
int32_t readInt32();//从缓冲区中读取一个 32 位整数(4 字节),并将其从网络字节序(大端字节序)转换为主主机字节。
string retrieveAllAsString();// 从缓冲区取出所有数据,当作string返回,并删除缓冲区中的数据
string retrieveAsString(size_t len);// 从缓冲区取出 len 长度的数据,当作string返回,并删除缓冲区中的数据

7 TcpClient 类

函数作用
void connect();// 连接服务器 —— 非阻塞接口
void disconnect();// 关闭连接
TcpConnectionPtr connection() const;// 获取客户端对应的 TcpConnection 连接
// Muduo库的客户端也是通过 EventLoop 进行 IO 事件监控 IO 处理的
void setConnectionCallback(ConnectionCallback cb);// 连接建立成功/关闭的回调处理
void setMessageCallback(MessageCallback cb);// 收到消息的回调处理

补充说明:因为 Client 的 connect 接口是一个非阻塞操作,所以有可能出现一种意外情况:connect连接还没有建立完成的情况下,调用 connection 接口获取连接,send 发送数据

8 CountDownLatch 类(做计数同步操作的类 )

CountDownLatch {
    void wait(); // 计数大于0则阻塞
    void countDown(); // 计数--,为0时唤醒 wait
};

第一个程序:简单的回声服务器

让我们通过一个简单的回声服务器示例来学习 muduo 的基本用法。这个服务器会接收客户端发送的数据,并原样返回。

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>
#include <functional>

using namespace muduo;
using namespace muduo::net;

// 连接回调:处理连接建立/关闭事件
void onConnection(const TcpConnectionPtr& conn) {
  if (conn->connected()) {
    LOG_INFO << "新连接建立:" << conn->peerAddress().toIpPort();
  } else {
    LOG_INFO << "连接关闭:" << conn->peerAddress().toIpPort();
  }
}

// 消息回调:处理客户端发送的数据(回声逻辑)
void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp time) {
  // 从缓冲区读取所有数据
  string msg(buf->retrieveAllAsString());
  LOG_INFO << "收到数据:" << msg.size() << " 字节,内容:" << msg;
  
  // 原样返回给客户端
  string s_msg = "#client: " + msg;
  conn->send(s_msg);
}

int main() {
  LOG_INFO << "服务器启动...";
  
  // 创建事件循环(主线程)
  EventLoop loop;
  
  // 监听地址:127.0.0.1:8888
  InetAddress listenAddr(8888);
  
  // 创建TcpServer,绑定事件循环和监听地址
  TcpServer server(&loop, listenAddr, "EchoServer");
  
  // 设置回调函数
  server.setConnectionCallback(onConnection);
  server.setMessageCallback(onMessage);
  
  // 设置线程数(子线程处理IO事件,0表示仅主线程)
  server.setThreadNum(4);
  
  // 启动服务器(开始监听端口)
  server.start();
  
  // 运行事件循环(阻塞,处理事件)
  loop.loop();
  
  return 0;
}

连接回调函数 onConnection

  1. 当客户端与服务器建立连接或断开连接时,这个函数会被调用
  2. 通过 TcpConnectionPtr参数可以判断连接状态(connected() 方法)
  3. 可以通过 peerAddress() 方法获取客户端的地址信息

消息回调函数 onMessage

  1. 当服务器收到客户端发送的数据时,这个函数会被调用
  2. Buffer* 参数是存储接收到数据的缓冲区
  3. Timestamp参数表示数据到达的时间戳
  4. 我们使用 retrieveAllAsString() 方法从缓冲区读取所有数据
  5. 使用 send()方法将数据原样返回给客户端

在这里插入图片描述

makefile:

CFLAG= -std=c++11 -I ../../build/release-install-cpp11/include/
LFLAG= -L../../build/release-install-cpp11/lib  -lmuduo_net -lmuduo_base -pthread
all: text
text: text.cpp
	g++  $(CFLAG) $^ -o $@ $(LFLAG)

.PHONY:clean
clean:
	rm -f server client

在这里插入图片描述

第二个程序:简单的字典服务器

1 服务端:


#include <muduo/net/EventLoop.h>       
#include <muduo/net/TcpConnection.h>   
#include <muduo/net/TcpServer.h>       
#include <muduo/net/Buffer.h>  
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <unordered_map> // 哈希表容器,用于存储单词-翻译的映射关系

// 工作流程
// 1 服务器启动后监听 8080 端口
// 2 客户端建立连接时,触发onConnection()回调并输出 "连接建立!"
// 3 客户端发送数据时,onMessage()回调被触发,读取数据并进行翻译查询
// 4 服务器将查询结果返回给客户端
// 5 客户端断开连接时,输出 "连接断开!"

class DictServer //封装了翻译服务器的核心逻辑
{
public:
    DictServer(int port) : _server(&_baseloop, 
        muduo::net::InetAddress("0.0.0.0", port),
        "EchoServer", muduo::net::TcpServer::kReusePort)
    {
        // 设置连接回调函数:当客户端连接建立或断开时调用onConnection
        _server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));

        // 设置消息回调函数:当客户端发送数据时调用onMessage
        _server.setMessageCallback(std::bind(&DictServer::onMessage, this,
        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    }
    void start()
    {
        _server.start();// 启动TCP服务器,开始监听端口并接受连接
        _baseloop.loop();// 启动事件循环,处理所有IO事件(阻塞操作)
    }

private:
    // 连接回调函数:处理客户端连接建立/断开事件
    // 参数conn:TCP连接智能指针,包含连接状态和操作方法
    void onConnection(const muduo::net::TcpConnectionPtr &conn) //处理连接事件,当客户端连接建立或断开时输出相应信息
    {
        // 判断连接是否建立成功
        if (conn->connected()) {
            std::cout << "连接建立!" << std::endl;
        }
        else {
            std::cout << "连接断开!" << std::endl;
        }
    }

    // 消息回调函数:处理客户端发送的数据并返回翻译结果
    // 参数conn:当前连接的智能指针
    // 参数buf:存储接收数据的缓冲区
    // 参数timestamp:数据接收的时间戳(未使用)
    void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){
        static std::unordered_map<std::string, std::string> dict_map = {
            {"hello",  "你好"},
            {"world",  "世界"},
            {"handsome",  "帅哥"}
        };
        std::string msg = buf->retrieveAllAsString();
        std::string res;
        auto it = dict_map.find(msg);
        if (it != dict_map.end()) {
            res = it->second;
        }else {
            res = "未知单词!";
        }
        conn->send(res);
    }

private:
    muduo::net::EventLoop _baseloop;  // 事件循环对象,处理IO事件
    muduo::net::TcpServer _server;    // TCP服务器对象,管理监听和连接
};

int main()
{
        DictServer server(8080);
        server.start();
        
    
    return 0;
}

2 客户端:

#include <muduo/net/TcpClient.h>  
#include <muduo/net/EventLoop.h>    
#include <muduo/net/EventLoopThread.h> 
#include <muduo/net/TcpConnection.h>   
#include <muduo/net/Buffer.h>        
#include <muduo/base/CountDownLatch.h> 
#include <iostream>
#include <string>

class DictClient
{
public:
    DictClient(const std::string &sip, int sport)
        : _baseloop(_loopthread.startLoop()), // 启动事件循环线程并获取事件循环指针
        _downlatch(1),                        // 初始化倒计时器,用于等待连接建立
        _client(_baseloop, muduo::net::InetAddress(sip, sport), "DictClinet")
    {
        // 设置连接回调函数:当连接建立或断开时触发
        _client.setConnectionCallback(std::bind(&DictClient::onConnection, this, std::placeholders::_1));

        // 设置消息回调函数:当接收到服务器数据时触发
        _client.setMessageCallback(std::bind(&DictClient::onMessage, this,
        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

        _client.connect();// 发起连接请求
        _downlatch.wait();// 等待连接建立完成(阻塞当前线程)
    }

    // 发送消息到服务器
    // 参数:msg-要发送的字符串
    // 返回值:发送成功返回true,失败返回false
    bool send(const std::string &msg) 
    {
        if (_conn->connected() == false) {
            std::cout << "连接已经断开,发送数据失败!" << std::endl;
            return false;
        }
        _conn->send(msg);
        return true;
    }

private:
// 连接回调函数:处理连接建立/断开事件
    // 参数:conn-TCP连接智能指针
    void onConnection(const muduo::net::TcpConnectionPtr &conn) {
        if (conn->connected()) {
            std::cout << "连接建立!" << std::endl;
            _downlatch.countDown();  // 倒计时器减1,唤醒等待的线程
            _conn = conn;
        }
        else {
            std::cout << "连接断开!" << std::endl;
            _conn.reset(); // 重置连接对象(释放资源)
        }
    }
    // 消息回调函数:处理接收到的服务器数据
    // 参数:conn-TCP连接智能指针,buf-数据缓冲区,接收时间戳
    void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp) {
        std::string res = buf->retrieveAllAsString();
        // 输出服务器发送的信息
        std::cout << res << std::endl;
        std::cout.flush();  // 确保立即刷新输出缓冲区
    }

private:
    muduo::net::TcpConnectionPtr _conn;       // TCP连接智能指针,管理当前连接
    muduo::CountDownLatch _downlatch;         // 倒计时器,用于同步连接建立过程
    muduo::net::EventLoopThread _loopthread;  // 事件循环线程,处理IO事件
    muduo::net::EventLoop *_baseloop;         // 事件循环指针,指向事件循环线程中的循环对象
    muduo::net::TcpClient _client;            // Tcp客户端对象
};

int main()
{
    DictClient client("127.0.0.1", 8080);
    while(1) {
        std::string msg;
        std::cin >> msg;
        client.send(msg);
    }
    return 0;
}

通过封装原本的muduo库中的类,主函数能更简洁,简化了其初始操作
在服务端的回调函数中实现简单的字典翻译和判断连接正常的功能
在客户端的回调函数来实现接收和判断连接的功能的功能

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值