认识协议
协议(Protocol) 是一种通信规则或标准,用于定义通信双方或多方之间如何交互和传输数据。在计算机网络和通信系统中,协议规定了通信实体之间信息交换的格式、顺序、定时以及有关同步等事宜的约定。简易来说协议就是通信双方所约定好的结构化字段。
序列化与反序列化的认识
当结构化字段在网络进行传输的过程中可能会由于平台和语言的差异性导致发送方和接收方对于同一个结构化字段的读取方式不同(如内存存储差异、数据格式差异)所以在针对结构化数据进行网络收发时会先将其序列化成字节流然后再进行发送到发送缓冲区中进行发送。然后接收端在接收缓冲区中收到数据以后再将字节流格式的数据反序列化成结构化字段数据,从而实现数据准确的接受与发送。其实序列化同时也间接的提高了数据的传输效率,因为序列化后的字段通常是压缩成更加紧凑的字节流形式,从而减少了数据量,进而提高传输效率。
自定义协议实现网络版本计算器
在自定义协议时必须让客户端与服务器都能够看到同一份协议字段,这样才能保证在发送接收数据的时候按照规定的格式进行准确无误的发收。
自定义协议
对于我们实现的协议不仅仅有序列化结构体字段,其实还有报头的封装,因为tcp是有连接的,数据是流式传输的,所以传输过程并不是每次发送时将想要发送的数据完整的发送到对端的接收端缓冲区。其实准确来说,应用层在对数据进行序列化打包以后其实是将数据发送到TCP缓冲区内部的,然后在TCP缓冲区内部进行数据的流量控制等处理以便能够可靠传输,再然后就是将部分数据打包好再向下递交。所以接收方将数据接收在对应的TCP接受缓冲区时并不能保证将完整的信息全部读取到内存缓冲区中,所以此时就需要解决粘包问题(实际就是将数据进行解析拆分组装),也就是通过报头的数据长度字段来获取完整的一次数据请求。一般可以采用报头中的定长字段或特殊标识字符结尾的方式来将每一次的完整数据进行分离从而解决粘包问题。
以下是采用包头中的定长字段进行解决数据粘包的问题:
#pragma once
#include <iostream>
#include <string>
using namespace std;
// 模拟定协议
const string sepa = " ";
const string line_break = "\n";
// 添加报头数据和解报头
void Encode(string &mes)
{
int len = mes.size();
string ret = to_string(len) + line_break + mes + line_break;
mes = ret;
}
bool Decode(string &package, string &ret)
{
// 格式:"len\n数据信息\n"
// 先判断收到的数据中是否可以提炼出一次完整的数据请求
int len = package.size();
int pos = package.find(line_break, 0); // 指向第一个换行符
if (pos == string::npos)
return false;
int data_size = stoi(package.substr(0, pos));
int sz = pos + line_break.size() + data_size + line_break.size();
if (sz > len)
return false;
// 解包后的数据
ret = package.substr(pos + line_break.size(), data_size);
// 去掉被读走的数据
package = package.substr(sz);
return true;
}
class Request
{
friend class Cal;
public:
Request() {}
Request(int x, int y, char op)
: _x(x), _y(y), _oper(op)
{
}
void info()
{
cout << _x << ' ' << _oper << ' ' << _y << " = ?" << endl;
}
void Serialize(string &out) // 序列化
{
out = to_string(_x) + sepa + _oper + sepa + to_string(_y);
}
//"x + y"
void Deserialize(const string s) // 反序列化
{
int begin = 0;
int end = s.find(sepa, begin);
_x = stoi(s.substr(begin, end - begin));
begin = end + sepa.size(); // 加的1其实就是' '的长度
end = s.find(sepa, begin);
_oper = s.substr(begin, end - begin)[0];
begin = end + sepa.size();
_y = stoi(s.substr(begin));
}
private:
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response()
{
}
Response(int re, string ret_info)
: _result(re), _ret_info(ret_info)
{
}
void Info()
{
cout <&