【Linux】应用层协议设计实战(二):Jsoncpp序列化与完整实现

文章目录

    • 应用层协议设计实战(二):Jsoncpp序列化与完整实现
    • 一、Jsoncpp库详解
      • [1.1 为什么选择JSON](#1.1 为什么选择JSON)
      • [1.2 安装Jsoncpp](#1.2 安装Jsoncpp)
      • [1.3 Json::Value基本用法](#1.3 Json::Value基本用法)
        • [1.3.1 构造JSON对象](#1.3.1 构造JSON对象)
        • [1.3.2 序列化:对象→字符串](#1.3.2 序列化:对象→字符串)
        • [1.3.3 反序列化:字符串→对象](#1.3.3 反序列化:字符串→对象)
      • [1.4 类型检查方法](#1.4 类型检查方法)
    • 二、Request和Response序列化实现
      • [2.1 Request序列化](#2.1 Request序列化)
        • [2.1.1 char类型的处理](#2.1.1 char类型的处理)
        • [2.1.2 测试序列化](#2.1.2 测试序列化)
      • [2.2 Response序列化](#2.2 Response序列化)
        • [2.2.1 测试序列化](#2.2.1 测试序列化)
    • 三、Factory工厂模式
      • [3.1 为什么需要Factory](#3.1 为什么需要Factory)
      • [3.2 Factory类的实现](#3.2 Factory类的实现)
        • [3.2.1 为什么用shared_ptr](#3.2.1 为什么用shared_ptr)
        • [3.2.2 BuildRequest的重载](#3.2.2 BuildRequest的重载)
    • 四、TcpServer完整实现
      • [4.1 Calculator计算器类](#4.1 Calculator计算器类)
      • [4.2 Service业务逻辑](#4.2 Service业务逻辑)
        • [4.2.1 完整流程图](#4.2.1 完整流程图)
      • [4.3 多线程服务器主循环](#4.3 多线程服务器主循环)
    • 五、TcpClient完整实现
      • [5.1 客户端主函数](#5.1 客户端主函数)
      • [5.2 简化版:直接通信](#5.2 简化版:直接通信)
    • 六、完整代码结构
      • [6.1 Protocol.hpp(完整版)](#6.1 Protocol.hpp(完整版))
      • [6.2 Makefile](#6.2 Makefile)
    • 七、测试与验证
      • [7.1 编译](#7.1 编译)
      • [7.2 启动服务器](#7.2 启动服务器)
      • [7.3 启动客户端](#7.3 启动客户端)
      • [7.4 测试除零错误](#7.4 测试除零错误)
      • [7.5 抓包验证](#7.5 抓包验证)
    • 八、本篇总结
      • [8.1 核心要点](#8.1 核心要点)
      • [8.2 容易混淆的点](#8.2 容易混淆的点)

应用层协议设计实战(二):Jsoncpp序列化与完整实现

💬 开篇:上一篇完成了协议格式的设计,定义了Request和Response结构体,实现了Encode/Decode来处理报文边界和粘包问题,封装了Socket类。但Request和Response的序列化方法只是声明,还没实现。这一篇用Jsoncpp库实现序列化和反序列化,用Factory工厂模式构建对象,完整实现TcpServer和TcpClient,最后测试整个网络计算器。从库的安装、API的使用,到完整代码的实现,到测试验证,手把手带你完成一个生产级别的网络应用。

👍 点赞、收藏与分享:这篇会把所有代码都实现出来,包括Jsoncpp的详细用法、Factory模式的设计、服务器和客户端的完整逻辑。如果对你有帮助,请点赞收藏!

🚀 循序渐进:从Jsoncpp库的安装和基本用法开始,到Request/Response序列化实现,到Factory工厂模式,到TcpServer完整实现,到TcpClient完整实现,到测试验证,一步步构建完整的应用。


一、Jsoncpp库详解

1.1 为什么选择JSON

序列化方案有很多:JSON、XML、Protobuf、MessagePack等。我们选JSON的原因:

优点

  • 人类可读,易于调试(抓包能直接看懂内容)
  • 跨语言支持好(几乎所有语言都有JSON库)
  • 格式简单,学习成本低
  • 库成熟稳定(Jsoncpp是C++最常用的JSON库之一)

缺点

  • 体积较大(文本格式,有很多冗余字符)
  • 解析速度较慢(相比二进制格式如Protobuf)

对于我们的网络计算器,数据量很小,JSON的缺点不明显,但优点很突出。

1.2 安装Jsoncpp

Ubuntu

bash 复制代码
sudo apt-get install libjsoncpp-dev

CentOS

bash 复制代码
sudo yum install jsoncpp-devel

安装后,头文件在/usr/include/jsoncpp/json/,库文件是libjsoncpp.solibjsoncpp.a

编译时链接

bash 复制代码
g++ -o server TcpServerMain.cc -ljsoncpp -std=c++11

1.3 Json::Value基本用法

Json::Value是Jsoncpp的核心类,可以表示JSON的任意类型。

1.3.1 构造JSON对象
cpp 复制代码
#include <jsoncpp/json/json.h>

Json::Value root;
root["name"] = "张三";
root["age"] = 30;
root["city"] = "北京";

这构造了一个JSON对象:

json 复制代码
{
    "name": "张三",
    "age": 30,
    "city": "北京"
}
1.3.2 序列化:对象→字符串

有三种方式:

方式一:toStyledString(格式化输出)

cpp 复制代码
std::string s = root.toStyledString();
std::cout << s << std::endl;

输出(有缩进、换行):

json 复制代码
{
    "age" : 30,
    "city" : "北京",
    "name" : "张三"
}

方式二:FastWriter(紧凑输出,我们用这个)

cpp 复制代码
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;

输出(无缩进、无换行):

json 复制代码
{"age":30,"city":"北京","name":"张三"}

方式三:StreamWriter(高级定制)

cpp 复制代码
Json::StreamWriterBuilder wbuilder;
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;

我们选FastWriter,因为:

  • 紧凑,减少网络传输量
  • 代码简单,一行搞定
  • 性能好(无格式化开销)
1.3.3 反序列化:字符串→对象
cpp 复制代码
std::string json_string = "{\"name\":\"张三\",\"age\":30,\"city\":\"北京\"}";

Json::Reader reader;
Json::Value root;
bool success = reader.parse(json_string, root);

if (success) {
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;
} else {
    std::cout << "Parse error: " << reader.getFormattedErrorMessages() << std::endl;
}

关键点:

  • Json::Reader负责解析
  • reader.parse()返回bool,表示是否成功
  • root["name"].asString()提取字符串值
  • root["age"].asInt()提取整数值
  • 解析失败时,getFormattedErrorMessages()返回错误信息

1.4 类型检查方法

JSON支持多种类型,Json::Value提供了类型检查方法:

cpp 复制代码
if (root["age"].isInt()) {
    int age = root["age"].asInt();
}

if (root["name"].isString()) {
    std::string name = root["name"].asString();
}

if (root.isObject()) {
    // root是一个对象(键值对集合)
}

if (root.isArray()) {
    // root是一个数组
}

常用检查方法:

  • isNull():是否为null
  • isBool():是否为布尔值
  • isInt()isInt64()isUInt():是否为整数
  • isDouble():是否为浮点数
  • isString():是否为字符串
  • isArray():是否为数组
  • isObject():是否为对象

二、Request和Response序列化实现

2.1 Request序列化

cpp 复制代码
class Request
{
public:
    Request() : _data_x(0), _data_y(0), _oper(0) {}
    Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op) {}
    
    bool Serialize(std::string *out)
    {
        Json::Value root;
        root["datax"] = _data_x;
        root["datay"] = _data_y;
        root["oper"] = _oper;  // char会被转成int(ASCII码)
        
        Json::FastWriter writer;
        *out = writer.write(root);
        return true;
    }
    
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in, root);
        if (res) {
            _data_x = root["datax"].asInt();
            _data_y = root["datay"].asInt();
            _oper = root["oper"].asInt();  // 读取int再转成char
        }
        return res;
    }
    
    int GetX() { return _data_x; }
    int GetY() { return _data_y; }
    char GetOper() { return _oper; }
    
private:
    int _data_x;
    int _data_y;
    char _oper;
};
2.1.1 char类型的处理

注意root["oper"] = _oper这行。_oper是char类型,JSON没有char类型,会自动转成int(ASCII码)。

例如_oper='+''+'的ASCII码是43,序列化后是:

json 复制代码
{"datax":10,"datay":20,"oper":43}

反序列化时,用asInt()读取43,再赋值给char变量,自动转回'+'

为什么不用asString()

因为JSON中存的是43(数字),不是"+"(字符串)。如果用asString()会失败。

当然,也可以显式转成字符串存储:

cpp 复制代码
// 序列化时
std::string op_str(1, _oper);  // char转string
root["oper"] = op_str;

// 反序列化时
std::string op_str = root["oper"].asString();
_oper = op_str[0];

但直接用int更简单。

2.1.2 测试序列化
cpp 复制代码
Request req(10, 20, '+');
std::string out;
req.Serialize(&out);
std::cout << out << std::endl;

输出:

bash 复制代码
{"datax":10,"datay":20,"oper":43}

2.2 Response序列化

cpp 复制代码
class Response
{
public:
    Response() : _result(0), _code(0) {}
    Response(int result, int code) : _result(result), _code(code) {}
    
    bool Serialize(std::string *out)
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        
        Json::FastWriter writer;
        *out = writer.write(root);
        return true;
    }
    
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in, root);
        if (res) {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return res;
    }
    
    void SetResult(int res) { _result = res; }
    void SetCode(int code) { _code = code; }
    int GetResult() { return _result; }
    int GetCode() { return _code; }
    
private:
    int _result;
    int _code;
};

和Request完全一样的模式,只是字段不同。

2.2.1 测试序列化
cpp 复制代码
Response resp(30, 0);
std::string out;
resp.Serialize(&out);
std::cout << out << std::endl;

输出:

bash 复制代码
{"code":0,"result":30}

三、Factory工厂模式

3.1 为什么需要Factory

创建对象时,可以直接new:

cpp 复制代码
Request *req = new Request(10, 20, '+');

但这有几个问题:

  • 如果构造逻辑复杂(需要初始化很多字段),代码会很乱
  • 无法统一管理对象的创建(比如加日志、统计对象数量)
  • 不利于后续扩展(比如改成对象池复用对象)

工厂模式把对象创建的逻辑封装起来,统一管理。

3.2 Factory类的实现

cpp 复制代码
class Factory
{
public:
    std::shared_ptr<Request> BuildRequest()
    {
        std::shared_ptr<Request> req = std::make_shared<Request>();
        return req;
    }
    
    std::shared_ptr<Request> BuildRequest(int x, int y, char op)
    {
        std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
        return req;
    }
    
    std::shared_ptr<Response> BuildResponse()
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>();
        return resp;
    }
    
    std::shared_ptr<Response> BuildResponse(int result, int code)
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
        return resp;
    }
};
3.2.1 为什么用shared_ptr

返回std::shared_ptr而不是裸指针的原因:

  • 自动管理内存,不需要手动delete
  • 避免内存泄漏
  • 多个地方可以持有同一个对象,引用计数归零时自动释放

使用示例:

cpp 复制代码
Factory factory;
auto req = factory.BuildRequest(10, 20, '+');
// 使用req...
// 离开作用域后,req自动释放,不需要delete
3.2.2 BuildRequest的重载

提供两个版本:

  • 无参版本:创建默认对象(字段都是0)
  • 有参版本:创建带初值的对象

服务器端通常用无参版本,接收到数据后反序列化填充:

cpp 复制代码
auto req = factory.BuildRequest();  // 创建空对象
req->Deserialize(message);          // 反序列化填充数据

客户端通常用有参版本,直接构造请求:

cpp 复制代码
auto req = factory.BuildRequest(10, 20, '+');  // 直接构造

四、TcpServer完整实现

4.1 Calculator计算器类

先实现业务逻辑------计算器:

cpp 复制代码
class Calculator
{
public:
    std::shared_ptr<Response> Calculate(std::shared_ptr<Request> req)
    {
        std::shared_ptr<Response> resp = _factory.BuildResponse();
        
        int x = req->GetX();
        int y = req->GetY();
        char op = req->GetOper();
        
        switch (op) {
            case '+':
                resp->SetResult(x + y);
                resp->SetCode(0);
                break;
            case '-':
                resp->SetResult(x - y);
                resp->SetCode(0);
                break;
            case '*':
                resp->SetResult(x * y);
                resp->SetCode(0);
                break;
            case '/':
                if (y == 0) {
                    resp->SetCode(1);  // 除零错误
                } else {
                    resp->SetResult(x / y);
                    resp->SetCode(0);
                }
                break;
            case '%':
                if (y == 0) {
                    resp->SetCode(1);  // 除零错误
                } else {
                    resp->SetResult(x % y);
                    resp->SetCode(0);
                }
                break;
            default:
                resp->SetCode(2);  // 非法运算符
                break;
        }
        
        return resp;
    }
    
private:
    Factory _factory;
};

状态码定义:

  • 0:成功
  • 1:除零错误
  • 2:非法运算符

4.2 Service业务逻辑

cpp 复制代码
void Service(Socket *sock)
{
    std::string inbuffer;  // 接收缓冲区
    
    while (true) {
        // 1. 读取数据,追加到缓冲区
        bool res = sock->Recv(&inbuffer, 1024);
        if (!res) break;  // 连接断开或出错
        
        // 2. 循环解包,处理所有完整报文
        std::string message;
        while (Protocol::Decode(inbuffer, &message)) {
            // 3. 反序列化Request
            auto req = _factory.BuildRequest();
            req->Deserialize(message);
            
            // 4. 业务处理
            auto resp = _cal.Calculate(req);
            
            // 5. 序列化Response
            std::string send_string;
            resp->Serialize(&send_string);
            
            // 6. 编码(加长度前缀)
            send_string = Protocol::Encode(send_string);
            
            // 7. 发送
            sock->Send(send_string);
        }
    }
    
    sock->CloseSocket();
}
4.2.1 完整流程图
bash 复制代码
客户端发送:{"datax":10,"datay":20,"oper":43}
↓
协议编码:36\r\n{"datax":10,"datay":20,"oper":43}\r\n
↓
网络传输
↓
服务器接收:sock->Recv追加到inbuffer
↓
协议解码:Decode提取message = {"datax":10,"datay":20,"oper":43}
↓
反序列化:req->Deserialize得到Request对象(x=10, y=20, op='+')
↓
业务处理:_cal.Calculate计算result=30, code=0
↓
构造Response:Response对象(result=30, code=0)
↓
序列化:resp->Serialize得到{"code":0,"result":30}
↓
协议编码:Encode得到23\r\n{"code":0,"result":30}\r\n
↓
发送:sock->Send
↓
网络传输
↓
客户端接收

4.3 多线程服务器主循环

cpp 复制代码
void Start()
{
    while (true) {
        std::string clientip;
        uint16_t clientport;
        
        Socket *sock = _listensock->AcceptConnection(&clientip, &clientport);
        if (sock == nullptr) continue;
        
        std::cout << "Accept new connection from " << clientip 
                  << ":" << clientport << std::endl;
        
        // 创建线程处理连接
        std::thread t(&TcpServer::Service, this, sock);
        t.detach();
    }
}

每个连接创建一个线程处理,线程detach后自动回收。


五、TcpClient完整实现

5.1 客户端主函数

cpp 复制代码
int main(int argc, char *argv[])
{
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    
    // 1. 创建socket并连接
    Socket *sock = new TcpSocket();
    if (!sock->BuildConnectSocketMethod(serverip, serverport)) {
        std::cerr << "Connect failed" << std::endl;
        return 2;
    }
    
    std::cout << "Connect success" << std::endl;
    
    // 2. 通信循环
    Factory factory;
    std::string inbuffer;
    
    while (true) {
        // 构造请求(测试用,写死几组数据)
        static int x = 10, y = 20;
        static char ops[] = {'+', '-', '*', '/', '%'};
        static int idx = 0;
        
        auto req = factory.BuildRequest(x, y, ops[idx % 5]);
        idx++;
        
        // 序列化+编码
        std::string send_string;
        req->Serialize(&send_string);
        send_string = Protocol::Encode(send_string);
        
        // 发送
        sock->Send(send_string);
        
        // 接收响应
        sock->Recv(&inbuffer, 1024);
        
        // 解码+反序列化
        std::string message;
        if (Protocol::Decode(inbuffer, &message)) {
            auto resp = factory.BuildResponse();
            resp->Deserialize(message);
            
            std::cout << x << " " << ops[(idx-1) % 5] << " " << y 
                      << " = " << resp->GetResult() 
                      << " (code: " << resp->GetCode() << ")" << std::endl;
        }
        
        sleep(1);  // 每秒发送一个请求
    }
    
    sock->CloseSocket();
    delete sock;
    return 0;
}

5.2 简化版:直接通信

为了测试方便,客户端写死了几组测试数据,自动循环发送。也可以改成交互式:

cpp 复制代码
while (true) {
    int x, y;
    char op;
    std::cout << "Enter expression (x op y): ";
    std::cin >> x >> op >> y;
    
    auto req = factory.BuildRequest(x, y, op);
    
    // 发送请求
    // ...
    
    // 接收响应
    // ...
    
    std::cout << "Result: " << resp->GetResult() << std::endl;
}

六、完整代码结构

6.1 Protocol.hpp(完整版)

cpp 复制代码
#pragma once
#include <iostream>
#include <memory>
#include <jsoncpp/json/json.h>

namespace Protocol
{
    const std::string LineBreakSep = "\r\n";
    
    std::string Encode(const std::string &message)
    {
        std::string len = std::to_string(message.size());
        std::string package = len + LineBreakSep + message + LineBreakSep;
        return package;
    }
    
    bool Decode(std::string &package, std::string *message)
    {
        auto pos = package.find(LineBreakSep);
        if (pos == std::string::npos)
            return false;
        
        std::string lens = package.substr(0, pos);
        int messagelen = std::stoi(lens);
        int total = lens.size() + messagelen + 2 * LineBreakSep.size();
        
        if (package.size() < total)
            return false;
        
        *message = package.substr(pos + LineBreakSep.size(), messagelen);
        package.erase(0, total);
        return true;
    }
    
    class Request
    {
    public:
        Request() : _data_x(0), _data_y(0), _oper(0) {}
        Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op) {}
        
        bool Serialize(std::string *out)
        {
            Json::Value root;
            root["datax"] = _data_x;
            root["datay"] = _data_y;
            root["oper"] = _oper;
            
            Json::FastWriter writer;
            *out = writer.write(root);
            return true;
        }
        
        bool Deserialize(std::string &in)
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (res) {
                _data_x = root["datax"].asInt();
                _data_y = root["datay"].asInt();
                _oper = root["oper"].asInt();
            }
            return res;
        }
        
        int GetX() { return _data_x; }
        int GetY() { return _data_y; }
        char GetOper() { return _oper; }
        
    private:
        int _data_x;
        int _data_y;
        char _oper;
    };
    
    class Response
    {
    public:
        Response() : _result(0), _code(0) {}
        Response(int result, int code) : _result(result), _code(code) {}
        
        bool Serialize(std::string *out)
        {
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            
            Json::FastWriter writer;
            *out = writer.write(root);
            return true;
        }
        
        bool Deserialize(std::string &in)
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (res) {
                _result = root["result"].asInt();
                _code = root["code"].asInt();
            }
            return res;
        }
        
        void SetResult(int res) { _result = res; }
        void SetCode(int code) { _code = code; }
        int GetResult() { return _result; }
        int GetCode() { return _code; }
        
    private:
        int _result;
        int _code;
    };
    
    class Factory
    {
    public:
        std::shared_ptr<Request> BuildRequest()
        {
            return std::make_shared<Request>();
        }
        
        std::shared_ptr<Request> BuildRequest(int x, int y, char op)
        {
            return std::make_shared<Request>(x, y, op);
        }
        
        std::shared_ptr<Response> BuildResponse()
        {
            return std::make_shared<Response>();
        }
        
        std::shared_ptr<Response> BuildResponse(int result, int code)
        {
            return std::make_shared<Response>(result, code);
        }
    };
}

6.2 Makefile

makefile 复制代码
.PHONY: all
all: tcp_server tcp_client

tcp_server: TcpServerMain.cc
	g++ -o $@ $^ -ljsoncpp -std=c++11 -lpthread

tcp_client: TcpClientMain.cc
	g++ -o $@ $^ -ljsoncpp -std=c++11

.PHONY: clean
clean:
	rm -f tcp_server tcp_client

注意:

  • -ljsoncpp:链接Jsoncpp库
  • -std=c++11:启用C++11(shared_ptr、thread需要)
  • -lpthread:链接pthread库(thread需要)

七、测试与验证

7.1 编译

bash 复制代码
make

生成tcp_servertcp_client两个可执行文件。

7.2 启动服务器

bash 复制代码
./tcp_server 8888

输出:

bash 复制代码
[2025-02-07 10:30:00] [INFO] Server start on port 8888

7.3 启动客户端

bash 复制代码
./tcp_client 127.0.0.1 8888

输出:

bash 复制代码
Connect success
10 + 20 = 30 (code: 0)
10 - 20 = -10 (code: 0)
10 * 20 = 200 (code: 0)
10 / 20 = 0 (code: 0)
10 % 20 = 10 (code: 0)
10 + 20 = 30 (code: 0)
...

服务器端输出:

bash 复制代码
Accept new connection from 127.0.0.1:54321
Recv request: x=10, y=20, op=+
Send response: result=30, code=0
Recv request: x=10, y=20, op=-
Send response: result=-10, code=0
...

7.4 测试除零错误

修改客户端代码,发送除零请求:

cpp 复制代码
auto req = factory.BuildRequest(10, 0, '/');

客户端输出:

bash 复制代码
10 / 0 = 0 (code: 1)

状态码为1,表示除零错误。

7.5 抓包验证

用tcpdump抓包:

bash 复制代码
sudo tcpdump -i lo -A port 8888

看到的数据:

bash 复制代码
36\r\n{"datax":10,"datay":20,"oper":43}\r\n

可以直接看懂协议内容,这就是JSON的优势。


八、本篇总结

8.1 核心要点

Jsoncpp使用

  • Json::Value构造JSON对象
  • Json::FastWriter序列化(紧凑格式)
  • Json::Reader反序列化
  • char类型存储为int(ASCII码)
  • asInt()、asString()提取值

Factory工厂模式

  • 封装对象创建逻辑
  • 返回shared_ptr自动管理内存
  • 提供无参和有参两个版本

服务器实现

  • Service函数:Recv→Decode→Deserialize→Calculate→Serialize→Encode→Send
  • 循环Decode处理粘包
  • 多线程处理并发连接

客户端实现

  • BuildConnectSocketMethod连接服务器
  • 构造Request→序列化→编码→发送
  • 接收→解码→反序列化→提取结果

完整流程

bash 复制代码
客户端:Request对象 → Serialize → JSON字符串 → Encode → 带长度前缀 → Send
网络传输
服务器:Recv → 拼接缓冲区 → Decode → JSON字符串 → Deserialize → Request对象
服务器:Calculate → Response对象 → Serialize → Encode → Send
网络传输
客户端:Recv → Decode → Deserialize → Response对象

8.2 容易混淆的点

  1. 为什么char要用asInt():JSON没有char类型,char会转成ASCII码(int),反序列化时用asInt()读取。

  2. 为什么要循环Decode:一次Recv可能读到多个完整报文(粘包),必须循环解析所有报文。

  3. Recv为什么要拼接:一次Recv可能只读到半个报文,需要多次Recv拼接成完整数据再Decode。

  4. Factory为什么返回shared_ptr:自动管理内存,避免内存泄漏,多处持有同一对象时引用计数管理生命周期。

  5. 为什么用FastWriter而不是toStyledString:FastWriter紧凑无格式化,减少网络传输量,提高效率。

  6. 状态码为什么放在Response里:让客户端能区分计算成功、除零错误、非法运算符等不同情况。


💬 总结:应用层协议设计实战系列两篇到此结束!从协议格式设计,到TCP全双工原理,到Encode/Decode实现,到Jsoncpp序列化,到Factory工厂模式,到完整的网络计算器实现,完整地走了一遍应用层协议设计的全流程。掌握了这些,你就能设计自己的网络协议,实现任意复杂的网络应用。协议设计是网络编程的核心技能,后续如果做HTTP服务器、RPC框架、游戏服务器,都要用到这些知识。
👍 点赞、收藏与分享:如果这个系列帮你理解了应用层协议设计,请点赞收藏!后续可能会讲HTTP协议解析、Protobuf使用、RPC框架设计等更高级的内容。感谢阅读!

相关推荐
Wpa.wk13 小时前
接口自动化 - 多环境统一文件配置 +多响应统一转换处理
运维·服务器·测试工具·自动化·接口自动化·统一配置
我在人间贩卖青春13 小时前
C++之继承与派生类的关系
c++·向上造型·向下造型
pitch_dark13 小时前
渗透测试系统基础篇——kali系统
网络·安全·web安全
独行soc14 小时前
2026年渗透测试面试题总结-20(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
是枚小菜鸡儿吖14 小时前
从 0 到 1 生成自定义算子:CANN + AIGC 的自动化工作流
运维·自动化·aigc
EmbedLinX14 小时前
嵌入式之协议解析
linux·网络·c++·笔记·学习
考琪14 小时前
Nginx打印变量到log方法
java·运维·nginx
vortex514 小时前
解密UUOC:Shell编程中“无用的cat使用”详解
linux·shell编程
凉、介14 小时前
VMware 三种网络模式(桥接 / NAT / Host-Only)原理与实验解析
c语言·网络·笔记·操作系统·嵌入式·vmware