【Linux网络】Linux 网络编程:应用层自定义协议与序列化(2)序列化与反序列化

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> 摘要](#1 ~> 摘要)
  • [2 ~> 前言:为什么要自定义应用层协议](#2 ~> 前言:为什么要自定义应用层协议)
  • [3 ~> TCP/UDP 通信本质与粘包问题](#3 ~> TCP/UDP 通信本质与粘包问题)
    • [3.1 通信本质](#3.1 通信本质)
    • [3.2 粘包问题](#3.2 粘包问题)
    • [3.3 对比TCP / UDP有无"粘包问题"](#3.3 对比TCP / UDP有无“粘包问题”)
  • [4 ~> Linux 内核报文管理:sk_buff 原理](#4 ~> Linux 内核报文管理:sk_buff 原理)
    • [4.1 内核管理思想](#4.1 内核管理思想)
    • [4.2 核心结构体](#4.2 核心结构体)
    • [4.3 封装与解包](#4.3 封装与解包)
    • [4.4 socket 与报文队列](#4.4 socket 与报文队列)
  • [5 ~> 协议本质与直接传输结构体的缺陷](#5 ~> 协议本质与直接传输结构体的缺陷)
    • [5.1 什么是协议](#5.1 什么是协议)
    • [5.2 直接发结构体为什么不行](#5.2 直接发结构体为什么不行)
    • [5.3 内核为什么能用结构体](#5.3 内核为什么能用结构体)
    • [5.4 为什么会传递结构化对象? ~~> 协议!](#5.4 为什么会传递结构化对象? ~~> 协议!)
  • [6 ~> 序列化与反序列化核心思想](#6 ~> 序列化与反序列化核心思想)
    • [6.1 定义](#6.1 定义)
    • [6.2 一句话记忆](#6.2 一句话记忆)
    • [6.3 价值](#6.3 价值)
    • [6.4 序列化 / 反序列化](#6.4 序列化 / 反序列化)
      • [6.4.1 序列化](#6.4.1 序列化)
      • [6.4.2 序列化 / 反序列化流程](#6.4.2 序列化 / 反序列化流程)
  • [7 ~> 应用层协议设计:以网络计算器为例](#7 ~> 应用层协议设计:以网络计算器为例)
    • [7.1 需求](#7.1 需求)
    • [7.2 请求协议 Request](#7.2 请求协议 Request)
    • [7.3 应答协议 Response](#7.3 应答协议 Response)
    • [7.4 约定](#7.4 约定)
  • [8 ~> TCP 粘包解决方案:长度 + 分隔符](#8 ~> TCP 粘包解决方案:长度 + 分隔符)
    • [8.1 方案格式](#8.1 方案格式)
    • [8.2 发送流程](#8.2 发送流程)
    • [8.3 接收解析流程](#8.3 接收解析流程)
    • [8.4 优点](#8.4 优点)
  • [9 ~> Jsoncpp 序列化实战与代码实现](#9 ~> Jsoncpp 序列化实战与代码实现)
    • [9.1 怎么做?](#9.1 怎么做?)
    • [9.2 JSON 优势](#9.2 JSON 优势)
    • [9.3 Jsoncpp 核心 API](#9.3 Jsoncpp 核心 API)
    • [9.4 Jsoncpp安装](#9.4 Jsoncpp安装)
    • [9.5 核心代码片段](#9.5 核心代码片段)
    • [9.6 编译](#9.6 编译)
  • [10 ~> 网络计算器代码框架](#10 ~> 网络计算器代码框架)
    • [10.1 文件结构](#10.1 文件结构)
    • [10.2 服务端流程](#10.2 服务端流程)
    • [10.3 客户端流程](#10.3 客户端流程)
    • [10.4 demo代码(附带演示)](#10.4 demo代码(附带演示))
  • 结尾


1 ~> 摘要

摘要:本文从 TCP/UDP 通信本质、Linux 内核报文管理入手,系统讲解应用层自定义协议设计、序列化 / 反序列化原理与工程实践,并以网络计算器为例完整实现,彻底解决 TCP 粘包问题,适合后端开发、网络编程学习者系统掌握


2 ~> 前言:为什么要自定义应用层协议

网络编程不只调用 socket API,业务数据需要结构化传输、跨平台 / 跨语言互通、可靠解析。传输层只负责数据搬运,不理解业务语义 ------必须在应用层约定协议、做序列化、处理报文边界


3 ~> TCP/UDP 通信本质与粘包问题

3.1 通信本质

  • TCP:面向字节流,无报文边界
  • UDP:面向数据报,自带报文边界
  • read/write/send/recv 本质:内核缓冲区 ↔ 用户态缓冲区拷贝
  • 一个 socket fd 自带发送缓冲区 + 接收缓冲区,天然支持全双工

3.2 粘包问题

  • 根源:TCP 流式传输,多次发送的数据在接收端连在一起,边界丢失
  • 表现:多读、少读、半报文
  • UDP:不存在粘包

3.3 对比TCP / UDP有无"粘包问题"

  • TCP:连续字节流
  • UDP:一个个独立数据报

粘包只发生在 TCP,由应用层解决。


4 ~> Linux 内核报文管理:sk_buff 原理

4.1 内核管理思想

操作系统对所有网络报文的管理遵循"先描述,再组织"的规则。

4.2 核心结构体

  • struct sk_buff:内核描述报文的核心结构
  • 包含链表指针、各层协议头、数据指针、长度、设备信息等

4.3 封装与解包

  • 封装:data 指针上移,预留头部空间
  • 解包:data 指针下移,剥离头部
  • 移动指针而非拷贝数据,高效。

4.4 socket 与报文队列

一条链路:fd → struct file → struct socket → struct sock

  • struct sock包含:
    • sk_write_queue:发送队列
    • sk_receive_queue:接收队列

5 ~> 协议本质与直接传输结构体的缺陷

5.1 什么是协议

协议 = 通信双方约定的结构化数据struct/class),包含字段含义、顺序、解析规则。

5.2 直接发结构体为什么不行

  • 跨平台性差:内存对齐规则不同、字节序不同
  • 跨语言性差:C++ 结构体无法被 Java/Python/ 前端识别
  • 扩展性差:字段增减会导致解析崩溃

5.3 内核为什么能用结构体

  • 统一用 C 语言实现
  • 做了跨平台字节序 / 对齐兼容
  • 追求极致性能

5.4 为什么会传递结构化对象? ~~> 协议!


6 ~> 序列化与反序列化核心思想

6.1 定义

  • 序列化:结构化对象 → 字节流 / 字符串(方便网络发送)
  • 反序列化:字节流 / 字符串 → 结构化对象(方便业务处理)

6.2 一句话记忆

发数据:多变一

收数据:一变多

6.3 价值

  • 跨平台、跨语言、可扩展、可持久化

6.4 序列化 / 反序列化

6.4.1 序列化

6.4.2 序列化 / 反序列化流程


7 ~> 应用层协议设计:以网络计算器为例

7.1 需求

客户端发送计算请求,服务端计算并返回结果与状态。

7.2 请求协议 Request

字段:

  • x:左操作数
  • y:右操作数
  • oper:运算符(+ - * / %)

7.3 应答协议 Response

字段:

  • result:计算结果
  • exitcode:状态码(0 成功,非 0 错误)

7.4 约定

客户端与服务端共用同一份协议头文件 Protocol.hpp


8 ~> TCP 粘包解决方案:长度 + 分隔符

8.1 方案格式

bash 复制代码
长度\r\n报文内容\r\n

8.2 发送流程

  1. 结构化数据序列化
  2. 计算长度
  3. 拼接:长度 + \r\n + 报文 + \r\n

8.3 接收解析流程

1、从缓冲区查找\r\n,提取长度字符串;

2、转成整数,得到报文真实长度;

3、判断缓冲区数据是否足够;

4、提取完整报文,移除已读数据;

5、反序列化交给业务

8.4 优点

  • 通用、稳定、易实现
  • 彻底解决粘包与半包
  • 支持多报文连续传输

9 ~> Jsoncpp 序列化实战与代码实现

9.1 怎么做?

9.2 JSON 优势

  • 纯文本、轻量
  • 跨语言支持极好
  • 键值对结构,自描述

9.3 Jsoncpp 核心 API

  • 序列化:Json::ValueFastWriter → string
  • 反序列化:Json::Reader → parse → 按 key 取值

J

9.4 Jsoncpp安装

Jsoncpp作为一个开源的第三方库,需要自己手动安装一下。

  • Ubuntu24.04环境下的安装命令:
bash 复制代码
sudo apt-get install -y libjsoncpp-dev

安装加载过程:

bash 复制代码
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/OnlineCalculator$ sudo apt-get install -y libjsoncpp-dev
[sudo] password for alice: 
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  eatmydata libeatmydata1 python3-json-pointer python3-jsonpatch python3-jsonschema python3-pyrsistent
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  libjsoncpp25
The following NEW packages will be installed:
  libjsoncpp-dev libjsoncpp25
0 upgraded, 2 newly installed, 0 to remove and 60 not upgraded.
Need to get 106 kB of archives.
After this operation, 352 kB of additional disk space will be used.
Get:1 http://mirrors.tencentyun.com/ubuntu noble/main amd64 libjsoncpp25 amd64 1.9.5-6build1 [82.8 kB]
Get:2 http://mirrors.tencentyun.com/ubuntu noble/main amd64 libjsoncpp-dev amd64 1.9.5-6build1 [23.0 kB]
Fetched 106 kB in 0s (671 kB/s)      
Selecting previously unselected package libjsoncpp25:amd64.
(Reading database ... 162861 files and directories currently installed.)
Preparing to unpack .../libjsoncpp25_1.9.5-6build1_amd64.deb ...
Unpacking libjsoncpp25:amd64 (1.9.5-6build1) ...
Selecting previously unselected package libjsoncpp-dev:amd64.
Preparing to unpack .../libjsoncpp-dev_1.9.5-6build1_amd64.deb ...
Unpacking libjsoncpp-dev:amd64 (1.9.5-6build1) ...
Setting up libjsoncpp25:amd64 (1.9.5-6build1) ...
Setting up libjsoncpp-dev:amd64 (1.9.5-6build1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.7) ...
Scanning processes...                                                                                                                                                               
Scanning linux images...                                                                                                                                                            
Running kernel seems to be up-to-date.
No services need to be restarted.
No containers need to be restarted.
No user sessions are running outdated binaries.
No VM guests are running outdated hypervisor (qemu) binaries on this host.

9.5 核心代码片段

cpp 复制代码
// Request序列化
bool Serialize(string *out) {
    Json::Value root;
    root["datax"] = _x;
    root["datay"] = _y;
    root["oper"] = _oper;
    Json::FastWriter w;
    *out = w.write(root);
    return true;
}

// Request反序列化
bool Deserialize(string &in) {
    Json::Reader r;
    Json::Value root;
    if (!r.parse(in, root)) return false;
    _x = root["datax"].asInt();
    _y = root["datay"].asInt();
    _oper = root["oper"].asInt();
    return true;
}

9.6 编译

bash 复制代码
g++ -o cal main.cc Protocol.hpp -ljsoncpp

10 ~> 网络计算器代码框架

10.1 文件结构

  • Protocol.hpp:Request/Response + 序列化 / 反序列化 + Encode / Decode
  • TcpServer.hpp:TCP 服务器封装
  • OnlineCalServer.cc:服务端逻辑
  • OnlineCalClient.cc:客户端逻辑

10.2 服务端流程

  • accept 客户端
  • recv 数据到缓冲区
  • 循环 Decode 提取完整报文
  • 反序列化得到 Request
  • 计算逻辑,生成 Response
  • 序列化 + 编码发送

10.3 客户端流程

  • 构造 Request
  • 序列化 + 编码
  • send 发送
  • recv 接收响应
  • Decode + 反序列化
  • 输出结果

10.4 demo代码(附带演示)

10.4.1 OnlineCalClient.cc

cpp 复制代码
#include "Protocol.hpp"

int main()
{}

10.4.2 OnlineCalServer.cc

cpp 复制代码
#include "Protocol.hpp"

int main()
{
    // // 序列化
    // Request req1(10,20,'+');
    // std::string s;
    // req1.Serialize(&s);

    // std::cout << s << std::endl;

    // 改一下服务端的代码
    std::string jsonstring = "{\"datax\":-30,\"datay\":100,\"oper\":43}";
    Request req(0,0,'-');
    req.Print();

    // 分割线,看得清楚点
    std::cout << "----------------------------------" << std::endl;
    req.DeSerialize(jsonstring);
    req.Print();    // 也打印一下

    return 0;
}

10.4.3 Protocol.hpp

cpp 复制代码
#ifndef __PROTOCOL_HPP
#define __PROTOCOL_HPP

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// 请求报文
class Request
{
public:
    Request(int x,int y,char oper) : _x(x),_y(y),_oper(oper)
    {}
    // 序列化和反序列化
    void Serialize(std::string *outstr)
    {
        // 序列化 --> 结构化属性(多变一)
        // 设置键值,json里面
        Json::Value root;
        root["datax"] = _x;
        root["datay"] = _y;
        root["oper"] = _oper;

        root["score"].append(20);
        root["score"].append(40);
        root["score"].append(50);
        root["score"].append(70);

        // 把Json::Value转成字符串,至此序列化就有了
        Json::FastWriter writer;
        *outstr = writer.write(root);
    }
    void DeSerialize(std::string &instr)
    {
        // 反序列化 --> 字节流信息(一变多)
        Json::Value root;
        Json::Reader reader;
        if(reader.parse(instr,root))    // parse要转进来instr、root,第三个参数不管
        {
            _x = root["datax"].asInt();
            _y = root["datay"].asInt();
            _oper = root["oper"].asInt();   // oper是char,没有AsChar
        }   // 至此就完成了反序列化
    }
    void Print()
    {
        std::cout << "_x" << _x << std::endl;
        std::cout << "_y" << _y << std::endl;
        std::cout << "_oper" << _oper << std::endl;
    }
    ~Request()
    {}
private:
    int _x;
    int _y;
    char _oper; // _x _oper(运算符) _y, + - * / %(取模) 10 / 0 -1
};

// 应答报文,server -> client
class Response
{
public:
    Response()
    {}
    // 同样的,序列化和反序列化
    void Serialize()
    {
        // 序列化
    }
    void DeSerialize()
    {
        // 反序列化
    }
    ~Response()
    {}
private:
    int _result;    // 光有一个结果是不够的,还要有另外一个字段来表示,防止一个结果表示得混淆
    int _exitcode;  // 0: 结果可信;1/2/3/4 ...:结果不可信,错误原因,状态码,类似于进程退出码
};

// 如何序列化?如何反序列化?
// Request为例,自己做序列化和反序列化
// 今天先说方案1:
// "_x _opet _y" --> 序列化
// 如何解决粘包问题?是应用层(上层,程序员)自己解决的!
// len -> "len"\r\n"_x _opet _y"\r\n
// 解包
// 得到完整报文
// 反序列化
// 方案2:json方案

#endif

10.4.4 TcpServer.hpp

cpp 复制代码
// 现在还什么都没写

10.4.5 运行结果


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux网络】Linux 网络编程:应用层自定义协议与序列化(1)初识

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
智者知已应修善业1 小时前
【51单片机一个按键切合初始流水灯按一下对半闪烁按一下显示时间】2023-10-16
c++·经验分享·笔记·算法·51单片机
XiYang-DING1 小时前
【Java EE】Cookie
服务器·前端·java-ee
天问一1 小时前
bat文件切换电脑ip
服务器·网络·tcp/ip
kaoa0001 小时前
Linux入门攻坚——76、虚拟化技术基础原理-1
linux·运维·服务器
yyuuuzz1 小时前
企业出海aws运维常见问题梳理
运维·服务器·网络·数据库·aws
cui_ruicheng1 小时前
Linux线程(四):线程池、日志系统与单例模式
linux·开发语言·单例模式
AOwhisky1 小时前
Docker 学习笔记:网络篇
linux·运维·网络·笔记·学习·docker·容器
Bat U1 小时前
JavaEE|网络编程
运维·服务器·网络
gs801401 小时前
避坑指南:Nginx 多层代理下的“404”与“重定向死循环”深度排查
运维·nginx