目录
一、序列化与反序列化概念
二、自定协议实现一个加法网络计算器
(一)TCP如何保证接收方的接收到数据是完整性呢?
(二)自定义协议
(三)自定义协议的实现
1、基础类
2、序列化与反序列化
3、报头的增删
4、从缓冲区内提取完整报文
5、自定协议在服务器与客户端的实现
三、使用Json进行序列化和反序列化
(一)概念
(二)Json的安装
(三)针对序列化和反序列化的改造
一、序列化与反序列化概念
在之前的文章中,我们调用网络接口进行数据传输时都是使用字符串作为数据直接进行传输的,但是在一些场景中单个字符串是不能满足需要的,可能需要一个结构体或是数据结构,那么如何将结构体或数据结构进行网络传输呢?
序列化:将数据结构或对象转换为字节流,以便它们可以存储到磁盘上、通过网络传输或在不同的程序间共享;
反序列化:存储的字节流或文本数据重新转换为原来的数据结构或对象。这个过程用于恢复存储的或传输的数据,以便程序能再次使用它。
当我们需要发送一个结构体时,我们可以先将其进行序列化转换为字节流再进行网络传输,而接收方可以通过将其反序列化从而恢复传输的数据,以此达到数据传输的目的。
二、自定协议实现一个加法网络计算器
(一)TCP如何保证接收方的接收到数据是完整性呢?
UDP是面向数据报,而TCP是面向字节流的。
在UDP协议传输数据时,由于数据的发送和接收都是按数据报的格式进行的,每个数据报是独立传输的。虽然UDP协议本身不保证数据的完整性和可靠性,但在网络传输没有出现丢包或错误的情况下,数据是完整的。
但在使用TCP协议传输数据时,由于数据的发送和接收都是按字节流的形式进行的,发送和接收到的数据不一定是完整的,假如TCP的服务端读取速度小于TCP客户端的发送速度,那么在缓冲区一定堆积了大量的报文,那么如何从缓冲区内提取到一条完整的报文数据呢?
其实我们调用接口进行网络数据传输本质实际是拷贝。发送方发送数据,接收方接收数据,本质实际是发送方缓冲区内容拷贝给接收方缓冲区。
对于上述的问题,如果发送方发送数据的速度过快,导致接收方缓冲区内堆积了大量的报文,那么如何从大量数据中提取出一个完整的报文呢?
实际上可以定制协议,以下是协议设计方式:
- 定长(规定每个报文的固定长度)
- 特殊符合(在报文之间加上特殊符合用于分割报文)
- 自描述方式(数据本身描述其格式、大小等)
(二)自定义协议
本文采用自描述方式设计自定协议:
首先本文是针对加法计算器而做的协议,而这个协议不仅要包括数据的序列化和反序列化,还要有增添减少报头分割符的接口。除此之外,因为使用面向字节流进行输出,还要保证如何从缓冲区内提取一个完整的结构。
(三)自定义协议的实现
1、基础类
class Request
{
public:int _x; //左操作数int _y; //右操作数char _op; //操作符
};
class Response
{
public:int _exitcode; //退出码int _result; //计算结果
};
2、序列化与反序列化
class Request
{
public:Request() {}Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}//序列化bool serialize(string &out){out.clear();out += to_string(_x);out += SEP;out += _op;out += SEP;out += to_string(_y);return true;}//反序列化bool deserialize(const string &in){auto left = in.find(SEP);auto right = in.rfind(SEP);if (left == string::npos || right == string::npos || left == right)return false;if (right - left - SEP_LEN != 1)return false;string x_string = in.substr(0, left);string y_string = in.substr(right + SEP_LEN);if (x_string.empty() || y_string.empty())return false;_x = stoi(x_string);_y = stoi(y_string);_op = in[left + SEP_LEN];return true;}
public:int _x;int _y;char _op;
};
class Response
{
public:Response() {}Response(int exitcode, int result) : _exitcode(exitcode), _result(result) {}//序列化bool serialize(string &out){out.clear();out += to_string(_exitcode);out += SEP;out += to_string(_result);return true;}//反序列化bool deserialize(const string &in){auto index = in.find(SEP);if (index == string::npos)return false;string code = in.substr(0, index);string result = in.substr(index + SEP_LEN);if (code.empty() || result.empty())return false;_exitcode = stoi(code);_result = stoi(result);return true;}
public:int _exitcode;int _result;
};
3、报头的增删
#define SEP " "
#define SEP_LEN strlen(SEP)
#define SEP_LINE "\r\n"
#define SEP_LINE_LEN strlen(SEP_LINE)
//"text_len/r/text/r/n"
//增添报头
bool enLength(string &text)
{if (text.empty())return false;int len = text.size();string len_string = to_string(len);if (len_string.empty())return false;string ret;ret += len_string;ret += SEP_LINE;ret += text;ret += SEP_LINE;text = ret;return true;
}
//删除报头
bool deLength(const string &package, string &text)
{auto index = package.find(SEP_LINE);if (index == string::npos)return false;string len_string = package.substr(0, index);int len = stoi(len_string);text.clear();text = package.substr(index + SEP_LINE_LEN, len);return true;
}
4、从缓冲区内提取完整报文
bool recvPackage(const int &fd, string &inbuffer, string &text)
{while (true){char buffer[1024];ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;inbuffer += buffer;auto index = inbuffer.find(SEP_LINE);string len_string = inbuffer.substr(0, index);int len = stoi(len_string);int total = len + SEP_LINE_LEN * 2 + len_string.size();if (inbuffer.size() < total){cout << "正在等待后续消息" << endl;continue;}text = inbuffer.substr(0, total);inbuffer.erase(0, total);break;}elsereturn false;}return true;
}
5、自定协议在服务器与客户端的实现
//服务端
void handlerEntery(const int &fd, func_t func){string inbuffer;while (1){// 收到ReqRequest req;string req_str, req_text;if (recvPackage(fd, inbuffer, req_str) == false)return;// 去报头cout << "接收数据:" << req_str << endl;bool ret = deLength(req_str, req_text);if (ret == false)logMessage(ERROR, "Server : deLength fail");cout << "接收数据正文:" << req_text << endl;// 反序列化ret = req.deserialize(req_text);if (ret == false)logMessage(ERROR, "Server : deserialize fail");// 计算Response resp;func(req, resp);// 序列化string resp_text;ret = resp.serialize(resp_text);if (ret == false)logMessage(ERROR, "Server : deserialize fail");cout << "发送数据:" << resp_text << endl;// 加报头ret = enLength(resp_text);if (ret == false)logMessage(ERROR, "Server : enLength fail");cout << "发送数据正文:" << resp_text << endl;// 发送Respsend(fd, resp_text.c_str(), resp_text.size(), 0);}}
//客户端
void run(){struct sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(_Sip.c_str());addr.sin_port = htons(_Sport);if (connect(_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1){cerr << strerror(errno) << endl;exit(2);}else{string inbuffer;while (true){// 读取数据string line;cout << " please input#\n";getline(cin, line);Request req = parseLine(line);// 序列化string req_text;bool ret = req.serialize(req_text);if (ret == false)logMessage(ERROR, "Client : serialize fail");// 加报头ret = enLength(req_text);if (ret == false)logMessage(ERROR, "Client : enLength fail");// 发数据send(_fd, req_text.c_str(), req_text.size(), 0);// 接收数据string resp_str, resp_text;if (!recvPackage(_fd, inbuffer, resp_str))continue;// 去报头ret = deLength(resp_str, resp_text);if (ret == false)logMessage(ERROR, "Client : deLength fail");// 反序列化Response resp;ret = resp.deserialize(resp_text);if (ret == false)logMessage(ERROR, "Client : deserialize fail");cout << "exitcode :" << resp._exitcode << "\tresulet : " << resp._result << endl;}}}
三、使用Json进行序列化和反序列化
(一)概念
从上文可以得知,手动进行序列化和反序列化非常繁杂。实际上我们可以使用现成的方案。
Json是一种轻量级的数据交换格式,广泛用于网络应用中,尤其是在客户端与服务器之间的数据交换。它易于阅读和编写,同时也便于机器解析和生成。
(二)Json的安装
首先登录云服务器后输入指令:
sudo yum install -y jsonapp-devel
使用Json除了需要引入头文件以外还需要在编译中加入 -ljsoncpp 选项。
#include <jsoncpp/json/json.h>
g++ -g -o Server Server.cpp -std=c++11 -ljsoncpp
(三)针对序列化和反序列化的改造
class Request
{
public:Request() {}Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}//序列化bool serialize(string &out){Json::Value root;root["left"] = _x;root["right"] = _y;root["op"] = _op;Json::FastWriter writer;out = writer.write(root); return true;}//反序列化bool deserialize(const string &in){Json::Value root;Json::Reader reader;reader.parse(in, root);_x = root["left"].asInt();_y = root["right"].asInt();_op = root["op"].asInt();return true;}
public:int _x;int _y;char _op;
};
class Response
{
public:Response() {}Response(int exitcode, int result) : _exitcode(exitcode), _result(result) {}//序列化bool serialize(string &out){Json::Value root;root["exitcode"] = _exitcode;root["result"] = _result;Json::FastWriter writer;out = writer.write(root);return true;}//反序列化bool deserialize(const string &in){Json::Value root;Json::Reader reader;reader.parse(in, root);_exitcode = root["exitcode"].asInt();_result = root["result"].asInt();return true;}public:int _exitcode;int _result;
};