【Socket 编程】应用层自定义协议与序列化

news2024/11/22 15:53:03

文章目录

  • 再谈协议
  • 序列化和反序列化
  • 理解 read、write、recv、send 和 tcp 为什么支持全双工
  • 自定义协议网络计算器
  • 序列化和反序列化

再谈协议

协议就是约定,协议的内容就是约定好的某种结构化数据。比如,我们要实现一个网络版的计算器,客户端发送一个算术式子,服务端根据算术式子计算出结果,再把结果返回给客户端。在此之前,客户端和服务端可以做出如下两种方案的约定

  • 方案一:
    • 客户端发送一个形如“1+1"的字符串
    • 这个字符串中有两个操作数,都是整型
    • 两个数字之间会有一个字符是运算符,运算符只能是+
    • 数字和运算符之间没有空格
    • 等等
  • 方案二:
    • 定义一个结构体表示一个请求信息,信息内容包括两个整数,和一个操作符
    • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
    • 定义一个结构体表示一个服务器的响应,响应的信息包括,计算结果、错误码、错误信息等

为什么要做出诸如此类的约定呢? 一切交给服务器去处理不就行了吗?

做约定的本质就是为了方便读取数据和发送数据。假使客户端和服务端没有做任何约定,在我们这个网络版计算器中,客户端就有可能发送很多无效的信息,比如随便输入一段字符串。很显然,如果客户端不做任何处理直接发送给服务端,服务端虽然也能做一些手段去检测数据的合法性,但是那样太浪费时间了

所以,为了能让整个交互过程有序且高效,客户端一定要与服务端达成某种协议,即规定发送时数据的格式与接收数据的格式。这样一来,客户端发送数据时按照约定好的格式打包好再发送,客户端就不需要考虑服务端怎么读取数据了,因为已经约定好了。相反,服务端接收数据包时按照约定,正确的实施解包的步骤就能读取到正确的数据,因为已经约定好了,客户端发送过来的数据一定是按照约定的格式存储的。
这就是约定,这就是协议

序列化和反序列化

在上面我们谈到,协议是客户端与服务端约定好的某种存储数据的结构,说白了就是一个特定的结构体。但是无论是发送请求还是发送响应,都是需要通过网络来传输的,具体地说,在将传输给网络之前,我们要把上述包含数据的结构体进一步转换成可供网络传输的格式。同样的,从网络读取到数据后,在应用层还需要将其转换回来。前者就叫序列化,后者叫反序列化
简单理解,序列化,就是网络化,就是把数据转换成某种在网络中传输的格式。反序列化,就是逆序列化过程

其中Jsoncpp库中提供了一些供序列化和反序列化的方法。

  • 创建JSON对象
Json::Value root;
  • 设置值
root["name"] = "John Doe";
root["age"] = 30;
  • 访问值
td::string name = root["name"].asString();
int age = root["age"].asInt();
  • 将数据序列化为字符串:
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);

理解 read、write、recv、send 和 tcp 为什么支持全双工

在这里插入图片描述

  • 在每台主机上,TCP连接维护了两个缓冲区,分别用来发送数据和读取数据。所以发送数据和读取数据可以同时进行。因为这俩操作不是在同一个缓冲区进行的
  • sockfd描述符本质就是一个文件描述符,但是它对应着读写两个缓冲区,操作系统会自动区分读和写操作,所以tcp通过一个sockfd就能进行读和写两种操作
  • 实际数据什么时候发,发多少,处理错误,由TCP协议控制,所以TCP叫做传输控制协议。

自定义协议网络计算器

根据前面的学习,我们可以自己简单模式一个用户层的协议,来实现网络版的计算器。
对于服务端,事件流程如下:

  • 创建套接字
  • 进入监听状态
  • 获取连接
  • 获取请求,将其反序列化,得到一个包含客户端数据的结构体
  • 处理业务
  • 将应答消息序列化,得到一个字符串,发送给客户端
  • 上述步骤重复执行
  • 关闭连接,关闭套接字

对于客户端,事件如下:

  • 创建套接字
  • 连接服务端
  • 将序列化后的数据打包成一个请求发送给服务端
  • 获取服务端的应答,将应答反序列化获取结果
  • 关闭连接,关闭套接字

既然是自定义协议,那么我们可以规定请求和应答的数据格式为:len\r\n{json}\r\njson其实就是一个用Json库处理序列化后的一个字符串(该字符串包含有效数据),len表示的是json字符串的长度

中间的换行符用于区分字节流读取进度。假设读取到的数据流有一个换行符,说明该数据流一定包含len信息,拿到len信息后就可以再判断该数据流是否包含json字符串。

于是,在请求之前,我们需要先把有效数据转换成一个Json处理过的字符串,这叫做序列化。由于是网络中是字节流传输数据,为了辨别拿到的数据是否完整,我们还需要添加一些”标识“数据,这就是报头,整合起来就是一个报文
完整代码可以去我gitee上去获取:点击查看
下面只给出序列化和反序列化实现模块

序列化和反序列化

#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>

static const std::string sep = "\r\n";

// 报文格式 len\r\n{joson}\r\n

// 将jsonstr格式的字符串加上报头
std::string Encode(const std::string &jsonstr)
{
    int len = jsonstr.size();
    std::string lenstr = std::to_string(len);
    return lenstr + sep + jsonstr + sep;
}

// 将接收到的数据中的joson段提取出来
std::string Decode(std::string &packagestream)
{
    // 分析
    auto pos = packagestream.find(sep);
    if (pos == std::string::npos)
    {
        return std::string();
    }

    std::string lenstr = packagestream.substr(0, pos);
    int len = std::stoi(lenstr);
    // 计算一个完整的报文应该多长
    int total = lenstr.size() + 2 * sep.size() + len;
    if (packagestream.size() < total)
    {
        return std::string();
    }
    // 提取
    std::string josonstr = packagestream.substr(pos + sep.size(), len);
    packagestream.erase(0, total);
    return josonstr;
}

class Request
{

public:
    Request() {}
    ~Request() {}

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    // 序列化
    bool Serialize(std::string *out)
    {
        // 使用joson库
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;
        Json::FastWriter write;
        std::string s = write.write(root);
        *out = s;
        return true;
    }
    // 反序列化
    bool Deserialize(const std::string &josonstr)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();
        return res;
    }

    void Print()
    {
        std::cout << _x << std::endl;
        std::cout << _y << std::endl;
        std::cout << _oper << std::endl;
    }

    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }

    void SetValue(int x, int y, char oper)
    {
        _x = x;
        _y = y;
        _oper = oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

// 应答
class Response
{
public:
    ~Response() {}
    Response()
        : _result(0), _code(0), _desc("success")
    {
    }
    // 序列化
    bool Serialize(std::string *out)
    {
        // 使用joson库
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        root["desc"] = _desc;
        Json::FastWriter write;
        std::string s = write.write(root);
        *out = s;
        return true;
    }
    // 反序列化
    bool Deserialize(const std::string &josonstr)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中
        _result = root["result"].asInt();
        _code = root["code"].asInt();
        _desc = root["desc"].asString();
        return res;
    }
    void PrintResult()
    {
        std::cout << "result: " << _result << " code: " << _code << " desc: " << _desc << std::endl;
    }

    int _result;
    int _code;
    std::string _desc;
};

class Factory
{
public:
    static std::shared_ptr<Request> BuildRequestDefault()
    {
        return make_shared<Request>();
    }

    static std::shared_ptr<Response> BuildResponseDefault()
    {
        return make_shared<Response>();
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1944063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌握互联网路由选择协议:从基础入门到实战

文章目录 路由选择协议的基本概念路由选择算法的分类分层次的路由选择协议路由信息协议&#xff08;RIP&#xff09;内部网关协议&#xff1a;OSPF外部网关协议&#xff1a;BGP互联网中的实际应用总结 互联网的路由选择协议是网络通信的核心&#xff0c;它决定了数据包如何在网…

Artix7系列FPGA实现SDI视频编解码+图像缩放+多路视频拼接,基于GTP高速接口,提供4套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本博已有的FPGA图像缩放方案本博已有的已有的FPGA视频拼接叠加融合方案本方案的无缩放应用本方案在Xilinx--Kintex系列FPGA上的应用本方案在Xilinx--Zynq系列FPGA上的应用 3、详细设计方案设计原理框图S…

nodejs编译报错 集合

目录 一、使用命令编译typescript时报错&#xff0c;报错文件tsconfig.json 二、npm start运行后报错&#xff0c;could not find module 一、使用命令编译typescript时报错&#xff0c;报错文件tsconfig.json npx tsc 报错&#xff1a; Specified include paths were [&…

Layer2区块链扩容方案(1)——总述

写在前面 这篇文章作为一个简单介绍&#xff0c;很多技术只是大致提及或者引用&#xff0c;之后会在详细学习后逐项解释。 补充知识 在了解扩容方案之前&#xff0c;我们最好了解一些相关的知识概念 EVM “EVM” 是“Ethereum Virtual Machine”&#xff08;以太坊虚拟机&…

SSRF学习笔记

1.NAT学习 Nat&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是 一种网络通信技术主要用于将私有网络中的内部IP地址转换成公共网络中的公共IP地址&#xff0c;以实现局域网内部设备访问互联网的功能。具体来说&#xff0c;Nat有以下几个主要…

【Emacs有什么优点,用Emacs写程序真的比IDE更方便吗?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

分析性能提升40%,阿里云Hologres流量场景最佳实践

在互联网和移动分析时代&#xff0c;流量数据成为了企业洞察用户行为、优化产品决策和提升运营效率的关键资源。流量数据主要来源于用户在使用APP、小程序或访问网站等媒介平台时产生的各种操作行为&#xff0c;如点击、浏览、注册、下单等。这些行为数据通过数据埋点技术被采集…

Python爬虫技术 第10节 requests库

requests 是 Python 中非常流行的 HTTP 库&#xff0c;它使得发送 HTTP/1.1 请求变得简单直观。下面我会通过几个实际案例来详细介绍如何使用 requests 库。 1. 发送 GET 请求 最简单的请求类型就是 GET 请求&#xff0c;通常用于获取网页或其他资源。 import requests# 发送…

大语言模型-RetroMAE-检索预训练模型

一、背景信息&#xff1a; RetroMAE是2022年10月由北邮和华为提出的一种密集检索预训练策略。 RetroMAE主要应用于检索模型的预训练&#xff0c;模型架构为非对称的Encoder-Decode结构。 二、整体结构&#xff1a; RetroMAE的模型架构为非对称的Encoder-Decode结构。 Encod…

ArcGIS Desktop使用入门(四)——ArcMap软件彻底卸载删除干净

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

【逆向工程】十六进制编辑器与反编译的手写PE文件格式(详细教程)

原理 (1)程序如何在本地生成注册码 1.打开文件并写入MZ头部&#xff1a;打开一个二进制文件以进行写入操作。写入MZ头部&#xff0c;即前64字节&#xff0c;通常以字节序列 4D 5A 开始。 2.写入PE头部&#xff1a;PE头部紧随在MZ头部之后&#xff0c;其位置由MZ头部中的偏移…

构建稳固与安全的网络环境:从微软蓝屏事件看软件更新流程与应急响应

“微软蓝屏”事件暴露了网络安全哪些问题&#xff1f; 近日&#xff0c;由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅让科技领域为之震动&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件源于美国电脑安全技术公司“众击”的一…

GIT新手提交操作

1、创建一个本地分支 进入Xshell已经拉取的该项目的项目代码路径下执行git checkout -b 姓名全拼音&#xff0c;例如&#xff1a;git checkout -b xiewei&#xff0c;当前显示已创建。 cuihengyidell-PowerEdge-T550:~/SVN/Git_R11/R11_V4.02.0_Source$ git checkout -b cuih…

数据库之PHP联动

目录 一、软件安装 二、软件讲解 三、搭配环境 四、编辑软件配置 五、成果展示 如果有人问&#xff1a;为什么非要用xampp、VS code编辑软件?不用phpstudy等其他工具。 那么我只想说&#xff1a;因为xampp、VS code编辑软件免费(ಡωಡ)hiahiahia 一、软件安装 下载连…

S-HTTP协议:确保网络通信安全的重要基石

随着互联网技术的飞速发展&#xff0c;网络通信安全已成为一个不可忽视的重要议题。在众多保障网络通信安全的协议中&#xff0c;S-HTTP&#xff08;Secure Hypertext Transfer Protocol&#xff09;以其独特的加密和认证机制&#xff0c;成为保护数据传输安全的重要工具。本文…

SpringBoot 数据访问操作

目录 一.SpringBoot整合Mybatis与Mybatis-Plus 二.SpringBoot切换druid数据源 3.1DRUID配置参数 3.2Druid监控平台 一.SpringBoot整合Mybatis与Mybatis-Plus 步骤&#xff1a; 1.坐标 <dependency><groupId>com.baomidou</groupId><artifactId>myb…

实现自动化采购:食堂采购系统源码开发详解

本篇文章&#xff0c;笔者将详细介绍食堂采购系统的开发过程&#xff0c;从需求分析、系统设计到实现和测试&#xff0c;为您全面解析如何构建一个高效的自动化采购系统。 一、需求分析 1.采购计划管理 2.供应商管理 3.订单管理 4.库存管理 5.财务管理 6.数据分析与报告 …

Power App学习笔记以及基础项目管理demo

Power App学习笔记以及基础项目管理demo 最近学习了一点Power App&#xff0c;感觉挺有意思。配置式组件开发。浅浅记录一下自己实现的项目管理系统&#xff08;即Excel数据的增删改查&#xff09;关于函数的一点皮毛认识。 效果图 筛选数据 编辑 详情 数据源 PowerApp 网…

杰发科技Bootloader(2)—— 基于7840的Keil配置地址

序 在7840的sample代码里面有一个简单的Boot跳转APP的示例 PFlash地址从0开始 DFlash的地址从1000000开始 Boot解析 他的boot地址配置为0 Boot的代码主要是这几行&#xff0c;主要作用就是Flash的跳转 int main(void) {SystemClock_Config();InitDebug();printf("demo…

分布式搜索引擎ES-DSL搜索详解

1.DSL搜索-入门语法 建立索引&#xff1a; xxx(自定义名称) 自定义mapping: POST /shop/_mapping {"properties": {"id": {"type": "long"},"age": {"type": "integer"},"username": {&quo…