网站设计与制作的基本步骤,深圳建筑设计公司,手机网站幻灯片,做平面设计用哪个素材网站好文章目录 协议定制 Json序列化反序列化1. 再谈 协议1.1 结构化数据1.2 序列化和反序列化 2. 网络版计算器2.1 服务端2.2 协议定制(1) 网络发送和读取的正确理解(2) 协议定制的问题 2.3 客户端2.4 代码 3. Json实现序列化反序列化3.1 简单介绍3.2 使用 协议定制 J… 文章目录 协议定制 Json序列化反序列化1. 再谈 协议1.1 结构化数据1.2 序列化和反序列化 2. 网络版计算器2.1 服务端2.2 协议定制(1) 网络发送和读取的正确理解(2) 协议定制的问题 2.3 客户端2.4 代码 3. Json实现序列化反序列化3.1 简单介绍3.2 使用 协议定制 Json序列化反序列化
1. 再谈 “协议”
1.1 结构化数据
协议是一种 “约定”socket api的接口, 在读写数据时都是按 “字符串” 的方式来发送接收的。如果我们要传输一些结构化的数据 怎么办呢?
结构化数据
比如我们在QQ聊天时并不是单纯地只发送了消息本身是把自己的头像、昵称、发送时间、消息本身一起发送给别人这种一起发送的就是结构化数据。
1.2 序列化和反序列化
序列化就是将对象转化成字节序列的过程。便于在传递和保存对象时保证对象的完整性和可传递性同时对象转换为有序字节流以便在网络上传输或者保存在本地文件中。反序列化就是将字节序列转化成对象的过程。便于根据字节流中保存的对象状态及描述信息通过反序列化重建对象。 在socket编程的基础上我们发现在实际生活中网络通信并不是单单发送一条消息本身它包含了很多其他类型的数据所以我们引入了结构化数据的概念将这些各种类型的数据都定义在一个结构体中形成结构化数据方便被上层设置与读取。 发送数据时将这个结构体按照一个规则转换成字符串接收到数据的时候再按照相同的规则把字符串转化回结构体实现序列化和反序列化方便网络通信。 那么发送方发送的结构化数据序列化成字符串接收方收到后是怎么知道反序列化成结构化数据呢这是因为两者间存在定址好的协议。所以协议的本质就是双方约定好的某种格式的数据常见的就是用结构体或者类来表达。
2. 网络版计算器
我们需要实现一个服务器版的计算器客户端把要计算的两个数和计算类型发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
2.1 服务端
服务端创建步骤
调用socket创建套接字调用bind绑定端口调用listen将套接字状态设置为监听调用accept获取新连接处理读取与写入的问题(重点)
2.2 协议定制
(1) 网络发送和读取的正确理解 客户端和服务器通信时会调用read和write函数它们是把数据直接发送到对端吗不是
TCP协议有自己的发送缓冲区和接收缓冲区调用write本质把用户所对应的数据拷贝到TCP的发送缓冲区调用read本质把数据从接收缓冲区拷贝到用户层所以read和write的本质是拷贝函数把数据拷贝到TCP发送缓冲区后剩下的数据怎么发是由TCP决定的所以TCP又叫做传输控制协议因为发送和接收是成对的可以同时进行的所以TCP协议是全双工的
综上
TCP通信的本质是把自己发送缓冲区的数据经过网络拷贝到对方的接收缓冲区中网络通信的本质也是拷贝
(2) 协议定制的问题
在定制协议之前先解决一个问题之前在使用TCP协议时我们只是简单的读取没有考虑TCP是面向字节流的读取数据不完整的问题。这里同样存在相同的问题如果一下子对方发送了很多报文这些报文都堆积在TCP的接收缓冲区中你怎么保证自己读到的是一个完整的报文呢
我们采用这样的方式
对报文定长使用特殊符号(在报文与报文之间增加特殊符号自描述方式(自己设计协议)
协议设计格式 Protocol.hpp
#includestring
#includeiostream
#includevector
#includecstring
#includesys/types.h
#includesys/socket.h
#includeUtil.hpp
using namespace std;// 给网络版本计算器定制协议
namespace Protocol_ns
{#define SEP #define SEP_LEN strlen(SEP) // 绝对不能写成sizeof#define HEADER_SEP \r\n#define HEADER_SEP_LEN strlen(\r\n)// 长度\r\n _x op _y\r\n// 10 20 7r\n10 20\r\n 报头 有效载荷// 请求/响应 报头\r\n有效载荷\r\n// 请求 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n// 10 20 7r\n10 20\r\nstring AddHeader(stringstr){coutAddHeader 之前:\nstrendl;string sto_string(str.size());sHEADER_SEP;sstr;sHEADER_SEP;coutAddHeader 之后:\nsendl;return s;}// 7r\n10 20\r\n 10 20 string RemoveHeader(const stringstr,int len){coutRemoveHeader 之前:\nstrendl;// 从后面开始截取string resstr.substr(str.size()-HEADER_SEP_LEN-len,len); coutRemoveHeader 之后:\nresendl; return res;}int Readpackage(int sock,stringinbuffer,string*package){coutReadPackage inbuffer 之前:\ninbufferendl;// 边读取char buffer[1024];ssize_t srecv(sock,buffer,sizeof(buffer)-1,0);if(s0)return -1;buffer[s]0;inbufferbuffer;coutReadPackage inbuffer 之中:\ninbufferendl;// 边分析, 7r\n10 20\r\nauto posinbuffer.find(HEADER_SEP);if(posstring::npos)return 0;string lenStrinbuffer.substr(0,pos); // 获取头部字符串, 没有动inbufferint lenUtil::toInt(lenStr); // 得到有效载荷的长度 123 - 123int targetPackageLenlen2*HEADER_SEP_LENlenStr.size(); // 得到整个报文长度if(inbuffer.size()targetPackageLen) // 不是一个完整的报文return 0;*packageinbuffer.substr(0,targetPackageLen); // 提取到了报文有效载荷, 没有动inbufferinbuffer.erase(0,targetPackageLen); // 从inbuffer中直接移除整个报文coutReadPackage inbuffer 之后:\ninbufferendl;return len;}// Request Response都要提供序列化和反序列化功能// 1. 自己手写// 2. 用别人的 --- json, xml, protobufclass Request{public:Request(){}Request(int x,int y,char op):_x(x),_y(y),_op(op){}// 序列化: struct-stringbool Serialize(string* outStr) {*outStr; string x_stringto_string(_x);string y_stringto_string(_y);// 手动序列化*outStrx_string SEP _op SEP y_string;std::cout Request Serialize:\n *outStr std::endl;return true;}// 反序列化: string-structbool Deserialize(const stringinStr) {// inStr: 10 20 [0]10, [1], [2]20vectorstring result;Util::StringSplit(inStr,SEP,result);if(result.size()!3)return false;if(result[1].size()!1)return false;_xUtil::toInt(result[0]);_yUtil::toInt(result[2]);_opresult[1][0];return true;}~Request(){}public:// _x op _y 10 * 9 ? 10 / 0 ?int _x;int _y;char _op;};class Response{public:Response(){}Response(int result,int code):_result(result),_code(code){}// 序列化: struct-stringbool Serialize(string* outStr) {// _result _code*outStr; string res_string to_string(_result);string code_string to_string(_code);// 手动序列化*outStrres_string SEP code_string;return true;}// 反序列化: string-structbool Deserialize(const stringinStr) {// 10 0vectorstring result;Util::StringSplit(inStr,SEP,result);if(result.size()!2)return false;_resultUtil::toInt(result[0]);_codeUtil::toInt(result[1]);return true;}~Response(){}public:int _result;int _code; // 0 success; 1,2,3,4代表不同错误码};}Util.hpp
#pragma once#includeiostream
#includestring
#includevector
using namespace std;class Util
{
public:// 输入: const // 输出: *// 输入输出: *static bool StringSplit(const string str, const string sep, vectorstring *result){// 10 20size_t start 0;while (start str.size()){auto pos str.find(sep, start);if (pos string::npos)break;result-push_back(str.substr(start, pos - start));// 更新位置start pos sep.size();}// 处理最后的字符串if(startstr.size())result-push_back(str.substr(start));return true;}static int toInt(const strings) // 字符串转整数{return atoi(s.c_str());}
};2.3 客户端
客户端创建步骤
调用socket创建套接字客户端不用自己bind端口调用connect连接服务器处理读取与写入的问题
2.4 代码
完整的代码lesson36 · 遇健/Linux - 码云 - 开源中国 (gitee.com)
运行结果 3. Json实现序列化反序列化
3.1 简单介绍
上面是自己定制协议实现序列化和反序列化下面我们使用一些现成的方案来实现序列化和反序列化。C常用的protobuf 和 json这里使用简单的 json。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式但是也使用了类似于C语言家族的习惯包括C, C, Java, JavaScript, Perl, Python等。这些特性使JSON成为理想的数据交换语言。Json数据由键值对组成大括号表示对象方括号表示数组。 3.2 使用
安装json库
yum install -y jsoncpp-devel使用json包含的头文件
#include jsoncpp/json/json.h注意makefile文件要包含Json库的名称 我们在使用的时候直接创建Json对象来进行序列化和反序列化
Protocol.hpp
#includestring
#includeiostream
#includevector
#includecstring
#includesys/types.h
#includesys/socket.h
#includeUtil.hpp
#includejsoncpp/json/json.h
using namespace std;// #define MYSELF 1// 给网络版本计算器定制协议namespace Protocol_ns
{#define SEP #define SEP_LEN strlen(SEP) // 绝对不能写成sizeof#define HEADER_SEP \r\n#define HEADER_SEP_LEN strlen(\r\n)// 长度\r\n _x op _y\r\n// 10 20 7r\n10 20\r\n 报头 有效载荷// 请求/响应 报头\r\n有效载荷\r\n// 请求 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n// 未来: 长度\r\n协议号\r\n_x op _y\r\n// 10 20 7r\n10 20\r\nstring AddHeader(stringstr){coutAddHeader 之前:\nstrendl;string sto_string(str.size());sHEADER_SEP;sstr;sHEADER_SEP;coutAddHeader 之后:\nsendl;return s;}// 7r\n10 20\r\n 10 20 string RemoveHeader(const stringstr,int len){coutRemoveHeader 之前:\nstrendl;// 从后面开始截取string resstr.substr(str.size()-HEADER_SEP_LEN-len,len); coutRemoveHeader 之后:\nresendl; return res;}int Readpackage(int sock,stringinbuffer,string*package){coutReadPackage inbuffer 之前:\ninbufferendl;// 边读取char buffer[1024];ssize_t srecv(sock,buffer,sizeof(buffer)-1,0);if(s0)return -1;buffer[s]0;inbufferbuffer;coutReadPackage inbuffer 之中:\ninbufferendl;// 边分析, 7r\n10 20\r\nauto posinbuffer.find(HEADER_SEP);if(posstring::npos)return 0;string lenStrinbuffer.substr(0,pos); // 获取头部字符串, 没有动inbufferint lenUtil::toInt(lenStr); // 得到有效载荷的长度 123 - 123int targetPackageLenlen2*HEADER_SEP_LENlenStr.size(); // 得到整个报文长度if(inbuffer.size()targetPackageLen) // 不是一个完整的报文return 0;*packageinbuffer.substr(0,targetPackageLen); // 提取到了报文有效载荷, 没有动inbufferinbuffer.erase(0,targetPackageLen); // 从inbuffer中直接移除整个报文coutReadPackage inbuffer 之后:\ninbufferendl;return len;}// Request Response都要提供序列化和反序列化功能// 1. 自己手写// 2. 用别人的class Request{public:Request(){}Request(int x,int y,char op):_x(x),_y(y),_op(op){}// 序列化: struct-stringbool Serialize(string* outStr) {*outStr;
#ifdef MYSELFstring x_stringto_string(_x);string y_stringto_string(_y);// 手动序列化*outStrx_string SEP _op SEP y_string;std::cout Request Serialize:\n *outStr std::endl;
#elseJson::Value root; // Value: 一种万能对象, 接受任意的kv类型root[x]_x;root[y]_y;root[op]_op;// Json::FastWriter writer; // writer: 是用来进行序列化的 struct - stringJson::StyledWriter writer;*outStrwriter.write(root);
#endifreturn true;}// 反序列化: string-structbool Deserialize(const stringinStr) {
#ifdef MYSELF// inStr: 10 20 [0]10, [1], [2]20vectorstring result;Util::StringSplit(inStr,SEP,result);if(result.size()!3)return false;if(result[1].size()!1)return false;_xUtil::toInt(result[0]);_yUtil::toInt(result[2]);_opresult[1][0];#elseJson::Value root; Json::Reader reader; // Reader: 是用来反序列化的reader.parse(inStr,root);_xroot[x].asUInt();_yroot[y].asUInt();_oproot[op].asUInt();#endifPrint();return true;}void Print(){std::cout _x: _x std::endl;std::cout _y: _y std::endl;std::cout _z: _op std::endl;}~Request(){}public:// _x op _y 10 * 9 ? 10 / 0 ?int _x;int _y;char _op;};class Response{public:Response(){}Response(int result,int code):_result(result),_code(code){}// 序列化: struct-stringbool Serialize(string* outStr) {// _result _code*outStr;
#ifdef MYSELFstring res_string to_string(_result);string code_string to_string(_code);// 手动序列化*outStrres_string SEP code_string;#elseJson::Value root; root[result]_result;root[code]_code;// Json::FastWriter writer;Json::StyledWriter writer;*outStrwriter.write(root);
#endifreturn true;}// 反序列化: string-structbool Deserialize(const stringinStr) {
#ifdef MYSELF// 10 0vectorstring result;Util::StringSplit(inStr,SEP,result);if(result.size()!2)return false;_resultUtil::toInt(result[0]);_codeUtil::toInt(result[1]);#elseJson::Value root;Json::Reader reader; reader.parse(inStr, root);_result root[result].asUInt();_code root[code].asUInt();
#endifPrint();return true;}void Print(){std::cout _result: _result std::endl;std::cout _code: _code std::endl;}~Response(){}public:int _result;int _code; // 0 success; 1,2,3,4代表不同错误码};
}完整代码lesson36/NetCal_v2 · 遇健/Linux - 码云 - 开源中国 (gitee.com)