快速上手 muduo
以词典服务与客户端为例的实战教程。
1. 词典服务端设计
1.1 服务端架构设计
TcpServer
是 Muduo 库中用于创建 TCP 服务器的核心类,它封装了服务端的监听、连接管理、数据收发等功能;
EventLoop
是 Muduo 库中最重要的类之一,它负责管理和调度所有 I/O 事件。
简单来说,TcpServer
类负责发起各种任务,这些任务通常被封装成回调函数;EventLoop
负责处理各种被触发的任务。
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
class DictServer
{
private:
muduo::net::EventLoop _baseloop;
muduo::net:TcpServer _server;
};
1.2 构造 DictServer
muduo::net::TcpServer 的构造函数:
#include <muduo/net/TcpServer.h> TcpServer(EventLoop* loop, const InetAddress& listenAddr, const string& nameArg, Option option = kNoReusePort); // EventLoop 事件循环监控 // InetAddress 监听套接字 // Option 设置端口复用
public:
DictServer(uint16_t port)
:_server(&_baseloop, InetAddress("0.0.0.0", port), "DictServer", kReusePort)
{}
1.3 设置连接事件回调函数
连接事件回调函数
用于处理客户端和服务器之间的连接建立和断开事件;
通过设置这些回调函数,可以在连接状态发生变化时执行响应的操作。
#include <muduo/net/TcpConnection.h>
class DictServer
{
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if (conn->connected()) {
std::cout << "连接成功" << std::endl;
}
else {
std::cout << "连接断开" << std::endl;
}
}
public:
DictServer(uint16_t port)
:_server(&_baseloop, InetAddress("0.0.0.0", port), "DictServer", kReusePort)
{
// 设置连接事件回调函数
_server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));
}
};
1.4 设置消息事件回调函数
Buffer
是 Muduo 库中用于处理网络数据传输的一个重要类;它提供了一个高效的缓冲区管理机制,用于存储和操作从网络接收或准备发送的数据。
retrieveAllAsString()
—— 提取缓存区内的所有数据,作为一个字符串。
对于服务器而言,消息事件回调函数
用于处理客户端发送的信息;
通过设置这些回调函数,可以在接收到客户端消息后执行相应的操作。
#include <muduo/net/Buffer.h>
#include <string>
#include <unordered_map>
std::unordered_map<std::string, std::string> dict{
{"string", "字符串"},
{"iterator", "迭代器"},
{"callback", "回调"}
}
class DictServer
{
private:
void onMessage(const muduo::net::TcpConnectionPtr& conn, Buffer* buf)
{
// messageCallback 通常在客户端已经成功连接到服务器,并且有数据到达时才被调用;
// 因此大多数情况下,在消息事件回调函数处理接收到的数据时,客户端连接应该已经建立了。
// if (!conn->connected()) {} // 为了增加代码的健壮性,可以将这部分内容实现
std::string str = buf->retrieveAllAsString();
std::string res;
if (dict.find(str) != dict.end())
res = dict[str];
else
ret = "not found";
conn->send(res);
}
public:
DictServer(uint16_t port)
:_server(&_baseloop, InetAddress("0.0.0.0", port), "DictServer", kReusePort)
{
// 设置连接事件回调函数
_server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));
// 设置消息事件回调函数
_server.setMessageCallback(std::bind(&DictServer::onMessage, this,
std::placeholders::_1,
std::placeholders::_2));
}
};
1.5 服务启动
class DictServer {
public:
void Start() {
_server.start(); // 开始监听连接
_baseloop.loop(); // 开始事件循环监控
}
};
在 loop
方法中,_baseloop
会不断监听并执行各种回调函数:
- 当有新的连接请求,
_baseloop
会执行连接事件回调函数; - 当有新的数据到达,
_baseloop
会执行消息事件回调函数。
2. 词典客户端设计
依据上述知识,先搭建起一个客户端框架。
#include <iostream> #include <string> #include <muduo/net/TcpClient.h> #include <muduo/net/EventLoop.h> #include <muduo/net/TcpConnection.h> #include <muduo/net/Buffer.h> class DictClient { private: void onConnection(const muduo::net::TcpConnectionPtr& conn) { if (conn->connected()) { std::cout << "连接成功" << std::endl; _conn = conn; } else { std::cout << "连接断开" << std::endl; _conn.reset(); } } void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf) { std::string res = buf->retrieveAllAsString(); std::cout << res << std::endl; } public: DictClient(std::string serverip, uint16_t serverport) :_client(&_baseloop, muduo::net::InetAddress(serverip, serverport), "DictClient") { _client.setConnectionCallback(std::bind(&DictClient::onConnection, this, std::placeholders::_1)); _client.setMessageCallback(std::bind(&DictClient::onMessage, this, std::placeholders::_1, std::placeholders::_2))); _client.connect(); // 连接服务器 _baseloop.loop(); // 开始事件循环监控 } bool Send(std::string msg) { if (!_conn->connected()) { std::cout << "连接断开" << std::endl; return false; } _client.send(msg); return true; } private: muduo::net::TcpConnectionPtr _conn; muduo::net::EventLoop _baseloop; muduo::net::TcpClient _client; };
这段代码中有一个问题:构造函数中 _baseloop.loop()
开始执行后,主线程就进入了死循环,这会导致后面的一系列操作都无法被正常执行。因此,需要一个新的线程,单独负责事件循环监控。
2.1 使用 EventLoopThread 管理独立的事件循环
class DictClient
{
public:
DictClient(std::string serverip, uint16_t serverport)
: _baseloop(_loopthread.startLoop()) // 开始事件循环监控
, _client(_baseloop, muduo::net::InetAddress(serverip, serverport), "DictClient")
{
// ...
_client.connect();
_loopthread.startLoop(); // 开启事件循环监控
}
private:
muduo::net::EventLoopThread _loopthread; // 新增
muduo::net::EventLoop *_baseloop;
muduo::net::TcpConnectionPtr _conn;
muduo::net::TcpClient _client;
};
2.2 引入 CountDownLatch 类
CountDownLatch
是一个同步辅助类,用于确保某个操作在多线程中按预期顺序进行。
class DictClient
{
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn) {
if (conn->connected()) {
std::cout << "连接成功" << std::endl;
_downlatch.countDown(); // 连接成功 -- 为 0,将主线程唤醒
_conn = conn;
}
else {
std::cout << "连接断开" << std::endl;
_conn.reset();
}
}
public:
DictClient(std::string serverip, uint16_t serverport)
: _downlatch(1) // 指定初始值为 1
, _baseloop(_loopthread.start())
, _client(_baseloop, muduo::net::InetAddress(serverip, serverport), "DictClient")
{
_client.setConnectionCallback(std::bind(&DictClient::onConnection, this,
std::placeholders::_1));
// ...
_client.connect();
_downlatch.wait(); // 将主线程阻塞,等待连接成功
}
private:
muduo::CountDownLatch _downlatch;
muduo::net::EventLoopThread _loopthread; // 新增
muduo::net::EventLoop *_baseloop;
muduo::net::TcpConnectionPtr _conn;
muduo::net::TcpClient _client;
};
工作流程:
-
初始化
CountDownLatch
和EventLoopThread
-
_downlatch
初始化为 1 ,表示主线程需要等待一次countDown()
—— 计数器减为 0 -
_loopthread
用于创建一个单独的线程,运行EventLoop
-
-
设置
连接
/消息
事件回调函数,连接到服务器,阻塞主线程 -
连接成功回调
onConnection()
,_downlatch.countDown()
将CountDownLatch
的计数器减为 0 ,唤醒阻塞的主线程
2.3 启动客户端
int main() {
DictClient client("127.0.0.1", 8080);
while (1) {
std::string str;
std::cin >> str;
client.Send(str);
}
return 0;
}