【Linux网络编程】应用层自定义协议和序列化

前言:欢迎 各位光临本博客,这里IFMAXUE带你直接手撕,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

IF'Maxue个人主页
🔥 个人专栏 :
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

    • 一、开篇:五层协议与应用层核心痛点
    • 二、核心解决方案:序列化与反序列化
      • [1. 先否定两种不推荐的方案](#1. 先否定两种不推荐的方案)
      • [2. 序列化与反序列化的通俗理解](#2. 序列化与反序列化的通俗理解)
    • 三、底层原理:Socket读写接口的本质
      • [1. `write/send`的本质](#1. write/send的本质)
      • [2. `read`的本质](#2. read的本质)
      • [3. TCP通信的终极本质](#3. TCP通信的终极本质)
    • 四、实战封装:模板方法模式封装Socket(TCP/UDP)
      • [1. 父类Socket封装(Socket.cpp)](#1. 父类Socket封装(Socket.cpp))
      • [2. 辅助类inetAddr:封装网络地址](#2. 辅助类inetAddr:封装网络地址)
      • [3. TCP子类:重写差异化方法](#3. TCP子类:重写差异化方法)
      • [4. TCPServer封装:实现TCP长服务](#4. TCPServer封装:实现TCP长服务)
      • [5. UDP子类:重写差异化方法](#5. UDP子类:重写差异化方法)
      • [6. 主函数入口:启动TCP服务端](#6. 主函数入口:启动TCP服务端)
    • 五、核心实战:应用层协议定制(网络计算器)
      • [1. 协议的核心职责](#1. 协议的核心职责)
      • [2. 定义结构化数据模型](#2. 定义结构化数据模型)
      • [3. 协议层封装:衔接Socket与业务逻辑](#3. 协议层封装:衔接Socket与业务逻辑)
      • [4. 整合协议层与服务端:处理计算器业务](#4. 整合协议层与服务端:处理计算器业务)
      • [5. 整体架构:三层解耦清晰高效](#5. 整体架构:三层解耦清晰高效)
    • 六、优化方案:使用jsoncpp实现序列化与反序列化
      • [1. 序列化方案对比与选择](#1. 序列化方案对比与选择)
      • [2. 环境检查:确认jsoncpp是否安装](#2. 环境检查:确认jsoncpp是否安装)
      • [3. jsoncpp核心使用:序列化(结构化数据→JSON字符串)](#3. jsoncpp核心使用:序列化(结构化数据→JSON字符串))
      • [4. jsoncpp核心使用:反序列化(JSON字符串→结构化数据)](#4. jsoncpp核心使用:反序列化(JSON字符串→结构化数据))
      • [5. FastWriter与StyledWriter的区别](#5. FastWriter与StyledWriter的区别)
      • [6. 匿名表达式(Lambda)的使用](#6. 匿名表达式(Lambda)的使用)

一、开篇:五层协议与应用层核心痛点

我们在之前的学习中编写的所有Socket相关代码,都处于五层网络协议的应用层,这是我们开发者接触最多、能够自主定制逻辑的层级。

在实际开发中,我们很快会遇到一个核心问题:Socket提供的API接口,在读写网络数据时,默认只支持「纯字符串」格式的收发。但我们的业务逻辑中,往往需要传输结构化数据(比如计算器的两个操作数+一个运算符),这种情况下该如何处理呢?

二、核心解决方案:序列化与反序列化

要解决结构化数据的网络传输问题,核心就是序列化与反序列化,这也是应用层网络通信的最佳实践。

1. 先否定两种不推荐的方案

首先要明确,有两种方案可以实现结构化数据传输,但非常不推荐

  1. 逐个字段读写发送:比如先发送操作数1,再发送运算符,最后发送操作数2,接收方按相同顺序逐个读取。这种方式不仅效率极低,而且耦合度极高,一旦字段顺序变更或新增字段,两端代码都要修改,容易出现逻辑错误。
  2. 直接传输结构体:在C/C++中直接将结构体通过Socket发送,这种方式虽然比逐个字段发送高效,但存在跨平台兼容性问题(不同系统的字节序、结构体对齐规则不同),而且扩展性差,仅适用于极少数封闭、无跨平台需求的特殊场景。

2. 序列化与反序列化的通俗理解

  • 序列化:把本地的结构化数据(比如C++的类、结构体),转换成一段可以在网络中传输的「纯字符串」或「二进制流」,相当于给结构化数据打了一个「包裹」,方便网络传输。
  • 反序列化:把从网络中接收的「字符串/二进制流」,还原成本地可以识别和处理的结构化数据,相当于把传输过来的「包裹」拆开,提取里面的有效信息。

这一过程完美解决了结构化数据的跨主机传输问题,也是应用层协议设计的基础。

三、底层原理:Socket读写接口的本质

在深入封装和实战之前,我们需要先打破一个常见误区:write/sendread是不是直接把数据发送到网络、从网络读取数据?

答案是否定的,这些接口的本质都是**「内存拷贝」**,数据的实际网络传输由操作系统和TCP协议负责,具体细节如下:

1. write/send的本质

当我们调用writesend接口发送数据时,函数执行的操作仅仅是:把用户态缓冲区中的数据,拷贝到内核态的TCP发送缓冲区中

只要拷贝完成,这个函数就会返回,至于数据什么时候被发送到网络、发送过程中出现丢包该如何重传、网络拥堵时该如何控制发送速率,这些都由操作系统和TCP协议自主决定,我们开发者无需进行任何干预,这也是TCP被称为「传输控制协议」的核心原因。

简单示例代码(TCP发送数据):

cpp 复制代码
// 用户态缓冲区(结构化数据转换后的字符串)
std::string send_data = "10 + 20";
// sockfd是已建立连接的Socket文件描述符
ssize_t ret = write(sockfd, send_data.c_str(), send_data.size());
if (ret < 0) {
    perror("write error");
    return -1;
}
// 此时仅完成用户态到内核发送缓冲区的拷贝,并非已发送到网络

2. read的本质

当我们调用read接口接收数据时,函数执行的操作仅仅是:把内核态的TCP接收缓冲区中的数据,拷贝到用户态缓冲区中

如果TCP接收缓冲区中没有可用数据,read接口会进入阻塞状态(这是同步IO模型的特性),直到有数据到达并完成拷贝后才会返回。

简单示例代码(TCP接收数据):

cpp 复制代码
// 用户态缓冲区
char recv_buf[1024] = {0};
// sockfd是已建立连接的Socket文件描述符
ssize_t ret = read(sockfd, recv_buf, sizeof(recv_buf) - 1);
if (ret < 0) {
    perror("read error");
    return -1;
}
// 此时仅完成内核接收缓冲区到用户态的拷贝,数据来自对端的内核发送缓冲区

3. TCP通信的终极本质

两台主机之间的TCP通信,本质上是一个「缓冲区拷贝+网络传输」的流程:

发送方用户态缓冲区 → 发送方内核TCP发送缓冲区 → 网络传输 → 接收方内核TCP接收缓冲区 → 接收方用户态缓冲区。

我们开发者接触到的,仅仅是两端的「用户态<->内核态」拷贝,中间的网络传输细节被TCP协议和操作系统封装得严严实实,这也让我们的开发变得更加简洁高效。

四、实战封装:模板方法模式封装Socket(TCP/UDP)

为了实现「底层网络操作」和「上层业务逻辑」的解耦,我们采用模板方法模式来封装Socket,核心思路是:提取TCP和UDP通信的公共流程作为父类模板,差异化实现由子类重写。

1. 父类Socket封装(Socket.cpp)

首先定义Socket父类,封装所有Socket通信的公共逻辑(比如创建套接字、关闭套接字等),并定义纯虚函数作为差异化接口,由子类实现。

核心代码示例(Socket.hpp):

cpp 复制代码
#pragma once
#include <sys/socket.h>
#include <unistd.h>

class Socket {
public:
    // 构造函数:创建套接字
    Socket(int domain, int type, int protocol) {
        _sockfd = socket(domain, type, protocol);
        if (_sockfd < 0) {
            perror("socket create error");
            exit(EXIT_FAILURE);
        }
    }

    // 析构函数:关闭套接字
    virtual ~Socket() {
        if (_sockfd >= 0) {
            close(_sockfd);
            _sockfd = -1;
        }
    }

    // 获取Socket文件描述符
    int getSockfd() const { return _sockfd; }

    // 纯虚函数:绑定地址(公共流程,子类可重写细化)
    virtual int bind(const struct sockaddr* addr, socklen_t addrlen) = 0;

    // 纯虚函数:接收连接(仅TCP需要,UDP无需实现)
    virtual int accept(struct sockaddr* addr, socklen_t* addrlen) = 0;

private:
    int _sockfd; // Socket文件描述符
};

2. 辅助类inetAddr:封装网络地址

实现inetAddr类,提供setaddr方法,封装struct sockaddr_in地址结构,简化IP地址和端口号的配置与转换,避免直接操作繁琐的C语言地址结构。

核心代码示例(inetAddr.hpp):

cpp 复制代码
#pragma once
#include <netinet/in.h>
#include <string>

class inetAddr {
public:
    // 构造函数:初始化地址结构
    inetAddr() : _addr({0}) {}

    // setaddr方法:配置IP和端口
    void setaddr(const std::string& ip, uint16_t port) {
        _addr.sin_family = AF_INET; // IPv4协议
        // 转换IP地址(字符串→网络字节序)
        inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);
        // 转换端口号(主机字节序→网络字节序)
        _addr.sin_port = htons(port);
    }

    // 获取封装后的sockaddr结构
    const struct sockaddr_in& getAddr() const { return _addr; }

private:
    struct sockaddr_in _addr; // IPv4地址结构
};

3. TCP子类:重写差异化方法

TCP协议有连接特性,需要实现listen(监听端口)、accept(接收连接)等特有方法,子类继承自Socket父类,重写纯虚函数,实现TCP通信的差异化逻辑。

核心代码示例(TCPSocket.hpp):

cpp 复制代码
#pragma once
#include "Socket.hpp"
#include "inetAddr.hpp"

class TCPSocket : public Socket {
public:
    // 构造函数:创建TCP套接字(SOCK_STREAM:流式套接字)
    TCPSocket() : Socket(AF_INET, SOCK_STREAM, 0) {}

    // 重写bind方法:绑定IP和端口
    int bind(const struct sockaddr* addr, socklen_t addrlen) override {
        return ::bind(getSockfd(), addr, addrlen);
    }

    // 重写accept方法:接收客户端连接
    int accept(struct sockaddr* addr, socklen_t* addrlen) override {
        return ::accept(getSockfd(), addr, addrlen);
    }

    // TCP特有方法:监听端口
    int listen(int backlog) {
        return ::listen(getSockfd(), backlog);
    }
};

4. TCPServer封装:实现TCP长服务

封装TCPServer类,整合TCPSocketinetAddr,实现TCP服务端的完整流程(初始化、监听、接收连接),让服务端开发更加简洁,且无需关心底层Socket细节。

核心代码示例(TCPServer.hpp):

cpp 复制代码
#pragma once
#include "TCPSocket.hpp"
#include "inetAddr.hpp"
#include <functional>
#include <thread>

class TCPServer {
public:
    // 构造函数:初始化服务端IP和端口
    TCPServer(const std::string& ip, uint16_t port) : _ip(ip), _port(port), _tcp_socket() {}

    // 初始化服务端:绑定+监听
    bool init() {
        inetAddr addr;
        addr.setaddr(_ip, _port);
        // 绑定地址
        if (_tcp_socket.bind((const struct sockaddr*)&addr.getAddr(), sizeof(addr.getAddr())) < 0) {
            return false;
        }
        // 监听端口(backlog设为10)
        if (_tcp_socket.listen(10) < 0) {
            return false;
        }
        return true;
    }

    // 启动服务端:循环接收客户端连接
    void start(std::function<void(int)> handle_client) {
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        while (true) {
            // 接收客户端连接
            int client_fd = _tcp_socket.accept((struct sockaddr*)&client_addr, &client_addr_len);
            if (client_fd < 0) {
                perror("accept error");
                continue;
            }
            // 开启子线程处理客户端请求(避免阻塞主线程)
            std::thread t(handle_client, client_fd);
            t.detach(); // 分离线程,自动回收资源
        }
    }

private:
    std::string _ip;        // 服务端IP
    uint16_t _port;         // 服务端端口
    TCPSocket _tcp_socket;  // TCP Socket对象
};

5. UDP子类:重写差异化方法

UDP协议无连接特性,无需listenaccept方法,使用recvfromsendto收发数据,子类同样继承自Socket父类,实现UDP通信的差异化逻辑。

核心代码示例(UDPSocket.hpp):

cpp 复制代码
#pragma once
#include "Socket.hpp"

class UDPSocket : public Socket {
public:
    // 构造函数:创建UDP套接字(SOCK_DGRAM:数据报套接字)
    UDPSocket() : Socket(AF_INET, SOCK_DGRAM, 0) {}

    // 重写bind方法:绑定IP和端口(UDP也需要绑定端口接收数据)
    int bind(const struct sockaddr* addr, socklen_t addrlen) override {
        return ::bind(getSockfd(), addr, addrlen);
    }

    // 重写accept方法:UDP无需接收连接,直接返回-1(标识不支持)
    int accept(struct sockaddr* addr, socklen_t* addrlen) override {
        return -1;
    }

    // UDP特有方法:发送数据到指定地址
    ssize_t sendto(const void* buf, size_t len, const struct sockaddr* dest_addr, socklen_t addrlen) {
        return ::sendto(getSockfd(), buf, len, 0, dest_addr, addrlen);
    }

    // UDP特有方法:从指定地址接收数据
    ssize_t recvfrom(void* buf, size_t len, struct sockaddr* src_addr, socklen_t* addrlen) {
        return ::recvfrom(getSockfd(), buf, len, 0, src_addr, addrlen);
    }
};

6. 主函数入口:启动TCP服务端

通过main.cc整合上述封装,启动TCP服务端,指定处理客户端请求的回调函数,完成底层网络服务的搭建。

核心代码示例(main.cc):

cpp 复制代码
#include "TCPServer.hpp"
#include <iostream>

// 客户端请求处理函数(后续将与协议层联动)
void handle_client(int client_fd) {
    std::cout << "新客户端连接:fd = " << client_fd << std::endl;
    // 后续将在这里通过Protocol解析请求、处理业务、返回响应
    close(client_fd);
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "使用方式:./tcpserver [IP] [Port]" << std::endl;
        return 1;
    }

    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    // 创建TCP服务端对象
    TCPServer server(ip, port);
    // 初始化服务端
    if (!server.init()) {
        std::cerr << "服务端初始化失败" << std::endl;
        return 1;
    }

    std::cout << "TCP服务端启动成功,监听:" << ip << ":" << port << std::endl;
    // 启动服务端,传入客户端处理函数
    server.start(handle_client);

    return 0;
}

五、核心实战:应用层协议定制(网络计算器)

底层Socket封装完成后,我们需要定制应用层协议,约定结构化数据的收发规则,这里以「网络计算器」为例,实现请求与响应的结构化传输。

1. 协议的核心职责

应用层协议就像通信双方的「约定手册」,核心职责是明确「如何读」和「如何写」,即如何将结构化的请求/响应转换为可传输的数据,以及如何从传输的数据中还原结构化信息。

2. 定义结构化数据模型

首先定义计算器的请求体(Request)和响应体(Response),对应结构化数据,包含业务所需的核心字段:

  • Request:两个操作数(xy)、一个运算符(op);
  • Response:计算结果(ret)、退出码(code,标识计算是否成功,比如0=成功、1=除零错误、2=非法运算符)。

核心代码示例(Protocol.hpp 数据模型定义):

cpp 复制代码
#pragma once
#include <string>

// 计算器请求体:结构化数据
struct Request {
    int x;          // 操作数1
    int y;          // 操作数2
    char op;        // 运算符(+、-、*、/)
};

// 计算器响应体:结构化数据
struct Response {
    int ret;        // 计算结果
    int code;       // 退出码:0=成功,1=除零错误,2=非法运算符
};

3. 协议层封装:衔接Socket与业务逻辑

封装Protocol类,实现「序列化」和「反序列化」方法,衔接底层Socket和上层计算器业务,核心功能是:

  1. Request序列化为可传输的字符串;
  2. 将接收的字符串反序列化为Request
  3. Response序列化为可传输的字符串;
  4. 将接收的字符串反序列化为Response
  5. 接收Socket文件描述符,实现网络数据的读写与解析联动。

核心代码示例(Protocol.hpp 协议方法封装):

cpp 复制代码
// 接续上面的数据模型定义
#include <unistd.h>
#include <cstring>

class Protocol {
public:
    // 构造函数:传入Socket文件描述符
    Protocol(int sockfd) : _sockfd(sockfd) {}

    // 1. Request序列化:结构化数据→字符串(先采用自定义分隔符方案)
    bool serializeRequest(const Request& req, std::string& out_str) {
        // 拼接格式:x op y(用空格分隔)
        out_str = std::to_string(req.x) + " " + req.op + " " + std::to_string(req.y);
        return true;
    }

    // 2. 字符串反序列化→Request
    bool deserializeRequest(const std::string& in_str, Request& req) {
        // 分割字符串:按空格提取字段
        size_t pos1 = in_str.find(" ");
        size_t pos2 = in_str.find(" ", pos1 + 1);
        if (pos1 == std::string::npos || pos2 == std::string::npos) {
            return false;
        }

        // 提取并转换字段
        req.x = std::stoi(in_str.substr(0, pos1));
        req.op = in_str[pos1 + 1];
        req.y = std::stoi(in_str.substr(pos2 + 1));
        return true;
    }

    // 3. Response序列化:结构化数据→字符串
    bool serializeResponse(const Response& resp, std::string& out_str) {
        // 拼接格式:ret code(用空格分隔)
        out_str = std::to_string(resp.ret) + " " + std::to_string(resp.code);
        return true;
    }

    // 4. 字符串反序列化→Response
    bool deserializeResponse(const std::string& in_str, Response& resp) {
        // 分割字符串:按空格提取字段
        size_t pos = in_str.find(" ");
        if (pos == std::string::npos) {
            return false;
        }

        // 提取并转换字段
        resp.ret = std::stoi(in_str.substr(0, pos));
        resp.code = std::stoi(in_str.substr(pos + 1));
        return true;
    }

    // 5. 发送数据(通过Socket写入序列化后的字符串)
    bool sendData(const std::string& data) {
        ssize_t ret = write(_sockfd, data.c_str(), data.size());
        return ret == (ssize_t)data.size();
    }

    // 6. 接收数据(通过Socket读取字符串到用户缓冲区)
    bool recvData(std::string& data, size_t buf_size = 1024) {
        char buf[1024] = {0};
        ssize_t ret = read(_sockfd, buf, buf_size - 1);
        if (ret <= 0) {
            return false;
        }
        data = std::string(buf, ret);
        return true;
    }

private:
    int _sockfd; // Socket文件描述符,用于网络读写
};

4. 整合协议层与服务端:处理计算器业务

修改main.cc中的handle_client函数,整合Protocol类,实现「接收请求→解析→计算→返回响应」的完整业务流程。

修改后的handle_client函数:

cpp 复制代码
#include "Protocol.hpp"
#include <iostream>

// 计算器核心业务逻辑:执行计算
void calculate(const Request& req, Response& resp) {
    resp.code = 0; // 默认成功
    switch (req.op) {
        case '+':
            resp.ret = req.x + req.y;
            break;
        case '-':
            resp.ret = req.x - req.y;
            break;
        case '*':
            resp.ret = req.x * req.y;
            break;
        case '/':
            if (req.y == 0) {
                resp.code = 1; // 除零错误
                resp.ret = 0;
            } else {
                resp.ret = req.x / req.y;
            }
            break;
        default:
            resp.code = 2; // 非法运算符
            resp.ret = 0;
            break;
    }
}

// 客户端请求处理函数:整合协议层与业务逻辑
void handle_client(int client_fd) {
    std::cout << "新客户端连接:fd = " << client_fd << std::endl;

    // 创建协议对象,关联Socket文件描述符
    Protocol protocol(client_fd);
    Request req;
    Response resp;
    std::string recv_str, send_str;

    // 接收并解析请求
    if (!protocol.recvData(recv_str)) {
        std::cerr << "接收请求失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    if (!protocol.deserializeRequest(recv_str, req)) {
        std::cerr << "解析请求失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    // 执行计算
    calculate(req, resp);

    // 序列化并发送响应
    if (!protocol.serializeResponse(resp, send_str)) {
        std::cerr << "序列化响应失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    if (!protocol.sendData(send_str)) {
        std::cerr << "发送响应失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    std::cout << "客户端请求处理完成,fd = " << client_fd << std::endl;
    close(client_fd);
}

5. 整体架构:三层解耦清晰高效

整个网络计算器的架构分为三层,各司其职,实现了完美解耦,从网络通信直接过渡到业务处理,无需关心底层细节。

同时,基于TCP的应用层协议需要解决两个核心问题(后续优化重点):

  1. 粘包问题:TCP是流式协议,多个请求/响应可能会粘在一起传输,需要约定分隔符或长度字段来区分;
  2. 完整数据解析问题:确保读取到一个完整的结构化数据,避免读取到不完整的片段导致解析失败。

六、优化方案:使用jsoncpp实现序列化与反序列化

我们之前采用了「自定义分隔符」的序列化方案,虽然实现简单,但可扩展性差,面对复杂结构化数据时容易出错。实战中,我们优先使用成熟的开源库,这里以jsoncpp为例,实现更高效、更稳定的序列化与反序列化。

1. 序列化方案对比与选择

序列化方案主要分为两类:

  1. 自定义实现:如分隔符、二进制流等,适合简单场景,无需依赖第三方库;
  2. 成熟开源库:如jsoncpp(JSON格式)、protobuf(二进制格式)等,适合复杂场景,可扩展性强,兼容性好。

2. 环境检查:确认jsoncpp是否安装

首先通过终端命令检查系统是否安装了jsoncpp,确保开发环境可用:

bash 复制代码
pkg-config --list-all | grep json

3. jsoncpp核心使用:序列化(结构化数据→JSON字符串)

jsoncpp将结构化数据转换为JSON格式的字符串,方便网络传输,核心步骤是:

  1. 引入jsoncpp头文件(注意带路径,#include <json/json.h>);
  2. 创建Json::Value对象,存入结构化数据的字段;
  3. 使用Json::WriterFastWriterStyledWriter)将Json::Value转换为字符串。

核心代码示例(Protocol.hpp 中修改序列化方法):

cpp 复制代码
// 引入jsoncpp头文件
#include <json/json.h>

// 重写Request序列化:使用jsoncpp(结构化数据→JSON字符串)
bool serializeRequest(const Request& req, std::string& out_str) {
    Json::Value root;
    root["x"] = req.x;
    root["y"] = req.y;
    root["op"] = req.op;

    // FastWriter:生成紧凑格式JSON字符串(无缩进,适合网络传输)
    Json::FastWriter writer;
    out_str = writer.write(root);
    return true;
}

// 重写Response序列化:使用jsoncpp(结构化数据→JSON字符串)
bool serializeResponse(const Response& resp, std::string& out_str) {
    Json::Value root;
    root["ret"] = resp.ret;
    root["code"] = resp.code;

    Json::FastWriter writer;
    out_str = writer.write(root);
    return true;
}

序列化后的结果是一段标准的JSON字符串,带有换行符,可直接通过Socket传输,示例如下:

json 复制代码
{"x":10,"y":20,"op":"+"}\n

4. jsoncpp核心使用:反序列化(JSON字符串→结构化数据)

反序列化是序列化的逆过程,核心步骤是:

  1. 引入jsoncpp头文件;
  2. 创建Json::Reader对象,将接收的JSON字符串解析为Json::Value对象;
  3. Json::Value对象中提取字段,赋值给结构化数据。

核心代码示例(Protocol.hpp 中修改反序列化方法):

cpp 复制代码
// 重写Request反序列化:使用jsoncpp(JSON字符串→结构化数据)
bool deserializeRequest(const std::string& in_str, Request& req) {
    Json::Value root;
    Json::Reader reader;

    // 解析JSON字符串
    if (!reader.parse(in_str, root)) {
        return false;
    }

    // 提取字段并赋值
    req.x = root["x"].asInt();
    req.y = root["y"].asInt();
    req.op = root["op"].asChar();
    return true;
}

// 重写Response反序列化:使用jsoncpp(JSON字符串→结构化数据)
bool deserializeResponse(const std::string& in_str, Response& resp) {
    Json::Value root;
    Json::Reader reader;

    // 解析JSON字符串
    if (!reader.parse(in_str, root)) {
        return false;
    }

    // 提取字段并赋值
    resp.ret = root["ret"].asInt();
    resp.code = root["code"].asInt();
    return true;
}

5. FastWriter与StyledWriter的区别

jsoncpp提供了两种Writer,适用于不同场景:

  1. FastWriter:生成紧凑格式 的JSON字符串,无缩进、无多余空格,体积更小,适合网络传输数据存储
  2. StyledWriter:生成格式化格式 的JSON字符串,有缩进、有换行,可读性更强,适合调试和日志输出

6. 匿名表达式(Lambda)的使用

在实际开发中,我们常使用Lambda表达式简化代码,比如在TCPServerstart方法中,直接传入Lambda表达式作为客户端处理函数,无需单独定义函数,让代码更简洁紧凑。

示例代码(main.cc 中简化启动逻辑):

cpp 复制代码
// 启动服务端,直接传入Lambda表达式作为客户端处理函数
server.start([](int client_fd) {
    std::cout << "新客户端连接:fd = " << client_fd << std::endl;
    // 内部逻辑与之前的handle_client函数一致
    Protocol protocol(client_fd);
    Request req;
    Response resp;
    std::string recv_str, send_str;

    if (!protocol.recvData(recv_str) || !protocol.deserializeRequest(recv_str, req)) {
        std::cerr << "处理请求失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    calculate(req, resp);

    if (!protocol.serializeResponse(resp, send_str) || !protocol.sendData(send_str)) {
        std::cerr << "发送响应失败,fd = " << client_fd << std::endl;
        close(client_fd);
        return;
    }

    std::cout << "客户端请求处理完成,fd = " << client_fd << std::endl;
    close(client_fd);
});
相关推荐
晚风吹长发2 小时前
初步理解Linux中的信号概念以及信号产生
linux·运维·服务器·算法·缓冲区·inode
ccieluo2 小时前
华为eNSP网络工程毕业设计 基于双出口智能选路的中小型企业网络设计 策略路由 IPSec SSL 无线网络 BGP
网络·华为·毕业设计
历程里程碑2 小时前
Linux 4 指令结尾&&通过shell明白指令实现的原理
linux·c语言·数据结构·笔记·算法·排序算法
zhengtianzuo2 小时前
049-Linux抓屏-xcb
linux·抓屏·xcb
Zfox_2 小时前
【Docker#2】容器化虚拟化
运维·后端·docker·容器
fanruitian2 小时前
k8s 设置副本数
linux·容器·kubernetes
txinyu的博客2 小时前
unique_ptr
linux·服务器·c++
lpfasd1232 小时前
《影响力》精读笔记
网络·笔记·成长
lihui_cbdd2 小时前
GROMACS 2026 Beta 异构集群完全部署手册(5090可用)
linux·计算化学