【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,一经查实,立即删除!

相关文章

【logstash】logstash使用多个子配置文件

这里有个误区在pipelines.yml中写conf.d/*&#xff0c;实测会有问题&#xff0c;不同的filter处理逻辑会复用。 现在有两个从kafka采集日志的配置文件&#xff1a;from_kafka1.conf&#xff0c;from_kafka2.conf 修改pipelines.yml配置文件 config/pipelines.yml- pipeline.i…

关于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;有的话可以私聊发给我啊

python_使用多进程来处理数据写入Excel文件_multiprocessing.Process

python_使用多进程来处理数据写入Excel文件 优势&#xff1a;与多线程相比&#xff0c;多进程写入速度要更快&#xff0c;12万多行数据处理用时3.52秒&#xff0c;比多进程快了1秒左右。 import pandas as pd from io import BytesIO import multiprocessing import time impor…

Spring源码-AOP

1、spring aop和aspectJ什么关系&#xff1f; aop是编程思想&#xff0c;spring aop被aspectJ都是aop思想的具体实现。spring aop为了不重复造轮子&#xff0c;通过一定的取舍选取了aspectJ中适合自己的注解。spring初期版本的aop只支持通过实现aop接口的方式来实现切面增强&a…

Nginx 最常用的命令

目录 一、Nginx 安装与配置 1.1 下载与安装 1.2 配置文件 二、Nginx 基本操作 2.1 启动与停止 2.2 重启与重新加载 三、日志管理 3.1 访问日志 3.2 错误日志 四、虚拟主机管理 4.1 配置虚拟主机 4.2 管理虚拟主机 五、性能优化 5.1 缓存配置 5.2 连接优化 Nginx…

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;以纪念曾经努力学习奋斗的日子。本文…

【代码随想录第37天| 完全背包,518. 零钱兑换 II ,377. 组合总和 Ⅳ,70. 爬楼梯 (进阶)】

完全背包 完全背包中&#xff0c;每件物品都有无数件&#xff1b;这主要影响了遍历背包容量时的遍历顺序&#xff0c;应该从小到大去遍历&#xff0c;这样才能包括有多件相同物品的情况。 思路 先遍历物品&#xff0c;再遍历背包 for(int i 0; i < weight.size(); i) {…

pnpm 设置国内源

pnpm config set registry https://registry.npmmirror.com/

16、DDD系列-向微服务迈进

在本章中&#xff0c;我们将探讨如何从传统的单体架构向微服务架构演进。这个过程需要考虑许多因素&#xff0c;包括微服务的驱动力、所需条件、服务粒度的确定以及系统复杂性的治理。 1、目的&#xff1a;微服务的驱动力 微服务的主要驱动力是业务需求的快速变化和系统的可扩…

[Mysql-数据库基本知识了解]

为什么学习数据库&#xff1f; 数据的保存&#xff1a; 大量程序产生的数据在程序 运行时和程序结束运行后 数据应该怎么保存&#xff1f; 数据的完整性 &#xff1a;数据和数据之间的结构关系&#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&…

vue3组件通信(二)

组件通信 一.$attrs(祖>孙间接&#xff09;二、$refs()父>子&#xff0c; $parent()子>父三.provide&#xff0c;inject(祖>孙直接&#xff09;四.pinia五.slot1.默认插槽2.具名插槽3.作用域插槽 一.$attrs(祖>孙间接&#xff09; $attrs用于实现当前组件的父组…

实习心得—20240725

在实习的过程中&#xff0c;遇到了很多问题&#xff0c;对于fpga开发来说&#xff0c;我一开始值懂得一些皮毛&#xff0c;仅限于读懂代码或者写一些简单的代码&#xff0c;当接触到一个大的项目的时候&#xff0c;我发现很多事情并不是想象中的那么容易。 一个大的项目是由许…