网站基本常识,东莞网站推广策划活动,工程管理专业后悔死了,vue如何网站开发此前我们关于TCP协议一直写的都是直接recv或者read#xff0c;有了字节流的概念后#xff0c;我们知道这样直接读可能会出错#xff0c;所以我们如何进行分割完整报文#xff1f;这就需要报头来解决了#xff01;
但当前我们先不谈这个话题#xff0c;先从头开始。
将会…此前我们关于TCP协议一直写的都是直接recv或者read有了字节流的概念后我们知道这样直接读可能会出错所以我们如何进行分割完整报文这就需要报头来解决了
但当前我们先不谈这个话题先从头开始。
将会着重理解OSI 7层模型中传输层向上的3层并编码进行解释。
而恰好tcp/ip模型是4层或5层将OSI上三层统一压缩为1层应用层了这究竟又有什么关系呢 我们实际编程中也正是按照这种模式进行编写的。 目录 1. 服务端1.1 会话层1.2 表示层1.3 应用层 2. 客户端2.1表示层 3. 完整代码 1. 服务端
1.1 会话层
会话层是一个什么意思 通俗理解就是建立连接与断开连接也就是connect与accept
我们都是在server的hpp文件中进行的
下段代码是一个大概的流程这也就是编码实现会话层
int fd accept...IO_service(fd等需要的参数...)close(fd)1.2 表示层
这段话确实抽象 通俗理解就是两个通信的主机按照一定的格式进行传输信息即按照相同的请求协议与响应协议进行转化序列化和反序列化传输数据。
在之前的基于tcp的网络服务程序中我们表面没有体现出这个但实际上我们传输的协议就是传输字符串这也是我们约定好的。
这层也是我们今天的重点。
由于我们当前是基于结构体传输请求协议与响应协议但是由于技术原因与业务原因导致直接使用结构体传输会导致各种各样的问题所以我们序列化为固定格式的字符串进行传输在反序列为你需要的协议格式进行操作。这是我们在应用层自定义协议就已经说过的了。 但因为是字节流的原因所以recv或read到的字符串不一定是完整的请求因此需要添加报头进行解决。
我们先来解决序列化与反序列化的问题。 其中序列化我们可以手写也可以借助各种各样的库文件进行操作这里我们选择使用json最主要的原因还是因为可视化 关于json我们可以大概的了解一下熟悉一下接口即可。
class Request
{
public:Request(int x, int y, char oper): _x(x),_y(y),_oper(oper){}Request(){}void Serialization(std::string *out){Json::Value root;root[x] _x;root[y] _y;root[oper] _oper;Json::StyledWriter writer;// Json::FastWriter writer;*out writer.write(root);}void Deserialization(const std::string in){Json::Reader reader;Json::Value root;reader.parse(in, root);_x root[x].asInt();_y root[y].asInt();_oper root[oper].asInt();}void Print(){std::cout _x std::endl;std::cout _y std::endl;std::cout _oper std::endl;}~Request(){}public:int _x;int _y;int _oper;
};int main()
{Request req(1, 1, *);std::string str;req.Serialization(str);std::cout str std::endl;Request req1;req.Deserialization(str);req.Print();return 0;
}注意由于json是第三方库记得编译时-ljsoncpp 验证 尽管我们现在是在进行序列化与反序列化但是在序列化与反序列化前总得有请求请求协议与响应协议吧。 class Request
{
public:Request(int x, int y, char oper): _x(x),_y(y),_oper(oper){}Request(){}~Request(){}public:int _x;int _y;int _oper;
};class Response
{
public:Response(): _code(0),_desc(sucess){}~Response(){}public:int _result;int _code; // 0:sucess, 1:div zero, 2:mod zero, 3:invalid operstd::string _desc;
};上段代码就是两个协议的基本内容。
因此我们现在即可构建请求协议与响应协议的序列化与反序列化没错每种协议都需要构建序列化与反序列化 当客户端构建数据构需要序列化再传输服务端接收后再反序列化 服务端处理完数据后再将响应协议对象序列化传输客户端再反序列化得到结果。
这张图就很形象的展示了过程。 但是我们还需要解决如何获得的是一个完整的请求的问题我们已经说过解决方案了那就是添加报头于是我们进一步完善协议。
那么添加的报头是怎样的格式
len\r\n{json串}\r\n这种形式是非常通用的我们在HTTP协议中也可以看到这种形式的影子。
现在解释一下参数 len就是json串的长度\r\n本质上就是换行这样的健壮性更强\r是回退到初始行\n换行但现在我们的\n基本上都包含了换行到新行的开头的功能了。
现在解释一下为什么这么做
// le -- 残缺报文
// len\r\n{json} -- 残缺报文
// len\r\n{json}\r\n -- 完整报文
// len\r\n{json}\r\nlen\r\n{jso -- 冗余报文
// len\r\n{json}\r\nlen\r\n{json}\r\nlen\r\n{json}\r\n -- 冗余报文因为面向字节流所以我们有可能得到的数据是以上样子当我们这样设计报头时不论何种情况都可以处理。 假设是第一种情况我们先find \r\n若是没有则说明当前是不完整的报文继续recv即可。 若是第二种我们由于find到了\r\b所以就可得知json串的具体长度根据具体长度得到是否为完整的报文。 若是第四/五种我们直接截取最前方的完整报文即可。
故此时我们即可设计添加报头。
// 添加报头
std::string sep \r\n;std::string EnHeader(const std::string packagestream)
{int len packagestream.size();std::string ret std::to_string(len);return ret sep packagestream sep;
}// 注意这里我们传参是非const原因在于当得到不完整报文返回时还能续接。具体可以在完整代码中体现
std::string DeHeader(std::string packagestream)
{// 还没有读到lenauto pos packagestream.find(sep);if (pos std::string::npos){return {}; }// 检查是否为一个完整的json串int len std::stoi(packagestream.substr(0, pos));int total pos len 2 * sep.size();if (total packagestream.size()){return {};}// 至少有一个完整的json串std::string ret packagestream.substr(pos sep.size(), len);packagestream packagestream.erase(0, total);return ret;
}
如此准备工作便都做好了。
可以进行传输与接收了。
while (true)
{int n socket-Recv(messagequeue);if (n 0){break;}// 2. 去报头std::string ret DeHeader(messagequeue);if (ret.size() 0){continue;}// 3. 一个完整的报文进行反序列化// 只是利用工厂模式造了一个请求协议智能指针无需重点关注重点是进行序列化std::shared_ptrRequest req Bulider::GetReq();req-Deserialization(ret);// 4. 执行业务std::shared_ptrResponse resp _func(req);// 5. 序列化std::string jsonmessage;resp-Serialization(jsonmessage);// 6. 添加报头jsonmessage EnHeader(jsonmessage);// 7. 发送数据n socket-Send(jsonmessage);if (n 0){break;}
}1.3 应用层
应用层就是处理我们的业务的 我们上段代码的第四步就是应用层。
这里根据我们制作的网络计算机设计对应的业务即可。
class Calculator
{
public:Calculator(){}std::shared_ptrResponse calculate(std::shared_ptrRequest req){std::shared_ptrResponse resp std::make_sharedResponse();std::cout req-_x req-_oper req-_y std::endl;switch (req-_oper){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-_code 1;resp-_desc div zero;}else{resp-_result req-_x / req-_y;}}break;case %:{if (req-_y 0){resp-_code 2;resp-_desc mod zero;}else{resp-_result req-_x % req-_y;}}break;default:{resp-_code 3;resp-_desc illegal operation;}break;}return resp;}~Calculator(){}
};最后将3层整合在一起即可这些便都是套路了在完整代码中即可看到。
2. 客户端
本质上与服务端是很相似的只是那几个步骤变了变顺序而已。
2.1表示层
while (true)
{// 构建数据std::shared_ptrRequest req Bulider::GetReq();req-_x rand() % 10;req-_y rand() % 10;req-_oper oper[req-_y % oper.size()];// 序列化数据std::string reqstr;req-Serialization(reqstr);// 添加报头reqstr EnHeader(reqstr);// 发送数据int n sockclient-Send(reqstr);// 接收数据std::string recvstr;while (true){n sockclient-Recv(recvmessage);if (n 0){break;}// 去报头recvstr DeHeader(recvmessage);if (recvstr.size() 0){continue;}break;}// 反序列化std::shared_ptrResponse resp Bulider::GetResp();resp-Deserialization(recvstr);
}3. 完整代码
Gitee代码展示。 完~