当前位置: 首页 > article >正文

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

http://www.kler.cn/a/388778.html

相关文章:

  • 半导体企业如何利用 Jira 应对复杂商业变局?
  • Sigrity SPEED2000 Power Ground Noise Simulation模式如何查看PDS系统的自阻抗操作指导
  • Android OpenGL ES详解——立方体贴图
  • 华为机试HJ39 判断两个IP是否属于同一子网
  • 网页版五子棋——对战模块(服务器端开发②)
  • 实现一个BLE HID鼠标
  • 【iOS】知乎日报第三周总结
  • 金融市场中的量化分析:正大科技如何赋能投资者决策
  • 期权懂|你知道期权策略有哪些核心策略吗?
  • 保护Kubernetes免受威胁:容器安全的有效实践
  • 力扣力扣力:动态规划入门(1)
  • solo博客使用非docker方式进行https部署
  • Android 文件带进度的下载功能实现与封装
  • 2024年11月6日Github流行趋势
  • 【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】试卷(2)
  • 蓝桥杯:编程爱好者的试炼场
  • 运维的目标管理:以业务为核心,驱动运维价值最大化
  • 实时高效,全面测评快递100API的物流查询功能
  • 基于单片机洗衣机控制器的设计(论文+源码)
  • BMC运维管理:IPMI实现服务器远控制
  • 笔记整理—linux驱动开发部分(10)input子系统与相关框架
  • 计算机毕业设计 | SpringBoot社区物业管理系统(附源码)
  • 使用Golang实现开发中常用的【实例设计模式】
  • Android下的系统调用 (syscall),内联汇编syscall
  • 开源项目OpenVoice的本地部署
  • Swift中的Combine