NebulaChat项目构建笔记

一、nlohmann::json 常见用法

1. 引入和别名

复制代码
#include <nlohmann/json.hpp>
using json = nlohmann::json;

以后就可以直接写 json 这个类型。

2. 字符串 -> JSON 对象(parse)

在服务器里最常用的就是这句:

cpp 复制代码
json req = json::parse(line);

含义:

把一段 JSON 格式的字符串 line 解析成一个 json 类型的对象。

典型场景:客户端发来的请求:

cpp 复制代码
{"cmd":"echo","msg":"hi"}

服务器:

cpp 复制代码
try {
    json req = json::parse(line);
} catch (const std::exception& e) {
    // 说明 line 不是合法 JSON,直接按"坏请求"处理
}

3. 访问字段:operator[]value()

假设有:

cpp 复制代码
json req = json::parse(R"({"cmd":"echo","msg":"hi"})");

常用几种访问方式:

3.1 下标访问:operator[]
cpp 复制代码
std::string cmd = req["cmd"];  // 如果没有 "cmd" 会抛异常

特点:

  • key 不存在、类型不匹配,可能抛异常

  • 适合你"确定一定有"的字段

3.2 安全访问:value(key, default)
cpp 复制代码
std::string cmd = req.value("cmd", "");
std::string msg = req.value("msg", "");
int age        = req.value("age", 0);      // 不存在就用 0
bool close     = req.value("close", false);

规则:

  • "cmd" 存在 → 转成你给的默认值类型(这里是 std::string

  • "cmd" 不存在 → 直接返回第二个参数(默认值)

4. 构造 JSON 响应

现在在 processLine 里大概是这样写的:

cpp 复制代码
json resp;
resp["ok"]   = true;
resp["data"] = msg;

也可以一口气用初始化列表:

cpp 复制代码
json resp = {
    {"ok", true},
    {"data", msg}
};

数组也很简单:

cpp 复制代码
json arr = json::array();
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
// 或者
json arr2 = {1, 2, 3};

5. JSON -> 字符串:dump()

cpp 复制代码
std::string out = resp.dump();       // 一行压缩形式
std::string pretty = resp.dump(4);   // 带缩进的格式化输出(4 空格缩进)

在网络里用的一般是第一种:

复制代码
return resp.dump() + "\n";

这样客户端拿到的是一行 JSON,方便你用按行协议拆包。

6. 错误处理(parse 出错)

现在的模板:

cpp 复制代码
std::string Server::processLine(const std::string& line){
    json resp;
    try {
        json req = json::parse(line);
        // 正常业务逻辑......
    } catch (const std::exception& e) {
        resp["ok"]  = false;
        resp["err"] = "bad json";  // 或 resp["err"] = e.what();
    }
    return resp.dump() + "\n";
}

只要 parse 失败(不是合法 JSON),就会进 catch,就可以统一返回一个错误 JSON。

7. 小结一句话

最常用:

  1. json req = json::parse(line); 解析请求

  2. req.value("key", default) 读字段

  3. resp["key"] = value; resp.dump() 构造响应

二、std::map::find 返回值说明

以项目常见的这种为例:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns_;

1. find 返回什么?

复制代码
auto it = conns_.find(fd);

find 返回的是一个"迭代器"(iterator),可以理解成:

  • 指向 map 里某一个元素的"指针样的东西"

map 的元素类型是:

复制代码
std::pair<const Key, T>

在例子里:

cpp 复制代码
Key = int
T   = std::unique_ptr<Connection>

*it 的类型是:std::pair<const int, std::unique_ptr<Connection>>

2. 怎么判断有没有找到?

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) {
    // 没找到
} else {
    // 找到了
}

end() 表示"序列末尾的下一个位置",也就是"找不到"的标准写法。

这是非常固定的模式:

cpp 复制代码
auto it = m.find(key);
if (it != m.end()) {
    // 用 it
}

3. 找到后怎么用?

访问 keyvalue

cpp 复制代码
int key_fd = it->first;                        // key
std::unique_ptr<Connection>& ptr = it->second; // value

Connection& conn = *ptr;                      // 引用
Connection* pconn = ptr.get();                // 原始指针

在项目中经常看到类似:

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) return;
Connection& conn = *it->second;

理解为:

  1. it 是"指向某个键值对"的迭代器

  2. it->second 是那个 unique_ptr<Connection>

  3. *it->second 才是 Connection&

4. 结合你的场景:保存一个指针出来

写过:

cpp 复制代码
std::unique_ptr<Connection>* p_holder = nullptr;
{
    std::lock_guard<std::mutex> lock(conns_mtx_);
    auto it = conns_.find(fd);
    if(it == conns_.end()) return;
    p_holder = &it->second;
}
Connection& conn = *p_holder->get();

这里 step by step:

  • conns_.find(fd) → 找到对应的迭代器 it

  • it->secondunique_ptr<Connection>

  • &it->second 就是"指向这个 unique_ptr 的指针"

  • p_holder->get() → 拿到 Connection*

  • *p_holder->get() → 变成 Connection&

这么写的目的是:

在解锁后还能继续访问 Connection 对象本身(但已经不动 map 的结构了)。

三、智能指针总结(unique_ptr / shared_ptr / weak_ptr)

1. 为什么要用智能指针

裸指针(T*)的问题:

  • 忘记 delete → 内存泄漏

  • new / delete 分散在各处,不好维护

  • 异常、提前 return 时很容易少写 delete

  • 多个地方持有同一指针时,很容易 double free

智能指针的核心思想:RAII(资源在对象生命周期结束时自动释放)。

2. std::unique_ptr:独占所有权

特点:

  • 一个对象只能有一个 unique_ptr 拥有它

  • 不能拷贝,只能移动

  • 适合"资源有唯一主人"的场景,比如一个连接 Connection 只由 Server

常用写法:

cpp 复制代码
#include <memory>

std::unique_ptr<Connection> conn = std::make_unique<Connection>();

// 访问
conn->fd = 10;      // 像指针
(*conn).fd = 10;    // 像引用

// 传递所有权(移动)
std::unique_ptr<Connection> other = std::move(conn);
// 此时 conn == nullptr,所有权给了 other

// 释放资源
conn.reset();  // 显式释放
// 或者 conn 在作用域结束自动释放

容器里用:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns;
conns.emplace(fd, std::make_unique<Connection>());

3. std::shared_ptr:共享所有权

特点:

  • 可以有多个 shared_ptr 指向同一个对象

  • 内部有引用计数,最后一个 shared_ptr 析构时才真正 delete 对象

  • use_count() 可以查看当前引用数

典型用法:

cpp 复制代码
#include <memory>

auto p1 = std::make_shared<User>("Alice");
{
    auto p2 = p1;  // 引用计数 +1
}                   // p2 析构,计数 -1
// 最后一个 shared_ptr 消失时才 delete User

缺点:

  • 有一点额外开销(引用计数管理)

  • 两个对象互相持有 shared_ptr 时会产生"循环引用",导致内存泄漏(这时需要 weak_ptr)

一般用在:

  • 同一个对象需要被多个模块共享管理

  • 生命周期比较复杂,很难用一个"唯一主人"描述清楚的时候

4. std::weak_ptr:配合 shared_ptr 打破循环引用

特点:

  • 不增加引用计数

  • 只能观察 shared_ptr 管理的对象

  • 需要用 lock() 转成 shared_ptr 才能使用

例子:

cpp 复制代码
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();

a->next = b;                 // shared_ptr
b->prev = a;                 // 如果也是 shared_ptr,会循环引用

// 解决办法:prev 用 weak_ptr
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 不增加引用计数
};

5. 结合你现在的 NebulaChat

你现在的关键结构:

cpp 复制代码
std::map<int, std::unique_ptr<Connection>> conns_;

理解为:

  • conns_ 是"连接管理器"

  • 每个 Connection 只属于 Server 一个地方

  • map 中这个元素被 erase 或者整个 conns_ 析构时,对应连接自动 delete,不用你手动管理内存

结合 map.find:

cpp 复制代码
auto it = conns_.find(fd);
if (it == conns_.end()) return;

Connection& conn = *it->second;  // 从 unique_ptr 拿出真正的对象

这就是 map + unique_ptr + 智能指针三者一起工作的典型模式。

相关推荐
d111111111d2 小时前
STM32外设学习-串口数据包笔记-(程序)
笔记·stm32·单片机·嵌入式硬件·学习
Alberta ゙2 小时前
C++初阶
开发语言·c++
---学无止境---3 小时前
Linux内核brk系统调用深度解析:堆内存管理的设计与实现
linux
网络坤子-蔡先生3 小时前
openEuler 22.03 ARM64 KVM虚拟化安装
linux·开源·负载均衡
弘毅 失败的 mian3 小时前
编译和链接
c语言·经验分享·笔记·编程入门
偶像你挑的噻3 小时前
2-Linux驱动开发-内核;内核模块;设备树;设备树插件
linux·运维·驱动开发
温宇飞4 小时前
C++ 内存与性能优化:语言特性的开销分析与替代方案
c++
张暮笛4 小时前
Linux内核LED驱动开发:实现可控制闪烁与常亮的GPIO驱动
linux·驱动开发
CheungChunChiu4 小时前
[特殊字符] 嵌入式音频接口全景图解:I2S、TDM、PDM、SPDIF、AC’97 与 PCM 的关系
linux·audio·pulseaudio