⭐小白苦学IT的博客主页⭐
⭐初学者必看:Linux操作系统入门⭐
⭐代码仓库:Linux代码仓库⭐
❤关注我一起讨论和学习Linux系统
1.引言
协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?
在数据通信和程序交互的世界里,自定义协议是沟通的桥梁,而序列化和反序列化则是这座桥梁上的重要交通枢纽。它们将复杂的数据结构转化为可传输和可存储的格式,为数据在不同系统间的流通提供了可能。本文将深入探讨序列化和反序列化的概念,并通过一个实际案例来加深理解,最后给出具体的代码实现。
2.概念解析
2.1自定义协议
自定义协议,顾名思义,就是根据特定需求定制的通信协议。在数据交互频繁的现代应用中,我们往往需要定义一套或多套协议来规范数据的格式、传输方式和处理逻辑。这些协议可以是简单的文本协议,也可以是复杂的二进制协议,关键在于它们能够满足我们的实际需求。
为什么我们需要自定义协议呢?
- 满足特定需求:不同的应用或系统对数据的处理方式和传输要求可能各不相同。通过自定义协议,我们可以根据实际需求来定义数据的格式和传输方式,以满足特定的业务需求。
- 优化性能:自定义协议可以根据网络条件、数据类型和传输需求等因素进行优化,以提高数据传输的效率和稳定性。
- 保障安全性:通过自定义协议,我们可以加入数据加密、身份验证等安全措施,以保障数据在传输过程中的安全性和完整性。
2.2序列化和反序列化
序列化
序列化是指将数据结构或对象状态转换为可以存储或传输的格式的过程。这个过程会将数据结构或对象转换为一个连续的字节序列,即所谓的序列化形式。这样,数据就可以被写入持久存储,如硬盘,或者通过网络进行传输。
序列化的主要目的有以下几点:
数据传输:通过网络将数据结构发送到其他系统或应用程序,实现跨平台、跨语言的数据交换。
数据持久化:将数据结构保存到文件或数据库中,以便在需要时重新加载和恢复数据状态。
常见的序列化方法包括:
- JSON序列化:将数据结构转换为JSON格式的字符串,易于阅读和解析。
- Protocol Buffers(protobuf)序列化:由Google开发的一种二进制数据序列化格式,具有高效的编码和解码性能。
- XML序列化:将数据结构转换为XML格式的字符串,适用于需要跨平台交互的场景。
反序列化
反序列化是序列化的逆过程。它读取序列化后的字节序列,并将其恢复为原始的数据结构或对象状态。反序列化的过程需要遵循与序列化相同的协议和规则,以确保数据的正确性和完整性。
反序列化的作用主要有以下几点:
- 数据还原:将接收到的序列化数据还原为原始的数据结构,以便在本地系统或应用程序中进行处理。
- 对象重建:根据序列化数据中保存的对象状态,重新构建出完整的对象实例。
自定义协议的重要性,以及序列化和反序列化在通信协议中的核心作用。
自定义协议的重要性体现在多个方面。首先,自定义协议能够根据特定应用的需求,精确定义数据的格式、传输方式和处理逻辑,从而确保数据在不同系统间的准确流通。这对于提升系统间的交互效率、保障数据的一致性和安全性至关重要。其次,自定义协议还可以增强系统的灵活性和扩展性,使得系统能够应对未来可能出现的新需求和新挑战。
序列化和反序列化在通信协议中扮演着核心的角色。序列化是将数据结构或对象状态转换为可存储或传输的格式的过程,而反序列化则是将序列化后的数据恢复为原始数据结构或对象状态的过程。这两个过程在数据通信中起着桥梁的作用,使得数据能够在不同系统间进行无障碍的传输和交换。
具体来说,序列化使得数据能够被写入持久存储或通过网络发送,从而实现了数据的跨系统流通。而反序列化则确保了接收方能够正确解析和处理接收到的数据,还原出原始的数据结构或对象状态,进而进行相应的业务逻辑处理。
因此,序列化和反序列化是通信协议中不可或缺的一部分,它们保障了数据的正确性和完整性,提升了系统间的交互效率和可靠性。在实际应用中,我们需要根据具体需求来设计和实现自定义协议中的序列化和反序列化过程,以确保数据在不同系统间的准确、高效流通。
反序列化与序列化是紧密相关的,它们共同构成了数据通信和持久化的基础。通过序列化和反序列化,我们可以实现数据的跨平台、跨语言传输和交换,以及数据的持久化保存和恢复。
2.3用一个故事来理解以上概念
故事时刻
在《王者荣耀》这款游戏中,我们可以借助自定义协议、序列化与反序列化的概念来更好地理解其背后的数据通信和交换机制。
首先,我们来看自定义协议。在《王者荣耀》中,游戏开发者根据游戏的特性和需求,定义了一套独特的通信协议。这套协议规定了游戏中各种数据(如玩家信息、游戏状态、操作指令等)的格式、传输方式和处理逻辑。正是这套自定义协议,确保了游戏内数据的准确、高效流通,为玩家提供了流畅的游戏体验。
接下来,我们来看序列化。在《王者荣耀》中,当玩家进行游戏时,他们的操作、状态以及游戏内的各种事件都会产生大量的数据。这些数据需要被转换为一种可传输的格式,以便在网络上进行传输。这就是序列化的过程。例如,玩家的移动指令、技能释放等操作,都会被游戏系统序列化为特定的字节序列,然后通过网络发送给服务器。
服务器在接收到这些序列化后的数据后,需要进行反序列化的过程。反序列化是将接收到的字节序列还原为原始的数据结构或对象状态的过程。在《王者荣耀》中,服务器会根据自定义协议中规定的格式和逻辑,将接收到的字节序列反序列化为具体的操作指令或游戏事件。这样,服务器就能准确地理解玩家的操作意图,并据此更新游戏状态。
通过序列化和反序列化的过程,游戏中的数据能够在玩家和服务器之间进行无障碍的传输和交换。这使得玩家能够实时地与服务器进行交互,获得及时的游戏反馈和体验。
总的来说,自定义协议、序列化和反序列化在《王者荣耀》这款游戏中发挥着重要的作用。它们确保了游戏内数据的准确、高效流通,为玩家提供了流畅、稳定的游戏体验。
3.网络版计算器(完整代码在gitee仓库中)
自定义协议代码(序列化和反序列化)
Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
std::string Encode(std::string &content)
{
std::string package = std::to_string(content.size());
package += protocol_sep;
package += content;
package += protocol_sep;
return package;
}
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if (pos == std::string::npos)
return false;
std::string str_len = package.substr(0, pos);
std::size_t len = std::stoi(str_len);
std::size_t total_len = str_len.size() + len + 2;
if (package.size() < total_len)
return false;
*content = package.substr(pos + 1, len);
package.erase(0, total_len);
return true;
}
class Request
{
public:
Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper)
{
}
Request()
{
}
~Request()
{
}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// 构建报文的有效载荷
std::string s = std::to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += std::to_string(y);
// 封装报文
*out = s;
return true;
#else
Json::Value root;
root["x"] = x;
root["y"] = y;
root["op"] = op;
Json::FastWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in)
{
#ifdef MySelf
std::size_t left = in.find(blank_space_sep);
if (left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
std::size_t right = in.rfind(blank_space_sep);
if (right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
op = in[left + 1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
x = root["x"].asInt();
y = root["y"].asInt();
op = root["op"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "请求构建完成: " << x << op << y << "=?" << std::endl;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response()
{
}
Response(int res, int c) : result(res), code(c)
{
}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
// 构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s += std::to_string(code);
// 封装报文
*out = s;
return true;
#else
Json::Value root;
root["result"] = result;
root["code"] = code;
Json::FastWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in)
{
#ifdef MySelf
std::size_t pos = in.find(blank_space_sep);
if (pos == std::string::npos)
{
return false;
}
std::string left = in.substr(0, pos);
std::string right = in.substr(pos + 1);
result = std::stoi(left);
code = std::stoi(right);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
result = root["result"].asInt();
code = root["code"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "结果响应完成: result: " << result << ", code: " << code << std::endl;
}
public:
int result;
int code;
};
ServerCal.hpp
#pragma once
#include<iostream>
#include"Protocol.hpp"
enum
{
Div_ZERO = 1,
Model_ZERO,
Other_Oper
};
class ServerCal
{
public:
ServerCal(){}
Response CalculatorHelper(const Request & req)
{
Response resp(0,0);
switch (req.op)
{
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 = Div_ZERO;
else resp.result = req.x / req.y;
}
break;
case '%':
{
if(req.y == 0)resp.code = Model_ZERO;
else resp.result = req.x % req.y;
}
break;
default:
resp.code = Other_Oper;
break;
}
return resp;
}
std::string Calculator(std::string & package)
{
std::string content;
bool r = Decode(package,&content);
if(!r) return "";
Request req;
r = req.Deserialize(content);
if(!r) return "";
content = "";
Response resp = CalculatorHelper(req);
resp.Serialize(&content);
content = Encode(content);
return content;
}
~ServerCal(){}
};
TcpServer.hpp
#pragma once
#include"Log.hpp"
#include"Socket.hpp"
#include<signal.h>
#include<functional>
using func_t = std::function<std::string (std::string & package)>;
class TcpServer
{
public:
TcpServer(){}
TcpServer(const uint16_t & port,func_t fun):_port(port),callback_(fun)
{}
bool ServerInit()
{
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
log.LogMessage(INFO,"server init ... done");
return true;
}
void Start()
{
signal(SIGCHLD,SIG_IGN);
signal(SIGPIPE,SIG_IGN);
while(true)
{
std::string serverip;
uint16_t serverport;
int sockfd = _listensock.Accept(&serverip,&serverport);
if(sockfd<0)
{
continue;
}
log.LogMessage(INFO,"accept a new link,sockfd: %d, clientip: %s, clientport: %d",sockfd,serverip.c_str(),serverport);
//提供服务
if(fork() == 0)
{
_listensock.Close();
std::string inbuffer_stream;
//数据计算
while(true)
{
char buffer[128];
ssize_t n = read(sockfd,buffer,sizeof(buffer));
if(n>0)
{
buffer[n] = 0;
inbuffer_stream+=buffer;
log.LogMessage(DEBUG,"\n%s ",inbuffer_stream.c_str());
std::string info = callback_(inbuffer_stream);
if(info.empty()) continue;
write(sockfd,info.c_str(),info.size());
}
else if(n == 0) break;
else break;
}
exit(0);
}
close(sockfd);
}
}
~TcpServer(){}
private:
uint16_t _port;
Sock _listensock;
func_t callback_;
};
Socket.hpp(套接字封装)
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include <cstring>
enum
{
SocketErr = 2,
BindErr,
ListenErr,
};
const int backlog = 10;
class Sock
{
public:
Sock() {}
~Sock() {}
public:
void Socket()
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
log.LogMessage(FATAL, "socket error , %s : %d", strerror(errno), errno);
exit(SocketErr);
}
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(_sockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
log.LogMessage(FATAL, "bind error , %s : %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen()
{
if(listen(_sockfd,backlog)<0)
{
log.LogMessage(FATAL, "listen error , %s : %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(std::string *clientip,uint16_t * clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(_sockfd,(struct sockaddr*)&peer,&len);
if(newfd<0)
{
log.LogMessage(WARNING,"accep error, %s : %d",strerror(errno),errno);
return -1;
}
*clientport = ntohs(peer.sin_port);
char ipstr[64];
inet_ntop(AF_INET,&(peer.sin_addr),ipstr,sizeof(ipstr));
*clientip = ipstr;
return newfd;
}
int Connect(const std::string & clientip,const uint16_t & clientport)
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(clientport);
inet_pton(AF_INET,clientip.c_str(),&(peer.sin_addr));
int n = connect(_sockfd,(struct sockaddr*)&peer,sizeof(peer));
if(n<0)
{
std::cerr<<"connect to error ..."<<std::endl;
return false;
}
return true;
}
int GetFd()
{
return _sockfd;
}
void Close()
{
close(_sockfd);
}
private:
int _sockfd;
};
ServerCal.cc
#include"TcpServer.hpp"
#include"Protocol.hpp"
#include"ServerCal.hpp"
static void Usage(const std::string & proc)
{
std::cout<<"\nUsage: "<<proc<<" port\n\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
ServerCal cal;
TcpServer * tsvp = new TcpServer(8080,std::bind(&ServerCal::Calculator,&cal,std::placeholders::_1));
tsvp->ServerInit();
tsvp->Start();
return 0;
}
ClientCal.cc
#include<iostream>
#include<string>
#include"Socket.hpp"
#include"Protocol.hpp"
#include<ctime>
#include<unistd.h>
#include<cassert>
static void Usage(const std::string & proc)
{
std::cout<<"\nUsage: "<<proc<<" serverip serverport\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool r = sockfd.Connect(serverip,serverport);
if(!r) return 1;
srand(time(nullptr) ^ getpid());
int cnt = 1;
const std::string opers = "+-*/%=&^#~";
std::string inbuffer_stream;
while(cnt<=20)
{
std::cout<<"===============第"<<cnt<<"次测试.....==============="<<std::endl;
int x = rand()%100+1;
usleep(1234);
int y = rand()%100;
usleep(4124);
char oper = opers[rand()%opers.size()];
Request req(x,y,oper);
req.DebugPrint();
std::string package;
req.Serialize(&package);
package = Encode(package);
std::cout<<"这是新发出的请求: \n"<<package;
write(sockfd.GetFd(),package.c_str(),package.size());
char buffer[128];
ssize_t n = read(sockfd.GetFd(),buffer,sizeof(buffer));
if(n>0)
{
buffer[n] = 0;
inbuffer_stream+=buffer;
std::cout<<inbuffer_stream;
std::string content;
bool r= Decode(inbuffer_stream,&content);
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
std::cout<<"====================================="<<std::endl;
sleep(1);
cnt++;
}
sockfd.Close();
return 0;
}
Makefile
.PHONY:all
all:servercal clientcal
Flag=#-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal
运行结果: