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

文章目录

  • 再谈协议
  • 序列化和反序列化
  • 理解 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.mzph.cn/web/50541.shtml

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

相关文章

关于P2P(点对点)

P2P 是一种客户端与客户端之间&#xff0c;点对点连接的技术&#xff0c;在早前的客户端都是公网IP&#xff0c;没有NAT的情况下&#xff0c;P2P是较为容易实现的。 但现在的P2P&#xff0c;实现上面会略微有一些复杂&#xff1a;需要采取UDP打洞的技术&#xff0c;但UDP打出来…

asp.net mvc 三层架构开发商城系统需要前台页面代完善

一般会后端开发&#xff0c;都不太想写前台界面&#xff0c;这套系统做完本来想开源&#xff0c;需要前台界面&#xff0c;后台已开发&#xff0c;有需求的朋友&#xff0c;可以开发个前端界面完善一下&#xff0c;有的话可以私聊发给我啊

Redis(三)

1. java连接redis java提高连接redis的方式jedis. 我们需要遵循jedis协议。 引入依赖 <!--引入java连接redis的驱动--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version&g…

Android Framework 之AMS

它管理了系统的四大组件:Activity、Service、ContentProvider、Broadcast。 它除了管理四大组件外&#xff0c;同时也负责管理和调度所有的进程 AMS相关目录结构 AMS代码主要在下面几个目录(AndroidQ上AMS相关部分功能移到了wm下)&#xff1a; frameworks/base/core/java/andro…

记录|LabVIEW从0开始

目录 前言一、表达式节点和公式节点二、脚本与公式2.1 公式 三、Excel表格3.1 位置3.2 案例&#xff1a;波形值存入Excel表中3.3 案例&#xff1a;行写入&#xff0c;列写入 四、时间格式化4.1 获取当前时间4.2 对当前时间进行格式化 更新时间 前言 参考视频&#xff1a; LabVI…

【STL】之 vector 使用方法及模拟实现

前言&#xff1a; 本文主要讲在C STL库中vector容器的使用方法和底层的模拟实现~ 成员变量的定义&#xff1a; 对于vector容器&#xff0c;我们首先采用三个成员变量去进行定义&#xff0c;分别是&#xff1a; private:iterator _start; // 指向数据块的开始iterator _finish…

React类组件生命周期与this关键字

类组件生命周期 参考链接 一图胜千言&#xff08;不常用的生命周期函数已隐藏&#xff09; 代码&#xff1a; //CC1.js import { Component } from "react";export default class CC1 extends Component {constructor(props) {super(props);console.log("con…

【Vue3】watchEffect

【Vue3】watchEffect 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文…

C++初学(7)

7.1、字符串 字符串是存储在内存的连续字节中的一系列字符。C处理字符串的方式有两种&#xff0c;第一种是来自C语言&#xff0c;常被称为C风格字符串&#xff0c;另一种则是基于string类库的方法。 存储在连续字节中的一系列字符意味着可以将字符存储在char数组中&#xff0…

微信小程序开发 快速学习 这篇就够了

目录 一、配置篇 &#xff08;1&#xff09;官网链接&#xff1a; &#xff08;2&#xff09;项目分析 &#xff08;3&#xff09;调试器 &#xff08;4&#xff09;预览体验 &#xff08;5&#xff09;配置文件 &#xff08;6&#xff09;配置pages &#xff08;7&…

CSRF Token 原理

CSRF 攻击 CSRF 攻击成功的关键是&#xff0c;恶意网站让浏览器自动发起一个请求&#xff0c;这个请求会自动携带 cookie &#xff0c;正常网站拿到 cookie 后会认为这是正常用户&#xff0c;就允许请求。 防范 如果在请求中加一个字段&#xff08;CSRF Token&#xff09;&am…

鸿蒙开发—黑马云音乐之Music页面

目录 1.外层容器效果 2.信息区-发光效果 3.信息区-内容布局 4.播放列表布局 5.播放列表动态化 6.模拟器运行并配置权限 效果&#xff1a; 1.外层容器效果 Entry Component export struct MuiscPage {build() {Column() {// 信息区域Column() {}.width(100%)// .backgroun…

kubernetes管理GUI工具Lens

从github上可以知道&#xff0c;lens的前端是用electron做的客户端工具&#xff0c;打开安装路径你会发现kubectl.exe,没错&#xff0c;就是你经常用的kubectl命令行的客户端工具。kubectl本来就能输出json的数据类型&#xff0c;集成前端更方便了。看到这里你是不是发现&#…

Linux云计算 |【第二阶段】AUTOMATION-DAY5

主要内容&#xff1a; YAML语法格式&#xff0c;层级关系、Ansible Playbook文件及语法格式、Ansible变量&#xff08;定义变量方法、优先级顺序、setup和debug查看变量&#xff09; 补充&#xff1a;Ansible ad-hoc 可以通过命令行形式远程管理其他主机&#xff0c;适合执行一…

视频逐帧播放查看神器-android闪黑闪白等分析辅助工具

背景 刚好有学员朋友在群里问道有没有什么播放软件可以实现对视频的逐帧即一帧一帧播放。在做android系统开发时候经常会偶尔遇到有时候是闪黑&#xff0c;闪白等一瞬间现象的问题。这类问题要分析的话就不得不需要对设备录屏&#xff0c;然后对录屏进行逐帧播放查看现象&…

2020真题-架构师案例(五)

问题1&#xff08;13分&#xff09; 针对该系统的功能&#xff0c;孪工建议采用管道-过滤器&#xff08;pipe and filter&#xff09;的架构风格&#xff0c;而王工则建议采用仓库&#xff08;reposilory&#xff09;架构风格。满指出该系统更适合采用哪种架构风格&#xff0c…

【C++题解】1581. 马里奥的银币1

问题&#xff1a;1581. 马里奥的银币1 类型&#xff1a;数组找数 题目描述&#xff1a; 马里奥有很多银币&#xff0c;有一天他得到了一张魔法卡&#xff0c;只要使用这张魔法卡&#xff0c;就可以使得他的银币里面的最大的银币金额变得更大。如果他最大的银币是偶数的金额&a…

获取正版免费的xshell

1&#xff0c;安装 xshell官网 打开xshell官网站点&#xff1a;NetSarang Homepage CN - NetSarang Website 请认准&#xff0c;百度的xshell中文网都是要收费的 1&#xff0c;点击 xshell的下载 点击进入xshell的下载页面&#xff0c;或者直接访问所有下载 - NetSarang Webs…

实验2-4-2 求N分之一序列前N项和**注意小细节

//实验2-4-2 求N分之一序列前N项和//计算序列 1 1/2 1/3 ... 的前N项之和。#include<stdio.h> #include<math.h> int main(){int N;double sum0.0;scanf("%d",&N);for(int a1;a<N;a)sum(1.0/a);//这里必须是1.0 不可以是1&#xff01;&#x…

VirtualBox创建共享磁盘

VirtualBox创建共享磁盘 目录 VirtualBox创建共享磁盘1、划分共享磁盘1.1、【管理】->【工具】->【虚拟介质管理】1.2、【创建】->【VDI&#xff08;VirtualBox 磁盘映像&#xff09;】->【下一步】1.3、【预先分配全部空间】->【下一步】1.4、【分配大小】->…