【网络】协议的定制与Json序列化和反序列化

文章目录

  • 应用层
  • 初识TCP协议通讯流程
  • 定制协议
    • 再谈协议
    • 网络版本计算器
      • Protocal.hpp
      • CalServer
      • CalClient
  • Json的安装

应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层

初识TCP协议通讯流程

  • 建立链接和断开链接

基于TCP协议,我们需要知道写代码时对应的接口大概在TCP通讯的过程中属于什么样的时间点角色,在TCP协议时详谈。三次握手,四次挥手

listen状态:准备好了,可以进行链接,accept:获取链接,不是创造链接,链接已经在底层创建好了,在应用层调用accept把链接拿上来

connect:1.发起链接请求2.绑定套接字;建立链接,在底层向服务端建立链接请求,在TCP中,采用链接的方案是三次握手的方案,connect会发起三次握手,发起链接请求和真正的建立链接是两码事,建立链接由双方OS自动完成的,为什么自动完成?网络分层中,下三层是OS内部的,用户感知不到。通过客户端调用connect让OS来帮我们把三次握手的工作做完。

而accept是获取链接,链接是已经建立好了的,所以accept并不参与三次握手的任何细节,accept一定是在获取链接前别人把链接做完,既链接建立完。三次握手是OS自己完成的,connect只是发起,accept只是收尾。即使上层不调用accept,三次握手也是能够建立好的。

TCP保证可靠性不是write和read有关系的,由双方OS完成的,后面详谈。

建立链接后面就要断开链接,所以UDP由于不需要建立链接,自然不需要谈论断开链接

而四次挥手的工作都是由双方的OS完成,而我们决定什么时候分手一旦调用系统调用close,用户层就不用管了。

  • 理解链接

谈男女朋友时,都会表达自己的爱意,一定有一方主动发起链接,无论如何表达,双方看对眼的概率是极低的。而主动发起链接,是怎么发起的呢?首先,男方先表白,然后女方在做表态,什么时候在一起?男方回答就现在。这就是双方三次握手成功。(虽然现实生活中被拒绝是常态)

建立链接究竟在干什么:记下一些东西

  • 什么是建立链接

所谓的建立链接,三次握手根本就是手段,不是目的,为了达到让双方都能记住这一套,一个服务端链接客户端,很多客户端来链接了,意味着很多的客户端来了,OS应该区分清楚,需要把链接管理起来,先描述在组织,需要创建对应的链接数据结构,把所有的链接描述起来,在对其进行管理。所谓的链接就是OS内部创建的链接结构体,包含了在建立链接时对应的属性信息。当有新的链接进来时,每到来一个链接,服务端会构建一个链接对象 ,将所有的链接对象在内部中用特定的数据结构管理起来。这就是链接的建模过程。维护链接是需要成本的。占用内存资源,要用对象进行管理。

断开链接需要四次挥手,断开链接的最终目的毫无疑问就是把建立好的链接信息释放。四次挥手理解:

男女朋友处的非常好,走到了婚姻的殿堂,但是被现实打败了,过不下去啦。然后一方提出离婚,但是你自己说了不算,另一方说好啊,过了一会,对象又说离就离,那我也要离,那么你一看,我也OK。所以断开链接是双方的事情,必须得征求双方的意见。双方在协商,TCP要保证可靠性,你说的话要保证你也听到了,我也知道了,反之也一样。这就是传说中的四次挥手

TCP与UDP对比

可靠传输VS不可靠传输

有连接VS无连接

字节流VS数据报

定制协议

应用层协议的定制

再谈协议

协议是一种约定,socket api的接口,在读写数据时,都是按照字符串的方式来接收的,如果要传输一些”结构化的数据“怎么办呢?

结构化的数据:群里说话的时候除了消息本身,还有头像,昵称时间等等信息 。但是不是一个一个独立的个体,你需要做的把这些消息形成一个报文——打包成一个字符串。

由多变一这个过程就是序列化。经过网络传输后,收到的是一个报文,收到一个报文要的是什么?把一个字符串变成多个字符串,这个过程是反序列化

业务数据发送到网络的时候,先序列化发送,收到的是序列字节流,要先进行反序列化, 然后才能使用

业务协议就是结构体,这样说还是不够的,所以我们要手写一个协议。

应用场景:形成字符串对方收到,收到之后上层来不及接收,对方又发一个,有可能一次全读的,上层如何保证收到的是一个报文?

tcp这里怎么保证收到一个完整的报文

理解业务协议,理解序列化和反序列化。

网络版本计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加减乘除数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端

TCP是面向字节流的,所以明确报文和报文的边界:

image-20230521235914835

TCP是全双工的,如果接收方来不及读,那接收缓冲区就会存在很多数据,读的时候怎么怎么保证读到一个完整的报文:

1.定长2.特殊符号3.自描述方式

序列化、反序列化与定制协议是两码事,是不同阶段的事情,定制协议:报头+有效载荷

Protocal.hpp

自定义协议:

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)

请求和响应:Request,Response

Request:x,y,op(“x op y”)x和y是数据,op是操作符,比如1+2

Response:设置了退出码exitcode和结果result()

对请求和响应添加报头,这里设置的报头是长度,enLength(即添加大小,转化成字符串),也就是封装了enLength函数:

//"x op y"->"content_len"\r\n"x op y"\r\n
//"exitcode result"->"cotent_len"\r\n"exitcode result"\r\n
const std::string enLength(const std::string &text)
{std::string send_string = std::to_string(text.size());send_string += LINE_SEP;send_string += text;send_string += LINE_SEP;return send_string;
}

对请求和响应提取报文,只要报文,不要报头,也就是封装了deLength函数:

//"cotent_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{auto pos = package.find(LINE_SEP);if (pos == std::string::npos)return false;std::string text_len_string = package.substr(0, pos);int text_len = std::stoi(text_len_string);*text = package.substr(pos + LINE_SEP_LEN, text_len);return true;
}

对请求和响应进行序列化和反序列化:对于序列化和反序列化我们可以用Json来进行实现

序列化过程:结构化数据->“x op y”

反序列化过程:“x op y”->结构化数据

Protocal.hpp还提供了recvPackage函数

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
enum
{OK = 0,DIV_ZERO,MOD_ZERO,OP_ERROR
};//"x op y" --->"content_len"\r\n"x op y"\r\n,添加报头
std::string enLength(const std::string &text)
{std::string send_string = std::to_string(text.size());send_string += LINE_SEP;send_string += text;send_string += LINE_SEP;return send_string;
}//"content_len"\r\n"exitcode result"\r\n
// 去掉报头,得到"exitcode result"
bool deLength(const std::string &package, std::string *text)
{auto pos = package.find(LINE_SEP);if (pos == std::string::npos)return false;std::string text_len_string = package.substr(0, pos); // content_len:如“14”int text_len = std::stoi(text_len_string);*text = package.substr(pos + LINE_SEP_LEN, text_len);return true;
}class Request
{
public:Request() : x(0), y(0), op(0){}Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_){}// 序列化:// 结构化-> "x op y"bool serialize(std::string *out){
#ifdef MYSELF*out = "";std::string x_string = std::to_string(x);std::string y_string = std::to_string(y);*out = x_string;*out += SEP;*out += op;*out += SEP;*out += y_string;
#elseJson::Value root;root["first"] = x;root["second"] = y;root["oper"] = op;Json::FastWriter writer;*out = writer.write(root);
#endifreturn true;}// 反序列化化://"x op y"->结构化bool deserialize(const std::string &in){
#ifdef MYSELFauto left = in.find(SEP);auto right = in.rfind(SEP);if (left == std::string::npos || right == std::string::npos)return false;if (left == right)return false;if (right - (left + SEP_LEN) != 1)return false;std::string x_string = in.substr(0, left);std::string y_string = in.substr(right + SEP_LEN);if (x_string.empty())return false;if (y_string.empty())return false;x = std::stoi(x_string);y = std::stoi(y_string);op = in[left + SEP_LEN];
#elseJson::Value root;Json::Reader reader;reader.parse(in, root);x = root["first"].asInt();y = root["second"].asInt();op = root["oper"].asInt();
#endifreturn true;}public:int x;int y;char op;
};class Response
{
public:Response() : exitcode(0), result(0){}Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_){}bool serialize(std::string *out){
#ifdef MYSELF*out = "";std::string ec_string = std::to_string(exitcode);std::string res_string = std::to_string(result);*out = ec_string;*out += SEP;*out += res_string;
#elseJson::Value root;root["exitcode"] = exitcode;root["result"] = result;Json::FastWriter writer;*out = writer.write(root);
#endifreturn true;}bool deserialize(const std::string &in){
#ifdef MYSELFauto mid = in.find(SEP);if (mid == std::string::npos)return false;std::string ec_string = in.substr(0, mid);std::string res_string = in.substr(mid + SEP_LEN);if (ec_string.empty() || res_string.empty())return false;exitcode = std::stoi(ec_string);result = std::stoi(res_string);
#elseJson::Value root;Json::Reader reader;reader.parse(in, root);exitcode = root["exitcode"].asInt();result = root["result"].asInt();
#endifreturn true;}public:int exitcode;int result;
};// 读取报文,保证读取的是一个完整的报文 ,inbuffer由外部传入
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{char buffer[1024];while(true){ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);if(n>0){buffer[n] = 0;inbuffer+=buffer;auto pos = inbuffer.find(LINE_SEP);if(pos == std::string::npos) continue;std::string text_len_string = inbuffer.substr(0,pos);int text_len  =std::stoi(text_len_string);int total_len = text_len_string.size()+2*LINE_SEP_LEN+text_len;std::cout<<"处理前#inbuffer:\n"<<inbuffer<<std::endl;if(inbuffer.size()< total_len){std::cout<<"你输入的消息,没有遵守所定制的协议,正在等待后续的内容,continue"<<std::endl;continue;}//至少是一个完整的报文*text = inbuffer.substr(0,total_len);inbuffer.erase(0,total_len);std::cout<<"处理后#inbuffer:\n"<<inbuffer<<std::endl;break;}elsereturn false;}return true;
}

对于recvPackage函数我们要保证读到的至少是一个完整的报文

CalServer

服务端代码

//CalServer.hpp
namespace server
{enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5;typedef std::function<bool(const Request &req, Response &resp)> func_t;void handlerEntery(int sock,func_t func){std::string inbuffer;while(true){//1.读取:"content_len"\r\n"x op y"\r\n//保证读到的消息是一个完整的请求std::string req_text,req_str;if(!recvPackage(sock,inbuffer,&req_text)) return;std::cout<<"带报头的请求:\n"<<req_text<<std::endl;//去掉报头if(!deLength(req_text,&req_str)) return;std::cout<<"去掉报头的正文:\n"<<req_str<<std::endl;//2.对请求Request,反序列化//2.1得到一个结构化的请求对象Request req;if(!req.deserialize(req_str)) return;//3.计算机处理————业务逻辑Response resp;func(req,resp);//4.对响应Response,进行序列化//4.1得到一个"字符串"std::string resp_str;resp.serialize(&resp_str);std::cout<<"计算完成,序列化响应:"<<resp_str<<std::endl;//5.发送响应std::string send_string = enLength(resp_str);std::cout<<"构建完成完整的响应\n"<<send_string<<std::endl;send(sock,send_string.c_str(),send_string.size(),0);}}class CalServer{public:CalServer(const uint16_t&port = gport):_listensock(-1),_port(port){}void initServer(){_listensock = socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){logMessage(FATAL,"create socket error");exit(SOCKET_ERR);}logMessage(NORMAL,"create socket success:%d",_listensock);struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0){logMessage(FATAL,"bind socket error");exit(BIND_ERR);}logMessage(NORMAL,"bind socket success");if(listen(_listensock,gbacklog)<0){logMessage(FATAL,"listen socker error");exit(LISTEN_ERR);}logMessage(NORMAL,"listen socket success");}void start(func_t func){for(;;){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock,(struct sockaddr*)&peer,&len);if(sock<0){logMessage(ERROR,"accept error,next");continue;}logMessage(NORMAL,"accept a new link success,get new sock:%d",sock);pid_t id = fork();if(id == 0){close(_listensock);handlerEntery(sock,func);close(sock);exit(0);}close(sock);pid_t ret = waitpid(id,nullptr,0);if(ret>0){logMessage(NORMAL,"wait child success");}}}~CalServer() {}public:int _listensock;uint16_t _port;};
}//CalServer.cc
#include "calServer.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}// req是处理好的完整的请求对象
// resp:根据req进行业务处理,填充resp,不需要管理任何IO,序列化和反序列化
bool cal(const Request &req, Response &resp)
{// req已经有结构化的数据resp.exitcode = OK;resp.result = 0;switch (req.op){case '+':resp.result = req.x+req.y;break;case '-':resp.result = req.x-req.y;break;case '*':resp.result = req.x*req.y;break;case '/':{if(req.y==0) resp.exitcode = DIV_ZERO;else resp.result = req.x/req.y;}break;case '%':{if(req.y == 0)resp.exitcode = MOD_ZERO;else resp.result = req.x%req.y;}break;default:resp.exitcode = OP_ERROR;break;}return true;
}// tcp服务器,启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<CalServer> tsvr(new CalServer(port));tsvr->initServer();tsvr->start(cal);return 0;
}

CalClient

ParseLine:解析,构建一个请求:“1+1”

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Protocol.hpp"
#define NUM 1024class CalClient
{
public:CalClient(const std::string &serverip, const uint16_t &serverport): _sock(-1), _serverip(serverip), _serverport(serverport){}void initClient(){// 1. 创建socket_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){std::cerr << "socket create error" << std::endl;exit(2);}}void start(){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(_serverport);server.sin_addr.s_addr = inet_addr(_serverip.c_str());if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0){std::cerr << "socket connect error" << std::endl;}else{std::string line;std::string inbuffer;while (true){std::cout << "mycal>>> ";std::getline(std::cin, line);  // 1+1Request req = ParseLine(line); // "1+1"std::string content;req.serialize(&content);std::string send_string = enLength(content);send(_sock, send_string.c_str(), send_string.size(), 0); // bug?? 不管std::string package, text;//  "content_len"\r\n"exitcode result"\r\nif (!recvPackage(_sock, inbuffer, &package))continue;if (!deLength(package, &text))continue;// "exitcode result"Response resp;resp.deserialize(text);std::cout << "exitCode: " << resp.exitcode << std::endl;std::cout << "result: " << resp.result << std::endl;}}}Request ParseLine(const std::string &line){// 建议版本的状态机!//"1+1" "123*456" "12/0"int status = 0; // 0:操作符之前,1:碰到了操作符 2:操作符之后int i = 0;int cnt = line.size();std::string left, right;char op;while (i < cnt){switch (status){case 0:{if(!isdigit(line[i])){op = line[i];status = 1;}else left.push_back(line[i++]);}break;case 1:i++;status = 2;break;case 2:right.push_back(line[i++]);break;}}std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;return Request(std::stoi(left), std::stoi(right), op);}~CalClient(){if (_sock >= 0)close(_sock);}private:int _sock;std::string _serverip;uint16_t _serverport;
};#include "calClient.hpp"
#include <memory>using namespace std;
static void Usage(string proc)
{cout<<"\nUasge:\n\t"<<proc<<" serverip serverport\n\n";
}
int main(int argc,char*argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);unique_ptr<CalClient> tcli(new CalClient(serverip,serverport));tcli->initClient();tcli->start();return 0;
}

Json的安装

sudo yum install -y jsoncpp-devel

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

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

相关文章

[Unity][疑难杂症]Mac电脑打开工程unity attempt to write a readonly database

报错的根本原因&#xff1a; 你Unity项目所在的硬盘&#xff0c;格式不是MacOS或者APFS格式&#xff0c;我的是ExFAT所以报这个错。 可能出现的问题&#xff1a; 1、删除unity项目中的c#之类的文件就会报错&#xff0c;并且强制退出 2、新项目压根打不开&#xff0c;在生成资源…

图片:前端展示图像(img 、picture、svg、canvas )及常用图片格式(PNG、JPG、JPEG、WebP、GIF、SVG、AVIF等)

一、浏览器网页展示图片方法 1.1、HTML <img> 标签 <!DOCTYPE html> <html><head><title>图片展示</title></head><body><h1>图片展示</h1><img src"example.jpg" alt"Example Image" w…

IPO观察丨健身器材行业加“数”前行,康力源如何重构竞争壁垒?

近年来&#xff0c;健身器材市场的火热&#xff0c;引来了资本的广泛关注。数据显示&#xff0c;2021年中国健身器材市场规模为546.5亿元&#xff0c;同比增长15%&#xff0c;预计2024年或可达到799.6亿元。在这股浪潮之下&#xff0c;多家健身器材企业递出了IPO申请。比如&…

合同数智化如何助力地产企业实现变革“突围”?

从稳步发展到求新求变&#xff0c; 数智化成破局关键 近年来&#xff0c;随着宏观经济政策调整&#xff0c;在中央房住不炒的大基调下&#xff0c;房地产逐步回归居住属性。在这样的大背景下&#xff0c;针对不同类型的房地产企业&#xff0c;国家出台了不同的数字化转型指导文…

Linux——1初识linux

目录 1.1 硬件和软件 1.2 初识Linux 1.2.1 Linux的诞生 1.2.2 LInux内核 1.2.3 Linux发行版 1.3 虚拟机介绍 1.4 VMware WorkStation 安装 1.5 在VMware上安装Linux 1.6 远程连接Linux系统 1.6.1 图形化、命令行 1.6.2 FinalShell 1.1 硬件和软件 我们所熟知的计算…

基于安卓的初中英语语法APP设计与实现(源码+文档+PPT)

智能手机的普及使移动学习成为一种全新的学习方式,越来越受到研究者与学习者的关注。调查显示,移动学习对英语学习特别是应用英语的学习有良好的效果。基于Android设计开发的英语语法APP包含“练习题册”、“语法分析”、“搜索语法”三大功能模块,能够帮助学生利用碎片时间学习…

发起投票平台投票吧网络投票平台网络投票平台

小程序投票活动如何做&#xff1f;很多企业在运营当中&#xff0c;都会通过投票活动来进行推广&#xff0c;从而达到吸粉、增加用户粘度等效果。而此类投票活动&#xff0c;通过小程序就可以实现&#xff0c;操作简单。 我们现在要以“促进诚信经营”为主题进行一次投票活动&am…

rust abc(5): 常量

文章目录 1. 目的2. 基本用法2.1 说明2.2 运行结果 3. 不推荐或不正确用法3.1 不推荐用小写字母作为常量名字3.2 常量名称中含有小写字母就会报warning3.3 定义常量时&#xff0c;不指定数据类型会编译报错 4. const 和 immutable 的区别4.1 const 可以在函数外声明&#xff0c…

2022前端趋势报告(上)

前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 一、前言 本文内容来自于《St…

MySQL 高级(进阶) SQL 语句

目录 创建两个表格 location 表格​编辑 store_info 表格​编辑 ---- SELECT ---- ---- DISTINCT ---- ---- WHERE ---- ---- AND OR ---- ---- IN ---- ---- BETWEEN ---- ---- 通配符 ---- ---- LIKE ---- ---- ORDER BY ---- ---- 函数 ---- ---- GROUP BY ---- ----…

viewLifecycleOwner.lifecycleScope生命周期,kotlin

viewLifecycleOwner.lifecycleScope生命周期&#xff0c;kotlin viewLifecycleOwner.lifecycleScope.launch {viewLifecycleOwner.whenCreated {Log.d(TAG,"onCreated")}viewLifecycleOwner.whenStarted {Log.d(TAG,"onStarted")}viewLifecycleOwner.whenR…

MySQL-SQL全部锁详解(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…