一、什么是 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:
- 当客户端与服务器建立连接或断开连接时,这个函数会被调用
- 通过 TcpConnectionPtr参数可以判断连接状态(connected() 方法)
- 可以通过 peerAddress() 方法获取客户端的地址信息
消息回调函数 onMessage:
- 当服务器收到客户端发送的数据时,这个函数会被调用
- Buffer* 参数是存储接收到数据的缓冲区
- Timestamp参数表示数据到达的时间戳
- 我们使用 retrieveAllAsString() 方法从缓冲区读取所有数据
- 使用 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库中的类,主函数能更简洁,简化了其初始操作
在服务端的回调函数中实现简单的字典翻译和判断连接正常的功能
在客户端的回调函数来实现接收和判断连接的功能的功能


1万+

被折叠的 条评论
为什么被折叠?



