跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

Linux 序列化与反序列化原理及自定义网络协议实现

深入探讨了 Linux 环境下序列化与反序列化的原理及其在网络通信中的应用。文章对比了直接传递结构体与序列化方案的优劣,分析了内存对齐、大小端及跨语言适配等问题。重点讲解了 TCP 缓冲区机制导致的半包与粘包现象,并提出通过自定义协议报头解决该问题的思路。最后结合网络版计算器项目,演示了如何使用 JsonCpp 库实现 Request/Response 对象的序列化与反序列化,并通过 Socket 封装与多进程模型完成完整的客户端 - 服务端通信架构。

FlinkHero发布于 2026/3/27更新于 2026/10/2299 浏览
Linux 序列化与反序列化原理及自定义网络协议实现

Linux 序列化与反序列化原理及自定义网络协议实现

在 Linux 网络编程和系统开发中,序列化与反序列化几乎是绕不开的话题。无论是进程间通信,还是基于 Socket 的网络传输,数据最终都需要以字节流的形式在系统中流转。很多初学者在实际开发中往往只会'用协议',却并不清楚协议是如何设计的、数据又是如何被序列化和还原的。今天我们将围绕 Linux 环境下的序列化与反序列化,从基本原理入手,逐步分析自定义协议的设计思路与实现方式。

一、序列化与反序列化基础

在讲解概念之前,先回顾一下直接传递结构体的方式。比如通过网络完成一个简单的计算器,客户端和服务端需要约定好传输的结构体内容。例如两个运算数字和运算符(+ - * /),客户端将结构体发给服务端,服务端通过指针提取数据。

但这种直接传递结构体的方式会面临许多问题:

  1. 内存对齐问题:不同编译器或操作系统对结构体的填充规则可能不同,导致读取时出现乱码。
  2. 大小端问题:如果客户端是小端机器,服务端是大端机器,数据读取顺序可能相反。
  3. 适配性问题:跨语言通信(如 C++ 客户端与 Java 服务端)难以完美模拟 C 内存中的物理布局。

尽管这些问题可以通过特定手段解决,且直接传递结构体效率极高(CPU 占用低),但为了更好的扩展性和维护性,我们通常采用序列化与反序列化。

序列化是将数据由结构体转化为字符串(多变一),方便网络发送;反序列化则是将字符串转化回结构体(一变多),方便上层处理。

相较于直接传递结构体,序列化的优点在于:

  • 方便网络发送:统一格式,减少传输复杂度。
  • 可扩展性和可维护性:支持新旧版本共存。例如未来增加头像、性别字段,接收方只需忽略未知字段即可,无需强制同步修改结构体定义。

自定义协议的本质就是制定双方都能认识的、符合通信和业务需要的结构化数据。

二、深入理解 IO 函数与 TCP 特性

有了序列化的认识后,我们需要重新审视 read, write, recv, send 这些 IO 函数以及 TCP 的全双工特性。

使用 Socket 编程 API 时,内核中会形成发送缓冲区和接收缓冲区。IO 函数的本质是拷贝:发送数据是将用户数据拷贝到内核缓冲区,读取数据也是从缓冲区拷贝到用户空间。至于何时发送、发多少,则由 TCP 协议决定。

这里涉及几个关键问题:

  1. 半包问题:发送的数据缺斤少两,导致接收方收到的消息不完整。
  2. 粘包问题:发送方一次性发送多组小数据,接收方缓冲区较大,一次性读出多组数据,导致无法区分边界。

这两种问题都需要通过自定义协议来解决。TCP 协议像快递员,只负责把包裹送到,不关心包裹内容;自定义协议则规定包裹里具体是什么(如牛仔裤)。

关于全双工通信,是因为每一端都有独立的发送和接收缓冲区,互不影响。每个通过 socket 接口创建的文件描述符,内核都会分配一套独立的缓冲区,确保多客户端通信时互不干扰。

三、网络版计算器实战

3.1 约定方案

为了实现网络版计算器,我们需要约定客户端和服务端的传输规则。这里采用第二种方案:定义结构体来标识交互信息,发送时将结构体按规则转化为字符串,接收时再还原。

3.2 代码实现

3.2.1 准备工作

首先创建必要的文件,包括服务端 (Main.cc) 和客户端 (Client.cc)。我们需要一个封装好的 InetAddr.hpp 类来处理 IP 和端口转换。












  {
:
    {
        _port = (_addr.sin_port);
         ipbuffer[];
        (AF_INET, &(_addr.sin_addr.s_addr), ipbuffer, (ipbuffer));
        _ip = ipbuffer;
    }

    {
        (&_addr, , (_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = (_port);
        (AF_INET, _ip.(), &(_addr.sin_addr.s_addr));
    }

:
    () {}
    (  sockaddr_in &addr) : _addr(addr) { (); }
    ( port,  std::string &ip = ) : _port(port), _ip(ip) { (); }

    { _addr = addr; (); }
    {  _ip; }
    {  _port; }
     * () {  (_addr); }
    {  (_addr); }
    {  _ip +  + std::(_port); }
     ==( InetAddr &addr) {  (_ip == addr._ip && _port == addr._port); }
    ~() {}

:
      _addr;
    std::string _ip;
     _port;
};
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define Conv(addr) ((struct sockaddr*)&addr)
class
InetAddr
private
void Net2Host()
ntohs
char
64
inet_ntop
sizeof
void Host2Net()
memset
0
sizeof
htons
inet_pton
c_str
public
InetAddr
InetAddr
const
struct
Net2Host
InetAddr
uint16_t
const
"0.0.0.0"
Host2Net
void Init(const struct sockaddr_in &addr)
Net2Host
std::string Ip()
return
uint16_t Port()
return
struct
sockaddr
Addr
return
Conv
socklen_t Length()
return
sizeof
std::string ToString()
return
"-"
to_string
bool
operator
const
return
InetAddr
private
struct
sockaddr_in
uint16_t

这个类封装了 Socket 套接字,便于主机序和网络序的转换,并提供了获取 IP 和端口的方法。

3.2.2 Socket 封装

我们使用模板方法模式封装 Socket 接口。定义一个基类 Socket,包含纯虚函数 create, bind, listen,子类 TcpSocket 实现这些函数。基类提供一个 Build 函数整合流程,通过多态调用完成初始化。

对于 accept 函数,除了返回文件描述符,还需要获取客户端的 IP 和端口信息,保存在 peer 结构中并通过 addr.Init() 赋值。

3.2.3 TcpServer.hpp 的实现

服务端核心逻辑在 TcpServer 中。构造函数通过 build 完成初始化。run 函数负责启动服务,采用多进程模型同时处理多个客户端请求。

为了避免父进程阻塞等待子进程结束,我们利用信号处理机制。父子进程需关闭各自不用的文件描述符:子进程关闭监听套接字,父进程关闭新创建的连接套接字。

子进程中实现 Recv 和 Send 函数处理数据收发。由于存在半包和粘包风险,接收数据时需判断完整性,不能简单累加字符串。

3.2.4 Protocol 协议的实现

自定义协议通过 Request 和 Response 类处理。Request 包含操作数和运算符,Response 包含计算结果和状态码 _code(用于标识除零等错误情况)。

序列化与反序列化借助 JsonCpp 库实现。JsonCpp 提供 Value 对象存储数据,FastWriter 生成紧凑字符串,Reader 解析字符串。

为了解决半包和粘包问题,我们在序列化后的字符串前添加报头,记录有效载荷长度。Protocol 类提供 Package 方法打包数据,Unpack 方法解包。

Unpack 函数将输入输出型参数 origin_str 传入,若遇到粘包,截取完整报文后将其从原字符串移除,避免影响后续逻辑。

3.2.5 客户端与服务端整合

服务端代码 (Main.cc) 整合了服务器模块、解析器 (Parser) 和计算器 (Calculator)。Parser 负责回调处理请求,Calculator 执行具体的加减乘除运算。

客户端代码 (Client.cc) 遵循相同逻辑,建立连接后发送序列化请求,接收并解析响应。

运行测试可见,网络计算器功能正常,包括除零等特殊情况也能通过 _code 正确反馈。

通过以上步骤,我们完成了从原理分析到自定义协议实现的完整闭环,真正搞懂了数据在网络通信中的生命周期。

目录

  1. Linux 序列化与反序列化原理及自定义网络协议实现
  2. 一、序列化与反序列化基础
  3. 二、深入理解 IO 函数与 TCP 特性
  4. 三、网络版计算器实战
  5. 3.1 约定方案
  6. 3.2 代码实现
  7. 3.2.1 准备工作
  8. 3.2.2 Socket 封装
  9. 3.2.3 TcpServer.hpp 的实现
  10. 3.2.4 Protocol 协议的实现
  11. 3.2.5 客户端与服务端整合

更多推荐文章

查看全部
  • 商汤开源 SenseNova-MARS:多模态搜索推理模型突破
  • 即梦 AI 基础操作指南:快速上手绘画与视频生成
  • Java 9 至 Java 25:语言演进与技术革新深度解析
  • AR 健身教练:形随心动 - 基于 Rokid CXR-M SDK 的实践落地
  • JVM 核心调优:十个最常用的配置参数
  • Prometheus 进阶实战:MySQL、Nginx 与 Memcached 监控部署
  • Python 学习路线图与核心技术栈详解
  • 无线联邦学习:隐私保护下的分布式 AI 协同机制
  • OpenClaw 多平台卸载指南(Windows/macOS/Linux/npm/pnpm)
  • MasterGo 设计稿转前端代码:基于 TRAE CN 与 MCP 协议实战
  • 顶级大模型普遍经过知识蒸馏:研究揭示量化方法与影响
  • Flutter for OpenHarmony 实战:flex_color_scheme 构建鸿蒙美学 UI
  • AI 大模型通信机制解析:流式传输与数据封装逻辑
  • 春晚机器人背后的 AI 大模型风口与高薪职业机会
  • OpenClaw 解析:AI 智能体的能力、隐患与未来
  • C++ map 与 multimap 使用指南:原理、API 及算法实战
  • FLUX.1-dev 与 SDXL 像素艺术生成质量深度对比
  • Dify 接入企业微信群聊机器人配置指南
  • Windows 环境下 Git 安装与配置指南
  • C++ 智能指针:使用场景、实现原理与内存泄漏防治

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online