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

工作流程:

  1. 初始化 CountDownLatchEventLoopThread

    • _downlatch 初始化为 1 ,表示主线程需要等待一次 countDown() —— 计数器减为 0

    • _loopthread 用于创建一个单独的线程,运行 EventLoop

  2. 设置 连接 / 消息 事件回调函数,连接到服务器,阻塞主线程

  3. 连接成功回调 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;
}
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值