协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定,比如怎么建立连接、怎么互相识别等。
为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
通信双方在进行网络通信时:
比如现在要实现一个网络版的计算器,那么客户端每次给服务端发送的请求数据当中,就需要包括左操作数、右操作数以及对应需要进行的操作,此时客户端要发送的就不是一个简单的字符串,而是一组结构化的数据。
如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要纠结如何将接收到的数据进行组合。因此客户端最好把这些结构化的数据打包后统一发送到网络当中,此时服务端每次从网络当中获取到的就是一个完整的请求数据,客户端常见的“打包”方式有以下两种。
将结构化的数据组合成一个字符串
约定方案一:
客户端可以按某种方式将这些结构化的数据组合成一个字符串,然后将这个字符串发送到网络当中,此时服务端每次从网络当中获取到的就是这样一个字符串,然后服务端再以相同的方式对这个字符串进行解析,此时服务端就能够从这个字符串当中提取出这些结构化的数据。
定制结构体+序列化和反序列化
约定方案二:
客户端可以定制一个结构体,将需要交互的信息定义到这个结构体当中。客户端发送数据时先对数据进行序列化,服务端接收到数据后再对其进行反序列化,此时服务端就能得到客户端发送过来的结构体,进而从该结构体当中提取出对应的信息。
序列化和反序列化:
OSI七层模型中表示层的作用就是,实现设备固有数据格式和网络标准数据格式的转换。其中设备固有的数据格式指的是数据在应用层上的格式,而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式。
序列化和反序列化的目的
我们可以认为网络通信和业务处理处于不同的层级,在进行网络通信时底层看到的都是二进制序列的数据,而在进行业务处理时看得到则是可被上层识别的数据。如果数据需要在业务处理和网络通信之间进行转换,则需要对数据进行对应的序列化或反序列化操作。
我们需要实现一个服务器版的计算器,客户端把要计算的两个数和计算类型发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
服务端创建步骤:
客户端和服务器通信时,会调用read和write函数,它们是把数据直接发送到对端吗?不是
综上:
在定制协议之前先解决一个问题,之前在使用TCP协议时我们只是简单的读取,没有考虑TCP是面向字节流的,读取数据不完整的问题。这里同样存在相同的问题,如果一下子对方发送了很多报文,这些报文都堆积在TCP的接收缓冲区中,你怎么保证自己读到的是一个完整的报文呢?
我们采用这样的方式:
协议设计格式:
Protocol.hpp
#include
#include
#include
#include
#include
#include
#include"Util.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" => "7"r\n""10 + 20"\r\n => 报头 + 有效载荷
// 请求/响应 = 报头\r\n有效载荷\r\n
// 请求 = 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n
// "10 + 20" => "7"r\n""10 + 20"\r\n
string AddHeader(string&str)
{
cout<<"AddHeader 之前:\n"
<<str<<endl;
string s=to_string(str.size());
s+=HEADER_SEP;
s+=str;
s+=HEADER_SEP;
cout<<"AddHeader 之后:\n"
<<s<<endl;
return s;
}
// "7"r\n""10 + 20"\r\n => "10 + 20"
string RemoveHeader(const string&str,int len)
{
cout<<"RemoveHeader 之前:\n"
<<str<<endl;
// 从后面开始截取
string res=str.substr(str.size()-HEADER_SEP_LEN-len,len);
cout<<"RemoveHeader 之后:\n"
<<res<<endl;
return res;
}
int Readpackage(int sock,string&inbuffer,string*package)
{
cout<<"ReadPackage inbuffer 之前:\n"
<<inbuffer<<endl;
// 边读取
char buffer[1024];
ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);
if(s<=0)
return -1;
buffer[s]=0;
inbuffer+=buffer;
cout<<"ReadPackage inbuffer 之中:\n"
<<inbuffer<<endl;
// 边分析, "7"r\n""10 + 20"\r\n
auto pos=inbuffer.find(HEADER_SEP);
if(pos==string::npos)
return 0;
string lenStr=inbuffer.substr(0,pos); // 获取头部字符串, 没有动inbuffer
int len=Util::toInt(lenStr); // 得到有效载荷的长度 => "123" -> 123
int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size(); // 得到整个报文长度
if(inbuffer.size()<targetPackageLen) // 不是一个完整的报文
return 0;
*package=inbuffer.substr(0,targetPackageLen); // 提取到了报文有效载荷, 没有动inbuffer
inbuffer.erase(0,targetPackageLen); // 从inbuffer中直接移除整个报文
cout<<"ReadPackage inbuffer 之后:\n"
<<inbuffer<<endl;
return len;
}
// Request && Response都要提供序列化和反序列化功能
// 1. 自己手写
// 2. 用别人的 --- json, xml, protobuf
class Request
{
public:
Request()
{
}
Request(int x,int y,char op)
:_x(x)
,_y(y)
,_op(op)
{
}
// 序列化: struct->string
bool Serialize(string* outStr)
{
*outStr="";
string x_string=to_string(_x);
string y_string=to_string(_y);
// 手动序列化
*outStr=x_string + SEP + _op + SEP + y_string;
std::cout << "Request Serialize:\n"
<< *outStr << std::endl;
return true;
}
// 反序列化: string->struct
bool Deserialize(const string&inStr)
{
// inStr: 10 + 20 => [0]=>10, [1]=>+, [2]=>20
vector<string> result;
Util::StringSplit(inStr,SEP,&result);
if(result.size()!=3)
return false;
if(result[1].size()!=1)
return false;
_x=Util::toInt(result[0]);
_y=Util::toInt(result[2]);
_op=result[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->string
bool Serialize(string* outStr)
{
// _result _code
*outStr="";
string res_string = to_string(_result);
string code_string = to_string(_code);
// 手动序列化
*outStr=res_string + SEP + code_string;
return true;
}
// 反序列化: string->struct
bool Deserialize(const string&inStr)
{
// 10 0
vector<string> result;
Util::StringSplit(inStr,SEP,&result);
if(result.size()!=2)
return false;
_result=Util::toInt(result[0]);
_code=Util::toInt(result[1]);
return true;
}
~Response()
{
}
public:
int _result;
int _code; // 0 success; 1,2,3,4代表不同错误码
};
}
Util.hpp
#pragma once
#include
#include
#include
using namespace std;
class Util
{
public:
// 输入: const &
// 输出: *
// 输入输出: *
static bool StringSplit(const string &str, const string &sep, vector<string> *result)
{
// 10 + 20
size_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(start<str.size())
result->push_back(str.substr(start));
return true;
}
static int toInt(const string&s) // 字符串转整数
{
return atoi(s.c_str());
}
};
客户端创建步骤:
上面是自己定制协议实现序列化和反序列化,下面我们使用一些现成的方案来实现序列化和反序列化。C++常用的:protobuf 和 json,这里使用简单的 json。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。Json数据由键值对组成,大括号表示对象,方括号表示数组。
yum install -y jsoncpp-devel
#include
注意makefile文件要包含Json库的名称
我们在使用的时候直接创建Json对象来进行序列化和反序列化
#include
#include
#include
#include
#include
#include
#include"Util.hpp"
#include
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" => "7"r\n""10 + 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" => "7"r\n""10 + 20"\r\n
string AddHeader(string&str)
{
cout<<"AddHeader 之前:\n"
<<str<<endl;
string s=to_string(str.size());
s+=HEADER_SEP;
s+=str;
s+=HEADER_SEP;
cout<<"AddHeader 之后:\n"
<<s<<endl;
return s;
}
// "7"r\n""10 + 20"\r\n => "10 + 20"
string RemoveHeader(const string&str,int len)
{
cout<<"RemoveHeader 之前:\n"
<<str<<endl;
// 从后面开始截取
string res=str.substr(str.size()-HEADER_SEP_LEN-len,len);
cout<<"RemoveHeader 之后:\n"
<<res<<endl;
return res;
}
int Readpackage(int sock,string&inbuffer,string*package)
{
cout<<"ReadPackage inbuffer 之前:\n"
<<inbuffer<<endl;
// 边读取
char buffer[1024];
ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);
if(s<=0)
return -1;
buffer[s]=0;
inbuffer+=buffer;
cout<<"ReadPackage inbuffer 之中:\n"
<<inbuffer<<endl;
// 边分析, "7"r\n""10 + 20"\r\n
auto pos=inbuffer.find(HEADER_SEP);
if(pos==string::npos)
return 0;
string lenStr=inbuffer.substr(0,pos); // 获取头部字符串, 没有动inbuffer
int len=Util::toInt(lenStr); // 得到有效载荷的长度 => "123" -> 123
int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size(); // 得到整个报文长度
if(inbuffer.size()<targetPackageLen) // 不是一个完整的报文
return 0;
*package=inbuffer.substr(0,targetPackageLen); // 提取到了报文有效载荷, 没有动inbuffer
inbuffer.erase(0,targetPackageLen); // 从inbuffer中直接移除整个报文
cout<<"ReadPackage inbuffer 之后:\n"
<<inbuffer<<endl;
return len;
}
// Request && Response都要提供序列化和反序列化功能
// 1. 自己手写
// 2. 用别人的
class Request
{
public:
Request()
{
}
Request(int x,int y,char op)
:_x(x)
,_y(y)
,_op(op)
{
}
// 序列化: struct->string
bool Serialize(string* outStr)
{
*outStr="";
#ifdef MYSELF
string x_string=to_string(_x);
string y_string=to_string(_y);
// 手动序列化
*outStr=x_string + SEP + _op + SEP + y_string;
std::cout << "Request Serialize:\n"
<< *outStr << std::endl;
#else
Json::Value root; // Value: 一种万能对象, 接受任意的kv类型
root["x"]=_x;
root["y"]=_y;
root["op"]=_op;
// Json::FastWriter writer; // writer: 是用来进行序列化的 struct -> string
Json::StyledWriter writer;
*outStr=writer.write(root);
#endif
return true;
}
// 反序列化: string->struct
bool Deserialize(const string&inStr)
{
#ifdef MYSELF
// inStr: 10 + 20 => [0]=>10, [1]=>+, [2]=>20
vector<string> result;
Util::StringSplit(inStr,SEP,&result);
if(result.size()!=3)
return false;
if(result[1].size()!=1)
return false;
_x=Util::toInt(result[0]);
_y=Util::toInt(result[2]);
_op=result[1][0];
#else
Json::Value root;
Json::Reader reader; // Reader: 是用来反序列化的
reader.parse(inStr,root);
_x=root["x"].asUInt();
_y=root["y"].asUInt();
_op=root["op"].asUInt();
#endif
Print();
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->string
bool Serialize(string* outStr)
{
// _result _code
*outStr="";
#ifdef MYSELF
string res_string = to_string(_result);
string code_string = to_string(_code);
// 手动序列化
*outStr=res_string + SEP + code_string;
#else
Json::Value root;
root["result"]=_result;
root["code"]=_code;
// Json::FastWriter writer;
Json::StyledWriter writer;
*outStr=writer.write(root);
#endif
return true;
}
// 反序列化: string->struct
bool Deserialize(const string&inStr)
{
#ifdef MYSELF
// 10 0
vector<string> result;
Util::StringSplit(inStr,SEP,&result);
if(result.size()!=2)
return false;
_result=Util::toInt(result[0]);
_code=Util::toInt(result[1]);
#else
Json::Value root;
Json::Reader reader;
reader.parse(inStr, root);
_result = root["result"].asUInt();
_code = root["code"].asUInt();
#endif
Print();
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代表不同错误码
};
}
本文到此结束,码文不易,还请多多支持哦!!!