应用层自定义协议与序列化
上一篇:socket套接字-TCPhttps://blog.csdn.net/Small_entreprene/article/details/147653994?sharetype=blogdetailsharerId=147653994sharerefer=PCsharesource=Small_entreprenesharefrom=mp_from_link之前我们学习过,网络协议栈是分层的,在不含物理层下,有四层协议:在我们之前编写的代码中,所有代码都属于应用层代码。我们所使用的创建套接字、绑定监听、接受连接、接收数据、发送数据等接口,全部都是操作系统提供的系统调用。需要重新明确的是:底层网络收发、TCP 握手、IP 路由、MAC 寻址,全是内核 / 驱动在默默干活。我们写的应用程序,只负责 “发什么数据、怎么解析数据”,不负责 “数据怎么传过去”。在我们以前的代码里,数据的传输都是以字节和字符串的形式进行。于是,我们有时候把一个字符串当作另一个字符串来处理,有时候又认为它是一条命令;有时候觉得它像是一个字典,有时候又觉得它是一个聊天消息。实际上,这只是随着应用场景的不同,我们对字符串本身的含义进行约定。其实,在之前的开发中我们已经接触过协议的概念,只不过当时我们没有特别强调如何定制一个协议。我们上一篇的 TCP 代码是有一点点问题的:char buffer[1024]; ssize_t n = read(_sockfd, buffer, sizeof(buffer)-1);//读取字符串,其实是有BUG的带着这个问题,我们下面来重谈应用层协议!一、应用层与协议概述我们程序员所编写的解决实际问题、满足日常需求的网络程序,都运行在应用层。再谈 "协议"协议本质上是一种“约定”。本质就是一种结构化数据,Socket API 的接口在读写数据时,按 "字符串" 的方式来发送接收。那么,若要传输 "结构化的数据",该怎么办呢?其实,协议就是双方约定好的结构化的数据。例如,实现一个服务器版的加法器,客户端发送两个加数,服务器进行计算并返回结果。我们可以有两种约定方案:方案一:客户端发送形如 “1+1” 的字符串,其中包含两个整型操作数,数字和运算符之间无空格,运算符仅限 “+”。方案二:定义结构体表示交互信息,发送数据时将结构体按规则转换为字符串,接收数据时再按相同规则转回结构体,这一过程称为 “序列化” 和 “反序列化”。【如果有知道 ProtoBuf 的话,就会更清楚了!】无论采用哪种方案,只要保证一端发送的数据另一端能正确解析即可,这就是应用层协议。(约定好各个字段的含义,本质就是约定好协议!)(双方都清楚的设计!!!)为了深入理解协议,我们打算自定义实现协议过程,采用方案二,引入序列化和反序列化,本篇使用现成的 Jsoncpp 库,并对 socket 进行字节流读取处理。二、重新理解 read、write、recv、send 和 TCP 为何支持全双工在任何一台主机上,TCP 连接既有发送缓冲区又有接收缓冲区,所以在内核中可以边发消息边收消息,即全双工,这就是为什么一个 tcp sockfd 既能读又能写的原因。实际数据的发送时机、发送量以及错误处理等由 TCP 控制,所以 TCP 被称为传输控制协议。其实我们下面对通过序列化和反序列化的学习理解,我们将更加深刻 TCP 为何是支持全双工的!举个生活当中的例子:背景:A 和 B 要实现在 QQ 上聊天,这当然就是通过网络来聊天啦😋消息的准备和序列化:在主机 A 上,用户输入消息 "你好啊",然后应用程序(QQ)会获取当前时间和用户昵称,比如时间是 "2023-10-12 15:30:45",昵称是 "新时代好青年"。这些信息在应用层被组织成结构化的数据,包括消息内容、时间戳和昵称。为了通过网络发送,这些结构化的数据需要被序列化,也就是转换成一长串字符,像 "你好啊 2023-10-12 15:30:45 新时代好青年" 这样。系统调用和内核发送缓冲区:主机 A的应用程序通过系统调用write(sockfd, buffer, strlen(buffer))将序列化后的数据从用户缓冲区写入操作系统内核的发送缓冲区。这里的sockfd是套接字文件描述符,buffer是存储序列化数据的用户缓冲区。操作系统内核的发送缓冲区会暂时存储这些数据,等待 TCP 协议进行处理。TCP 协议会将数据分段,并添加必要的头部信息(这是定制协议的过程,我们下面的自定义协议,可以通过在序列化后的字符串前添加信息达到对端的正确读取使用,因为 TCP 是面向字节流的,这会导致我们直接使用 read 从对端内核接收缓冲区读取数据的时候,导致少读或多读报文,导致报文读取不完整或不正确),然后通过网络层发送到主机B。网络传输:数据通过网络从主机 A 传输到主机 B。在这个过程中,数据以字节流的形式在物理网络中流动。内核接收缓冲区和系统调用:主机 B 的操作系统内核接收到数据后,会将数据存入内核接收缓冲区。然后,主机 B 的应用程序通过系统调用read(sockfd, buffer, strlen(buffer))从内核的接收缓冲区读取数据。这里的read系统调用会将数据从内核空间复制到用户空间的应用缓冲区,供应用程序使用。反序列化和消息显示:主机 B 的应用程序接收到数据后,会进行反序列化操作,将字符流还原为原来的结构化数据,包括消息内容、时间戳和昵称。最后,应用程序将消息内容显示在聊天界面中,用户就能看到 "你好啊" 这条消息了。在整个过程中,TCP 协议支持全双工通信,这意味着主机 A 和主机 B 可以同时发送和接收数据。这是因为 TCP 在内核中为发送和接收分别设置了独立的缓冲区,发送缓冲区用于存储待发送的数据,接收缓冲区用于存储已接收但尚未被应用层读取的数据。这种机制允许双方在通信过程中无需等待对方完成某一方向的数据传输,从而实现高效的双向通信。系统调用write/send用于将数据从用户空间写入内核发送缓冲区,而read/recv则用于从内核接收缓冲区读取数据到用户空间。这些调用是用户程序与操作系统内核之间进行数据交换的重要接口,确保了数据能够在应用层和网络层之间顺利传输。当 A 为客户端,B 为服务端,那么这其实也就是我们的 C/S 模式:服务器端(Server):一台具有特定 IP 地址和端口号的计算机,运行着服务器程序。服务器的主要职责是监听网络上的连接请求,管理和维护网络资源,并为客户端提供服务。例如,在一个文件服务器中,服务器端负责存储和管理大量的文件资源,等待客户端来请求访问这些文件。客户端(Client):可以是各种不同的设备,如个人电脑、智能手机等,它们运行着客户端程序。客户端的主要功能是向服务器端发送服务请求,并接收服务器返回的响应结果。比如,用户使用手机上的移动应用(客户端)来请求查看文件服务器中的某个文件。三、实现网络计算器前的准备其中有一些本篇用到的头文件,其实是之前就写好的了,这里不带过来增加整体文章们的代码冗余了😱。(一)Socket 封装Socket 接口类:设计为模版方法类,包含创建、绑定、监听、接受连接、连接服务器、获取和设置套接字描述符、关闭套接字以及收发数据等纯虚函数。TcpSocket 类:继承自 Socket 接口类,实现具体的功能,如创建套接字、绑定地址和端口、监听、接受连接、连接服务器等,并重载了收发数据的函数。以下是 Socket.hpp 封装代码:#pragma once #include iostream #include string #include unistd.h #include sys/socket.h #include sys/types.h #include netinet/in.h #include arpa/inet.h #include cstdlib #include "Common.hpp" #include "Log.hpp" #include "InetAddr.hpp" // Socket网络编程模块命名空间 namespace SocketModule { // 定义默认监听队列长度 const static int gbacklog = 16; // 抽象基类Socket,定义网络操作的通用接口 class Socket { public: // 虚析构函数,确保正确调用派生类析构函数 virtual ~Socket() {} // 纯虚函数,定义网络操作接口 virtual void SocketOrDie() = 0; // 创建socket virtual void BindOrDie(uint16_t port) = 0; // 绑定地址 virtual void ListenOrDie(int backlog) = 0; // 开始监听 virtual std::shared_ptrSocket Accept(InetAddr *client) = 0; // 接受连接 virtual void Close() = 0; // 关闭socket virtual int Recv(std::string *out) = 0; // 接收数据 virtual int Send(const std::string message) = 0; // 发送数据 virtual int Connect(const std::string server_ip, uint16_t port) = 0; // 建立连接 public: // 模版方法,定义TCP服务器创建流程 void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog) { SocketOrDie(); // 创建socket BindOrDie(port); // 绑定地址 ListenOrDie(backlog); // 开始监听 } // 模版方法,定义TCP客户端创建流程 void BuildTcpClientSocketMethod() { SocketOrDie(); // 创建socket } }; // 定义默认的无效文件描述符 const static int defaultfd = -1; // TcpSocket类,继承自Socket基类,实现TCP协议相关功能 class TcpSocket : public Socket { public: // 默认构造函数,初始化socket文件描述符为无效值 TcpSocket() : _sockfd(defaultfd) { } // 带参数构造函数,从已有的文件描述符创建TcpSocket对象 TcpSocket(int fd) : _sockfd(fd) { } // 析构函数 ~TcpSocket() {} // 实现Socket基类的纯虚函数 // 创建socket void SocketOrDie() override { // 使用socket系统调用创建一个TCP类型的socket _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd 0) { // 创建失败,记录日志并退出 LOG(LogLevel::FATAL) "socket error"; exit(SOCKET_ERR); } // 创建成功,记录日志 LOG(LogLevel::INFO) "socket success"; } // 绑定地址 void BindOrDie(uint16_t port) override { // 创建本地地址对象 InetAddr localaddr(port); // 使用bind系统调用将socket与本地地址绑定 int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen()); if (n 0) { // 绑定失败,记录日志并退出 LOG(LogLevel::FATAL) "bind error"; exit(BIND_ERR); } // 绑定成功,记录日志 LOG(LogLevel::INFO) "bind success"; } // 开始监听 void ListenOrDie(int backlog) override { // 使用listen系统调用使

相关新闻

如何用mockery解决复杂接口的Mock问题:Go开发者的终极指南

如何用mockery解决复杂接口的Mock问题:Go开发者的终极指南

如何用mockery解决复杂接口的Mock问题:Go开发者的终极指南 【免费下载链接】mockery A mock code autogenerator for Go 项目地址: https://gitcode.com/gh_mirrors/moc/mockery mockery是Go语言生态中一款强大的Mock代码自动生成工具,它能帮助开…

2026/7/8 19:00:30 阅读更多 →
Flower配置热加载终极指南:无需重启实时更新监控设置

Flower配置热加载终极指南:无需重启实时更新监控设置

Flower配置热加载终极指南:无需重启实时更新监控设置 【免费下载链接】flower Real-time monitor and web admin for Celery distributed task queue 项目地址: https://gitcode.com/gh_mirrors/fl/flower Flower作为Celery分布式任务队列的实时监控和Web管理…

2026/5/17 8:54:11 阅读更多 →
KlipperScreen多语言支持:如何轻松切换界面语言

KlipperScreen多语言支持:如何轻松切换界面语言

KlipperScreen多语言支持:如何轻松切换界面语言 【免费下载链接】KlipperScreen GUI for Klipper 项目地址: https://gitcode.com/gh_mirrors/kl/KlipperScreen KlipperScreen作为Klipper 3D打印机固件的图形用户界面,提供了全面的多语言支持功能…

2026/7/5 15:01:02 阅读更多 →

最新新闻

Java在AI应用中的工程化实战:RAG与Agent生产落地指南

Java在AI应用中的工程化实战:RAG与Agent生产落地指南

1. 开篇——AI应用时代,Java还能打吗?这个问题我去年在三个不同城市的Java技术沙龙上都被问过,每次提问的都不是刚毕业的学生,而是工作5年以上的后端工程师,有人已经带团队做Spring Cloud微服务三年,有人正…

2026/7/8 18:57:53 阅读更多 →
落地型中医AI智能体与纯理论LLM方案对比研究——基于两篇CSDN技术博文的实证辨析

落地型中医AI智能体与纯理论LLM方案对比研究——基于两篇CSDN技术博文的实证辨析

摘要 在中医药数字化转型浪潮下,AI中医智能体形成工程落地路线与纯理论算法路线两条发展分支,大量LLM中医方案仅停留在算法框架设计,缺失临床硬件、医疗合规、真实场景验证,陷入“纸上谈兵”困境。本文选取两篇代表性CSDN技术文献…

2026/7/8 18:53:50 阅读更多 →
GitHub + Trae:构建可编程的技能操作系统

GitHub + Trae:构建可编程的技能操作系统

1. 项目概述:把 GitHub 变成你专属的 Skill 操作系统最近在调试一个本地 AI 编程助手时,我随手把几个常用代码片段打包成.md文件,丢进 GitHub 仓库,再用trae命令行工具一注册——不到 12 秒,这些零散文档就自动变成可被…

2026/7/8 18:53:50 阅读更多 →
umy-ui:Vue大数据表格的虚拟滚动与高性能渲染机制解析

umy-ui:Vue大数据表格的虚拟滚动与高性能渲染机制解析

umy-ui:Vue大数据表格的虚拟滚动与高性能渲染机制解析 【免费下载链接】umy-ui A desktop component library based on Vue 2.0 prepared for developers 项目地址: https://gitcode.com/gh_mirrors/umy/umy-ui umy-ui作为基于Vue 2.0的桌面端组件库&#xf…

2026/7/8 18:53:50 阅读更多 →
Codex CLI全链路实战:离线安装、中文配置与DeepSeek适配

Codex CLI全链路实战:离线安装、中文配置与DeepSeek适配

1. 这不是“Codex教程”,而是你真正需要的 Codex 全链路实践手册Codex 这个词最近在开发者圈子里反复刷屏,但很多人点开搜索结果后发现:要么是零散的几行命令截图,要么是英文文档的机械翻译,再或者干脆是把 GitHub REA…

2026/7/8 18:51:49 阅读更多 →
两款AI PPT工具实测:一个卡住了,一个3分钟出片

两款AI PPT工具实测:一个卡住了,一个3分钟出片

AI 生成 PPT 的工具越来越多,Kimi 是其中知名度比较高的一款。最近 RaaS100 社区平台也上线了 AiPPT,同样主打一键生成演示文稿。 两款工具放在一起,到底谁更实用? 我把同一份文档——"2026年企业AI落地趋势"&#xf…

2026/7/8 18:47:48 阅读更多 →

日新闻

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: …

2026/7/8 0:00:48 阅读更多 →
MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N1 到批处理的全路径 一、从"功能正确"到"性能可接受"——MyBatis 批量操作的三段式进化 MyBatis 在日常增删改查场景中几乎是无感的——实体映射直观、SQL 控制灵活。但当数据量从千级上升到十万级、百万级,许…

2026/7/8 0:00:48 阅读更多 →
工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

1. 工业负载控制方案概述在工业自动化、电机驱动和照明控制等高需求场景中,可靠地控制电感和电阻负载是核心挑战之一。TPD2015FN作为东芝的8通道高端智能功率开关IC,配合PIC18F45K22微控制器,能够构建一套稳定、高效的负载控制系统。这套组合…

2026/7/8 0:02:48 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/8 16:14:06 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/7 12:34:47 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/8 16:59:55 阅读更多 →

月新闻