快速上手 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;
}