施磊老师rpc(三)

文章目录

mprpc框架项目动态库编译

框架生成动态库

src/CMakeLists.txt

cmake 复制代码
aux_source_directory(. SRC_LIST)
add_library(mprpc SHARED ${SRC_LIST})  # 生成动态库

example/callee/CMakeLists.txt

cmake 复制代码
set(SRC_LIST userservice.cc ../user.pb.cc)

add_executable(provider ${SRC_LIST})

target_link_libraries(provider mprpc protobuf)  # 链接动态库

正常编译即正确

框架初始化函数-文件读取

MprpcApplication::Init()

1. 为什么要传入 argc, argv

  • 为了支持从命令行读取配置文件路径(如 ./provider -i config.conf
  • 这样可以让多个部署节点通过不同的配置文件指定自己的网络参数、ZooKeeper地址等

2. 读取参数逻辑

  • 使用 getopt(需引入 <unistd.h>
  • 支持参数格式:-i config.conf
  • 如果参数错误或缺失,调用 ShowArgsHelp() 提示用户正确格式,并 exit(EXIT_FAILURE)

3. 配置文件设计

格式为 key=value 的文本文件,包含 4 项内容,例如:

复制代码
rpcserverip=127.0.0.1
rpcserverport=8000
zookeeperip=127.0.0.1
zookeeperport=2181

init部分实现

getopt函数 自行查看 man 3

c++ 复制代码
RETURN VALUE
       If an option was successfully found, then getopt() returns the option character.  If all command-line  options
       have  been  parsed, then getopt() returns -1.  If getopt() encounters an option character that was not in opt‐
       string, then '?' is returned.  If getopt() encounters an option with a missing argument, then the return value
       depends on the first character in optstring: if it is ':', then ':' is returned; otherwise '?' is returned.
c++ 复制代码
void MprpcApplication::Init(int argc, char **argv)
{
    if (argc < 3)
    {
        // std::cout << "error: argc < 3" << std::endl;
        // exit(1);
        ShowArgsHelp();
        exit(EXIT_FAILURE); // EXIT_FAILURE 是一个宏,表示程序异常退出
    }

    std::string config_file; // 配置文件
    // 解析命令行参数
    int c = 0;
    while ((c = getopt(argc, argv, "i:")) != -1) // getopt函数解析命令行参数
    {
        switch (c)
        {
        case 'i':
            config_file = optarg; // optarg是一个全局变量,存储当前选项的参数
            break;
        case '?':  // '?'表示没有找到对应的选项
        std::cout << "invalid option: " << (char)c<< std::endl;
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        case ':':  // ':'表示选项缺少参数
            std::cout << "need config_file " << std::endl;
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        default:
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        }
    } 

    // 读取配置文件----单独写 .h和.cc文件----解耦-且 代码 也不是很少

}

编译测试一下,

mprpc配置文件加载(一)

配置文件加载类

成员变量

  • 使用 std::unordered_map<std::string, std::string> 存储配置项的键值对。

主要方法

复制代码
  load_config_file(const char* config_file)
  • 打开配置文件,逐行读取内容。
  • 处理注释行(以 # 开头)、空行、以及前后多余的空格。
  • 解析合法的键值对(通过 = 分隔)。
  • 将键值对存入 unordered_map
复制代码
  load(const std::string& key)
  • 根据键查询配置项的值。
  • 如果键不存在,返回空字符串。

src/include/mprpcconfig.h

c++ 复制代码
#pragma once

#include <unordered_map>
#include <string>


// 框架读取配置文件的类
class MprpcConfig
{
    public:
    // 负责解析加载配置文件
    void LoadConfigFile(const char* config_file);

    // 查询配置项
    std::string Load(const std::string& key);

    private:
    std::unordered_map<std::string, std::string> m_configMap; // 存储配置文件的键值对
};

配置文件

bin/test.conf

c++ 复制代码
# rpc节点的ip地址
rpcserverip=127.0.0.1
# rpc节点的端口
rpcserverport=8000
# zk节点的ip地址
zookeeperip=127.0.0.1
# zk节点的端口
zookeeperport=5000

实现配置文件加载类

src/include/mprpcapplication.h

c++ 复制代码
#include "mprpcconfig.h"

....
 static MprpcConfig m_config; // 配置文件对象

类内的静态成员函数不能直接访问普通的成员变量。

静态成员函数不依赖于类的具体实例,而是属于类本身,因此它不能访问实例成员变量,因为实例成员变量是与具体对象实例相关联的。

多了解字符串类型, 面试问的很多, 要熟悉其各种方法,多运用

见知识补充

注意substr第二个参数的自动截断

src/mprpcconfig.cc

c++ 复制代码
#include "mprpcconfig.h"
#include <iostream>

// 负责解析加载配置文件
void MprpcConfig::LoadConfigFile(const char* config_file)  
{
    FILE* pf = fopen(config_file, "r");
    if(pf == nullptr)
    {
        std::cout << "error: config file is not exist" << std::endl;
        exit(EXIT_FAILURE);
    }

    while(!feof(pf)) // feof函数判断文件是否到达末尾
    {
        char buffer[512] = {0}; // 定义一个字符数组, 用于存储一行数据
        fgets(buffer, sizeof(buffer), pf); // 读取一行数据

        // 去掉多余空格
        std::string src_buf(buffer); // 将字符数组转换为字符串
        int idx = src_buf.find_first_not_of(" \t"); // 查找第一个不是空格或制表符的位置
        if(idx != std::string::npos)
        {
            src_buf = src_buf.substr(idx, src_buf.size() - idx); // 截取字符串
        }
        // 去掉注释
        if(src_buf[0] == '#' || src_buf.empty()) // 如果是注释或空行
        {
            continue; // 跳过 这一行
        }

        // 解析配置项
        idx = src_buf.find('='); // 查找第一个等号的位置
        if(idx!= std::string::npos)
        {
            std::string key = src_buf.substr(0, idx); // 截取键
            std::string value = src_buf.substr(idx + 1, src_buf.size() - idx - 1); // 截取值
            // 可以考虑conf书写不规范, 去掉多余空格
            
            // 存储配置项
            m_configMap.insert({key, value}); // 将键值对插入到map中
        }

    }

}

// 查询配置项
std::string MprpcConfig::Load(const std::string& key)
{
    // return m_configMap[key]; // 错误的, 不要用中括号, 不存在 会自动插入一个空值

    auto it = m_configMap.find(key); // 查找键
    if(it == m_configMap.end()) // 如果没有找到
    {
        std::cout << "error: key is not exist" << std::endl;
        return "";
    }
    return it->second; // 返回值
}

补充测试框架类init

c++ 复制代码
MprpcConfig MprpcApplication::m_config;  // 静态成员变量, 需要在类外初始化



void MprpcApplication::Init(int argc, char **argv)
{
    if (argc < 3)
    {
        // std::cout << "error: argc < 3" << std::endl;
        // exit(1);
        ShowArgsHelp();
        exit(EXIT_FAILURE); // EXIT_FAILURE 是一个宏,表示程序异常退出
    }

    std::string config_file; // 配置文件
    // 解析命令行参数
    int c = 0;
    while ((c = getopt(argc, argv, "i:")) != -1) // getopt函数解析命令行参数
    {
        switch (c)
        {
        case 'i':
            config_file = optarg; // optarg是一个全局变量,存储当前选项的参数
            break;
        case '?':  // '?'表示没有找到对应的选项
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        case ':':  // ':'表示选项缺少参数
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        default:
            ShowArgsHelp();
            exit(EXIT_FAILURE);
        }

        m_config.LoadConfigFile(config_file.c_str()); // 加载配置文件, config_file是一个std::string类型的变量, 文件名字
        
        std::cout<<"rpcserverip:"<<m_config.Load("rpcserverip")<<std::endl;
        std::cout<<"rpcserverport:"<<m_config.Load("rpcserverport")<<std::endl;
        std::cout<<"zookeeperip:"<<m_config.Load("zookeeperip")<<std::endl;
        std::cout<<"zookeeperport:"<<m_config.Load("zookeeperport")<<std::endl;
       
    } 

    // 读取配置文件----单独写 .h和.cc文件----解耦-且 代码 也不是很少
    // rpcserver_ip   rpcserver_port  zookeeper_ip zookeeper_port 

}

问题1

直接进行cmake 编译, 会报错!!

c++ 复制代码
[build] /usr/bin/ld: ../../src/libmprpc.so: undefined reference to `MprpcApplication::m_config'
[build] collect2: error: ld returned 1 exit status
[build] gmake[2]: *** [example/callee/CMakeFiles/provider.dir/build.make:114: ../bin/provider] Error 1
[build] gmake[1]: *** [CMakeFiles/Makefile2:157: example/callee/CMakeFiles/provider.dir/all] Error 2
[build] gmake: *** [Makefile:91: all] Error 2

问题描述:新增源文件后,没重新生成 Makefile,编译器"看不到"新文件

场景如下:

  • CMake 使用了 file(GLOB ...)aux_source_directory(...) 收集源文件;
  • 然后你手动在代码目录中添加了新的 .cpp 文件;
  • 但是你没有清除 CMake 缓存或重新运行 cmake 命令
  • 结果:Makefile 没更新,新文件不会被编译,也不在目标构建中。

问题2:

!WARNING

MprpcConfig MprpcApplication::m_config; // 静态成员变量, 需要在类外初始化

mprpc配置文件加载(二)

cmake添加

-g 是什么?

  • -g 是 GCC/Clang 的编译器选项,用于生成调试信息(供 GDB 等调试器使用)。
  • 编译出来的程序体积更大,但可以逐行调试、查看变量值等。

Debug 模式在 CMake 中

复制代码
cmake


复制编辑
set(CMAKE_BUILD_TYPE "Debug")

这条语句告诉 CMake 使用 Debug 配置,其效果通常是:

  • 自动添加 -g
  • 开启 -O0(不优化,便于调试)
  • 设置调试宏(如 _DEBUG
c++ 复制代码
set(CMAKE_BUILD_TYPE "Debug")  # 设置 Debug 模式并开启调试信息

gdb调试exe的某个源文件

c++ 复制代码
gdb ./provider
c++ 复制代码
break mprpcconfig.cc:<行数>

优化读取

为了 适应 更多不规范的 conf 文件

封装一下 去除前后空格---> 不仅可以一行前后去空格, 还可以取出 = 前后 再去空格

c++ 复制代码
void MprpcConfig::Trim(std::string &src_buf);
c++ 复制代码
// 去除前后空格
void MprpcConfig::Trim(std::string &src_buf)
{
    int idx = src_buf.find_first_not_of(" "); // 查找第一个不是空格的位置
    if (idx != std::string::npos)
    {
        src_buf = src_buf.substr(idx, src_buf.size() - idx); // 截取字符串
    }
    idx = src_buf.find_last_not_of(" "); // 查找最后一个不是空格的位置
    if (idx != std::string::npos)
    {
        src_buf = src_buf.substr(0, idx + 1); // 截取字符串
    }
}

src/mprpcconfig.cc

c++ 复制代码
// 负责解析加载配置文件
void MprpcConfig::LoadConfigFile(const char *config_file)
{
    FILE *pf = fopen(config_file, "r");
    if (pf == nullptr)
    {
        std::cout << "error: config file is not exist" << std::endl;
        exit(EXIT_FAILURE);
    }

    while (!feof(pf)) // feof函数判断文件是否到达末尾
    {
        char buffer[512] = {0};            // 定义一个字符数组, 用于存储一行数据
        fgets(buffer, sizeof(buffer), pf); // 读取一行数据

        // 去掉多余空格
        std::string read_buf(buffer); // 将字符数组转换为字符串
        Trim(read_buf);              // 去掉前后空格

        // 去掉注释
        if (read_buf[0] == '#' || read_buf.empty()) // 如果是注释或空行
        {
            continue; // 跳过 这一行
        }

        // 解析配置项
        int idx = read_buf.find('='); // 查找第一个等号的位置
        if (idx != std::string::npos)
        {
            std::string key = read_buf.substr(0, idx);                              // 截取键
            Trim(key); // 去掉前后空格

            // 先去\n
            int endidx = read_buf.find_last_not_of("\r\n", read_buf.size()-1); // 查找最后一个不是回车或换行的位置
            std::string value;
            if (endidx != std::string::npos)
            {
                value = read_buf.substr(idx+1, endidx-idx); // 截取字符串
            }
            Trim(value); // 再去掉前后空格

            // 下面这段不对, \n本身就是最后一个, 要是 \n之前紧挨着空格呢
            /*
            // 还有换行
            idx = value.find_last_not_of("\r\n"); // 查找最后一个不是回车或换行的位置
            if (idx != std::string::npos)
            {
                value = value.substr(0, idx); // 截取值
            }
            */

            // 存储配置项
            m_configMap.insert({key, value}); // 将键值对插入到map中
        }
    }
}

测试

自行测试

可能还不是最好的 处理 所有情况, 但是 要给用户 一定容错!!

错误

出现错误, 不要着急解决, 先去定位!!!

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言