自定义协议 ,序列化和反序列化

目录

​编辑

一,问题引入:

 二,协议

三,自定义协议

1,协议

2, 序列化和反序列化

四,网络版本的计算器

1,协议的定制

2,计算逻辑

3,服务端

4,客户端

5,main函数


 

一,问题引入:

当我们使用TCP协议来进行网络通信时,因为TCP通信的特点:

面向字节流,没有数据边界。

所以,当我们在读取数据时就会遇到一个问题:在使用TCP通信时到底怎样才算将一段数据读取完成呢?

 二,协议

为了解决上面的问题,在网络中便引入了协议。比较著名的协议就有HTTP协议,TCP协议等等。

这些协议通俗的讲其实就是一个约定,收发数据两方的约定。两方约定好如何发数据,如何收数据。

三,自定义协议

虽然在平时使用网络通信时并不需要我们来写协议。但是,我们还是有自己定制协议的能力的。  所以,为了更好的了解协议这个东西。我们可以自己尝试来写一个协议,然后让双方互相通信。

1,协议

现在,我们就来实现一个网络版的计算器。但是我们要做如下约定:数据要以如下格式发送:

len: 代表内容content的长度

\n:代表一个分割符

content:代表数据的正文内容

\n:代表一个分割符

2, 序列化和反序列化

在QQ接收消息时,我们不仅仅会收到信息,我们还会收到头像和昵称。这些消息就是一个结构化的消息。这些消息经过打包后会形成一段报文(一个整体),打包的过程就是序列化的过程。在将这段报文解开的过程就是一个反序列化的过程。序列化和反序列化的过程中要使用的就是协议。

四,网络版本的计算器

1,协议的定制

在写这个计算器时首先确定的便是协议的定制。协议定制如下:

#include<iostream>
#include"log.hpp"const std::string blank_sep = " ";
const std::string protocol_sep = "\n";std::string Encode(std::string& content)//加密:"len\ncontent\n"
{std::string package = std::to_string(content.size());package += protocol_sep;package+=content;package += protocol_sep;return package;//返回打包后的数据
}bool Decode(std::string &package,std::string* content) // 解密一段打包后数据:"len\ncontent\n"
{//先找第一个protocol_sepint pos = package.find(protocol_sep);if(pos == std::string::npos){// lg(Debug,"Decode err1");return false;}//找到了第一个protocol_sep,找到这段数据的长度std::string len_str = package.substr(0, pos);//std::cout << len_str << std::endl;int len = std::atoi(len_str.c_str());int total_len = len_str.size()+2+len;if(package.size()<total_len){// lg(Debug, "Decode err2");return false;}*content = package.substr(pos+1,len);package.erase(0,total_len);//解码后将package消掉return true;
}//定制协议
class Request//
{
public:
Request()
:x(0),y(0),op('+')
{}Request(int data1,int data2,char oper)
:x(data1),y(data2),op(oper)
{}//序列化:将结构化的数据变成字符串(x op y),并带出到外面
void  Serialize(std::string* content)
{std::string str = std::to_string(x);str += blank_sep;str += op;str+= blank_sep;str += std::to_string(y);*content = str;
}bool Deserialize(std::string &content) // 将字符串转化为结构化的数据
{int pos = content.find(blank_sep);if(pos == std::string :: npos){//lg(Debug,"Request Deserilization err1");return false;}x = std::stoi(content.substr(0, pos));op = content[pos + 1];pos = content.rfind(blank_sep);if (pos == std::string ::npos){// lg(Debug, "Request Deserilization err2");return false;}y = std::stoi(content.substr(pos+1));return true;
}public:int x;int y;char op;
};class Response
{public:Response():result(0),code(0){}Response(int res,int c): result(res), code(c){}void Serialize(std::string* content)//序列化:str:"result code"{std::string str;str += std::to_string(result);str += blank_sep;str+=std::to_string(code);*content = str;}bool Deserialize(std::string &content){int pos = content.find(blank_sep);if(pos == std::string::npos){//lg(Debug, "Response Deserilization err");return false;}result = std::stoi(content.substr(0, pos));code = std::stoi(content.substr(pos + 1));return false;}void Debugprint(){std::cout <<"result: "<< result << " "<<"code: " << code << std::endl;}public:int result;//结果int code;//0表示结果正确,!0表示结果错误
};

在这段代码中,我定义了两个类:

 Request:代表一个请求。这个类里面有两个方法,代表着序列化和反序列化方法。 类里面有三个成员:x y op,代表着左右操作数和操作符。

 Response:代表一个响应。这个类里面有两个方法,代表着序列化和反序列化方法。 类里面有三个成员:result  code,代表着结果和结果码(显示结果可不可信)。

在这段代码中还有两个公共的方法:

Encode:对序列化后的内容进行加码,变成如下形式:

Decode:对加码后的内容进行解码,变成一个简单的反序列化的代码。 

2,计算逻辑

 在制定好协议以后,便可以开始根据这些协议来对客户发来的数据进行处理了。但是如何处理呢?因为我们这里的处理逻辑就是一个计算。所以,写出计算逻辑如下:

#pragma once
#include "Protocol.hpp"class Calculator // 计算逻辑
{
public://开始计算Response CalHelper(Request& req)//开始计算{Response resp(0, 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.result = req.x / req.y;elseresp.code = 3;break;case '%':if (req.y != 0)resp.result = req.x % req.y;elseresp.code = 3;break;default:resp.code = 4;break;}return resp;}// 对加包的数据进行计算,并且要返回序列化并打包的结果std::string Calculate(std::string &package)//要保证我的数据是一个package{std::string content;bool r = Decode(package, &content);//将package解包并且带出内容if(!r)return "";Request req;r = req.Deserialize(content);//反序列化后得到了一个结果,然后去计算if(!r)return "";Response resp;resp = CalHelper(req);//对req进行计算content = "";//序列化并打包resp.Serialize(&content);//std::string package;content = Encode(content);return content;//返回打包后的结果}
};

Calculate:对服务端接收到的消息进行处理。处理过程便是先对数据进行解包,然后再对数据进行反序列化。

 CalHelper:对解包并且反序列化后的数据进行计算。并将结果存于Response对象中返回。

3,服务端

 在写完如上代码后,我们的服务端便可以开始处理数据了。现在就让我们来搭建一个基于TCP协议的服务端。代码如下:

#pragma once
#include "log.hpp"
#include"Protocol.hpp"
#include"Socket.hpp"
#include<signal.h>
#include"Calculator.hpp"Calculator Cal;//定义一个计算器class CalServer
{
public:CalServer(uint16_t port) : port_(port){}bool Init(){listensock_.Sock();listensock_.Bind(port_);listensock_.Listen();lg(Info, "init server .... done");return true;}void Start(){signal(SIGCHLD, SIG_IGN);//父进程忽略子进程的信号while (true){std::string clientip;int clientport;int sockfd = listensock_.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);// 提供服务if (fork() == 0){listensock_.Close();std::string inbuffer_stream;// 数据计算while (true)//为什么是两个循环?因为我要保证读取到的数据拼接到inbuffer_stream中时是一个完整的报文。{char buffer[1280];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;inbuffer_stream += buffer;lg(Debug, "debug:\n%s", inbuffer_stream.c_str());while (true){std::string info = Cal.Calculate(inbuffer_stream);if (info.empty())break;lg(Debug, "debug, response:\n%s", info.c_str());lg(Debug, "debug:\n%s", inbuffer_stream.c_str());write(sockfd, info.c_str(), info.size());}}else if (n == 0)break;elsebreak;}exit(0);}close(sockfd);}}~CalServer(){}private:uint16_t port_;Socket listensock_;
};

这里的listensock_ 对象是一个Socket类对象。这个Socket类定义如下:

#pragma once
#include<iostream>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>//定义一些变量
#define blog 10
#define defaultport 8080class Socket
{public://构造函数Socket(): sockfd_(0){}public://创建套接字bool Sock(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字if (sockfd_ < 0){std::cerr <<  "创建套接字失败" << std::endl;return false;}return true; // 将创建好的套接字返回}//bind,服务端只要绑定端口号bool Bind(int16_t port = defaultport){sockaddr_in server_addr;memset(&server_addr, 0, sizeof (server_addr));//清空数据server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);server_addr.sin_addr.s_addr = INADDR_ANY;int r1 = bind(sockfd_,(sockaddr*)&server_addr,sizeof server_addr);if(r1<0){std::cerr << "bind err" << std::endl;return false;}return true;}//监听bool Listen(){int r2 =  listen(sockfd_, blog);if(r2<0){std::cerr << "listen err" << std::endl;return 0;}return true;}//接收int Accept(std::string* ip,int* port){sockaddr_in cli_addr;socklen_t len = sizeof(cli_addr);int sockfd = accept(sockfd_, (sockaddr *)&cli_addr, &len);if(sockfd<0){std::cerr << "accept err" << std::endl;return -1;}char buff[64]={0};inet_ntop(AF_INET, &cli_addr, buff, sizeof(buff));*ip = buff;*port = ntohs(cli_addr.sin_port);return sockfd;}//连接bool Connect(std::string& ip,int16_t port){sockaddr_in addr_;addr_.sin_family = AF_INET;addr_.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(addr_.sin_addr));int r = connect(sockfd_, (sockaddr *)&addr_, sizeof (addr_));if(r<0){std::cerr << "connect err" << std::endl;return false;}return true;}//关闭void Close(){close(sockfd_);}public://成员int sockfd_;
};

CalServer内的函数作用: 

Init:创建套接字,bind套接字,监听套接字。

Start:循环接收客户端套接字,并且创建子进程对客户端的Request进行服务。在服务过程中父进程要对子进程的信号进行忽略,所以在创建子进程之前加上 signal(SIGCHLD, SIG_IGN)对子进程信号进行忽略。 

4,客户端

客户端的创建代码比较简单,代码如下:

#include "Socket.hpp"
#include "Protocol.hpp"
#include "log.hpp"class CalClient
{
public:void Init(std::string ip, int port){Sock.Sock(); // 创建套接字Sock.Connect(ip, port);srand(time(0));lg(Info, "Connect sucess");}void Start(){// 创建100以内的数据const std::string opers = "+-*/%";const int len = opers.size();int cnt = 10;while (cnt--){std::cout <<"------------"<< "第" << cnt << "次测试"<< "------------" << std::endl;int data1 = rand() % 100;int data2 = rand() % 100;char op = opers[rand() % len];// 建立需求Request req(data1, data2, op);// 序列化std::string content;req.Serialize(&content);// encodecontent = Encode(content);// 送数据到Serverstd::cout << "请求构建完成:" << req.x << req.op << req.y << "="<< "?" << std::endl;write(Sock.sockfd_, content.c_str(), content.size());// 接收数据char buff[1280];read(Sock.sockfd_, buff, sizeof(buff));// 解码std::string package = buff;content = "";Decode(package, &content);// 反序列化Response resp;resp.Deserialize(content);resp.Debugprint();std::cout << "结果相应完成"<< "-----------------" << std::endl;sleep(1);}}private:Socket Sock;
};

Init:创建套接字   向客户端建立连接,生成随机数种子。

Start:随机数的方式构建请求,将请求序列化和加码后使用write发送给服务端,然后再使用read将服务端发送回来的结果读取显示。

5,main函数

在是实现完如上代码后,我们可以来实现两个main函数来调用一下如上代码。

Server.cc:

#include "CalServer.hpp"void usage(std::string proc)
{std::cout << proc << "port[1024+]" << std::endl;
}int main(int argc, char *argv[])
{if(argc!=2){usage(argv[0]);}int port = std::stoi(argv[1]);CalServer *sev = new CalServer(port);sev->Init();sev->Start();return 0;
}

Client.cc:

#include"CalClient.hpp"
void usage(std::string proc)
{std::cout << proc << "port[1024+]" << std::endl;
}int main(int argc,char* argv[])
{if(argc!=3){usage(argv[0]);}std::string serip = argv[1];int serport = std::stoi(argv[2]);CalClient *cli = new CalClient;cli->Init(serip, serport);cli->Start();}

 调用以后结果如下:

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

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

相关文章

基于Spring Boot 3 + Spring Security6 + JWT + Redis实现接口资源鉴权

紧接上一篇文章&#xff0c;基于Spring Boot 3 Spring Security6 JWT Redis实现接口资源鉴权 系列文章指路&#x1f449; 系列文章-基于SpringBoot3创建项目并配置常用的工具和一些常用的类 项目源码&#x1f449; /shijizhe/boot-test 文章目录 1. 修改 UserDetailsServic…

U盘文件突然消失:原因分析与恢复策略

U盘遭遇“幽灵”之手&#xff0c;文件不翼而飞 你是否曾遭遇过这样的诡异情况&#xff1a;前一天还好好存放在U盘里的文件&#xff0c;第二天却突然消失得无影无踪&#xff1f;这简直就像是一场无声的灾难&#xff0c;令人措手不及。U盘作为我们日常工作和生活中不可或缺的数据…

算法系列之数组里的双指针

文章目录 算法系列之数组里的双指针快慢指针代替双重循环暴力解法快慢指针法 用双指针来排序暴力解法双指针排序 算法系列之数组里的双指针 在数组和链表的算法中双指针常出现&#xff0c;这篇用两道题来看一下双指针在数组算法里的用处。 快慢指针代替双重循环 https://lee…

嵌入式3-29

今日作业&#xff1a;用fwrite 和 fseek功能&#xff0c;将一张bmp格式的图片更改成 德国国旗#include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> typedef unsigned char bgr[3]; int main(int argc, const char *argv[])…

安装docker 并搭建出一颗爱心树

1、docker介绍 Docker 是⼀个开源的容器运⾏时软件&#xff08;容器运⾏时是负责运⾏容器的软件&#xff09;&#xff0c;基于 Go 语 ⾔编写&#xff0c;并遵从 Apache2.0 协议开源。 Docker可以让开发者打包⾃⼰的应⽤以及依赖到⼀个轻量的容器中&#xff0c;然后发布到任何…

开源大数据集群部署(十九)Hbase部署

作者&#xff1a;櫰木 1 HBASE 安装部署 hbase组件部署主机HMasterhd1.dtstack.com,hd2.dtstack.comHRegionServerhd3.dtstack.com,hd2.dtstack.com,hd1.dtstack.com 2 创建hbase Kerberos主体 在每台机器上进行生成 bash /data/kerberos/getkeytabs.sh /etc/security/key…

Kaggle注册验证码问题(Captcha must be filled out.)

Kaggle注册验证码问题 Captcha must be filled out.使用Edge浏览器 Header Editor 插件安装 下载插件Header Editor 导入重定向脚本 点击扩展插件&#xff0c; 打开Header Editor插件&#xff0c;进行管理 点击导入输入下载链接进行下载或者导入本地json文件(二者任选其一…

OpenHarmony系统开发之应用接口文件转换工具介绍

简介&#xff1a; 应用接口文件转换工具是根据异构格式接口文件(.h 文件)转换生成 OpenHarmony 系统应用层需要的 TS(type-script)接口文件(*.d.ts)的工具。若某个服务实现方式为 c&#xff0c;且供应用层访问的接口已在.h 文件中定义&#xff0c;此时&#xff0c;NAPI 接口开…

OpenHarmony无人机MAVSDK开源库适配方案分享

MAVSDK 是 PX4 开源团队贡献的基于 MavLink 通信协议的用于无人机应用开发的 SDK&#xff0c;支持多种语言如 C/C、python、Java 等。通常用于无人机间、地面站与通信设备的消息传输。 MAVLink 是一种非常轻量级的消息传递协议&#xff0c;用于与无人机&#xff08;以及机载无…

目标检测评价标准

主要借鉴&#xff1a;https://github.com/rafaelpadilla/Object-Detection-Metrics?tabreadme-ov-file 主要评价指标、术语&#xff1a; Intersection Over Union (IOU)&#xff1a;两个检测框交集面积与并集面积的比值 True Positive (TP)&#xff1a;IOU大于阈值的检测框…

计算机网络——30SDN控制平面

SDN控制平面 SDN架构 数据平面交换机 快速、简单&#xff0c;商业化交换设备采用硬件实现通用转发功能流表被控制器计算和安装基于南向API&#xff0c;SDN控制器访问基于流的交换机 定义了哪些可以被控制哪些不能 也定义了和控制器的协议 SDN控制器&#xff08;网络OS&#…

计算机基础系列 —— 虚拟机代码翻译器(1)

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.” ―Linus Torvalds 文中提到的所有实现都可以参考&#xff1a;nand2tetris_sol&#xff0c;但是最好还是自己学习课程实现一…