文章目录
- 再谈协议
- 序列化和反序列化
- 理解 read、write、recv、send 和 tcp 为什么支持全双工
- 自定义协议网络计算器
- 序列化和反序列化
再谈协议
协议就是约定,协议的内容就是约定好的某种结构化数据。比如,我们要实现一个网络版的计算器,客户端发送一个算术式子,服务端根据算术式子计算出结果,再把结果返回给客户端。在此之前,客户端和服务端可以做出如下两种方案的约定:
- 方案一:
- 客户端发送一个形如“1+1"的字符串
- 这个字符串中有两个操作数,都是整型
- 两个数字之间会有一个字符是运算符,运算符只能是+
- 数字和运算符之间没有空格
- 等等
- 方案二:
- 定义一个结构体表示一个请求信息,信息内容包括两个整数,和一个操作符
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 定义一个结构体表示一个服务器的响应,响应的信息包括,计算结果、错误码、错误信息等
为什么要做出诸如此类的约定呢? 一切交给服务器去处理不就行了吗?
做约定的本质就是为了方便读取数据和发送数据。假使客户端和服务端没有做任何约定,在我们这个网络版计算器中,客户端就有可能发送很多无效的信息,比如随便输入一段字符串。很显然,如果客户端不做任何处理直接发送给服务端,服务端虽然也能做一些手段去检测数据的合法性,但是那样太浪费时间了。
所以,为了能让整个交互过程有序且高效,客户端一定要与服务端达成某种协议,即规定发送时数据的格式与接收数据的格式。这样一来,客户端发送数据时按照约定好的格式打包好再发送,客户端就不需要考虑服务端怎么读取数据了,因为已经约定好了。相反,服务端接收数据包时按照约定,正确的实施解包的步骤就能读取到正确的数据,因为已经约定好了,客户端发送过来的数据一定是按照约定的格式存储的。
这就是约定,这就是协议。
序列化和反序列化
在上面我们谈到,协议是客户端与服务端约定好的某种存储数据的结构,说白了就是一个特定的结构体。但是无论是发送请求还是发送响应,都是需要通过网络来传输的,具体地说,在将传输给网络之前,我们要把上述包含数据的结构体进一步转换成可供网络传输的格式。同样的,从网络读取到数据后,在应用层还需要将其转换回来。前者就叫序列化,后者叫反序列化。
简单理解,序列化,就是网络化,就是把数据转换成某种在网络中传输的格式。反序列化,就是逆序列化过程
其中Jsoncpp库中提供了一些供序列化和反序列化的方法。
- 创建JSON对象
Json::Value root;
- 设置值
root["name"] = "John Doe";
root["age"] = 30;
- 访问值
td::string name = root["name"].asString();
int age = root["age"].asInt();
- 将数据序列化为字符串:
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);
理解 read、write、recv、send 和 tcp 为什么支持全双工
- 在每台主机上,TCP连接维护了两个缓冲区,分别用来发送数据和读取数据。所以发送数据和读取数据可以同时进行。因为这俩操作不是在同一个缓冲区进行的
- sockfd描述符本质就是一个文件描述符,但是它对应着读写两个缓冲区,操作系统会自动区分读和写操作,所以tcp通过一个sockfd就能进行读和写两种操作。
- 实际数据什么时候发,发多少,处理错误,由TCP协议控制,所以TCP叫做传输控制协议。
自定义协议网络计算器
根据前面的学习,我们可以自己简单模式一个用户层的协议,来实现网络版的计算器。
对于服务端,事件流程如下:
- 创建套接字
- 进入监听状态
- 获取连接
- 获取请求,将其反序列化,得到一个包含客户端数据的结构体
- 处理业务
- 将应答消息序列化,得到一个字符串,发送给客户端
- 上述步骤重复执行
- 关闭连接,关闭套接字
对于客户端,事件如下:
- 创建套接字
- 连接服务端
- 将序列化后的数据打包成一个请求发送给服务端
- 获取服务端的应答,将应答反序列化获取结果
- 关闭连接,关闭套接字
既然是自定义协议,那么我们可以规定请求和应答的数据格式为:len\r\n{json}\r\n
。json其实就是一个用Json库处理序列化后的一个字符串(该字符串包含有效数据),len表示的是json字符串的长度。
中间的换行符用于区分字节流读取进度。假设读取到的数据流有一个换行符,说明该数据流一定包含len信息,拿到len信息后就可以再判断该数据流是否包含json字符串。
于是,在请求之前,我们需要先把有效数据转换成一个Json处理过的字符串,这叫做序列化。由于是网络中是字节流传输数据,为了辨别拿到的数据是否完整,我们还需要添加一些”标识“数据,这就是报头,整合起来就是一个报文。
完整代码可以去我gitee上去获取:点击查看
下面只给出序列化和反序列化实现模块
序列化和反序列化
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>static const std::string sep = "\r\n";// 报文格式 len\r\n{joson}\r\n// 将jsonstr格式的字符串加上报头
std::string Encode(const std::string &jsonstr)
{int len = jsonstr.size();std::string lenstr = std::to_string(len);return lenstr + sep + jsonstr + sep;
}// 将接收到的数据中的joson段提取出来
std::string Decode(std::string &packagestream)
{// 分析auto pos = packagestream.find(sep);if (pos == std::string::npos){return std::string();}std::string lenstr = packagestream.substr(0, pos);int len = std::stoi(lenstr);// 计算一个完整的报文应该多长int total = lenstr.size() + 2 * sep.size() + len;if (packagestream.size() < total){return std::string();}// 提取std::string josonstr = packagestream.substr(pos + sep.size(), len);packagestream.erase(0, total);return josonstr;
}class Request
{public:Request() {}~Request() {}Request(int x, int y, char oper): _x(x), _y(y), _oper(oper){}// 序列化bool Serialize(std::string *out){// 使用joson库Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter write;std::string s = write.write(root);*out = s;return true;}// 反序列化bool Deserialize(const std::string &josonstr){Json::Value root;Json::Reader reader;bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return res;}void Print(){std::cout << _x << std::endl;std::cout << _y << std::endl;std::cout << _oper << std::endl;}int X(){return _x;}int Y(){return _y;}char Oper(){return _oper;}void SetValue(int x, int y, char oper){_x = x;_y = y;_oper = oper;}private:int _x;int _y;char _oper;
};// 应答
class Response
{
public:~Response() {}Response(): _result(0), _code(0), _desc("success"){}// 序列化bool Serialize(std::string *out){// 使用joson库Json::Value root;root["result"] = _result;root["code"] = _code;root["desc"] = _desc;Json::FastWriter write;std::string s = write.write(root);*out = s;return true;}// 反序列化bool Deserialize(const std::string &josonstr){Json::Value root;Json::Reader reader;bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中_result = root["result"].asInt();_code = root["code"].asInt();_desc = root["desc"].asString();return res;}void PrintResult(){std::cout << "result: " << _result << " code: " << _code << " desc: " << _desc << std::endl;}int _result;int _code;std::string _desc;
};class Factory
{
public:static std::shared_ptr<Request> BuildRequestDefault(){return make_shared<Request>();}static std::shared_ptr<Response> BuildResponseDefault(){return make_shared<Response>();}
};