【Linux网络】自定义应用层协议 (序列化)
📝个人主页🌹:Eternity._
⏩收录专栏⏪:Linux “ 登神长阶 ”
🌹🌹期待您的关注 🌹🌹
❀Linux自定义应用层协议
- 📒1. 封装 Socket
- 📜2. 自定义应用层协议
- 🌞应用层
- ⭐自定义协议 --- TCP
- 📚3. 网络版计算器
- 📖4. 总结
前言:应用层协议,作为网络通信架构中的最高层,直接与用户应用交互,负责数据的格式化和传输控制。通过自定义应用层协议,开发者可以实现特定业务需求,提高数据传输效率,增强系统的安全性和可扩展性。
本文旨在为广大Linux开发者提供一份关于自定义应用层协议的全面学习指南。我们将从协议设计的基本原理出发,深入探讨Linux系统下的网络通信机制,详细解析自定义协议的实现步骤和调试技巧,并分享一些实际案例和最佳实践。
📒1. 封装 Socket
首先,在自定义应用层协议前,我们先来封装一下 Socket 来简化网络通信的复杂性,并且我们可以提供更强的可维护性和可扩展性,以便更容易管理和使用网络连接的编程技术。在套接字编程TCP中,我们固定的步骤是:创建,连接,监听。我们可以先确定基类Socket
基类:Socket
const int backlog = 3;
class Socket
{
public:
~Socket()
{}
// 创建
virtual void CreateSocketOrDie() = 0;
// 连接
virtual void BindSocketOrDie(uint16_t port) = 0;
// 监听
virtual void ListenSocketOrDie(int backlog) = 0;
// accept
virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
// connect服务
virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;
// 获取文件描述符
virtual int GetSocket() = 0;
// 创建文件描述符
virtual void SetSocket(int sockfd) = 0;
// 关闭文件描述符
virtual void CloseSocket() = 0;
// 接收,发送信息
virtual bool Recv(std::string *buffer, int size) = 0;
virtual void Send(std::string &send_str) = 0;
public:
void BuildListenSocketMethod(uint16_t port, int backlog)
{
CreateSocketOrDie();
BindSocketOrDie(port);
ListenSocketOrDie(backlog);
}
bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport)
{
CreateSocketOrDie();
return ConnectServer(serverip, serverport);
}
void BuildNOrmalSocketMethod(int sockfd)
{
SetSocket(sockfd);
}
};
子类:TcpSocket
const static int defaultsockfd = -1; // 定义一个缺省值
#define Convert(addrptr) ((struct sockaddr *)(addrptr)) // 定义宏用于强制类型转换
// 错误码
enum
{
SocketError = 1,
BindError,
ListenError,
};
class TcpSocket : public Socket
{
public:
// 构造与析构
TcpSocket(int sockfd = defaultsockfd)
: _sockfd(sockfd)
{}
~TcpSocket()
{}
// 调用套接字接口进行封装
// socket
void CreateSocketOrDie() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0) exit(SocketError);
}
// bind
void BindSocketOrDie(uint16_t port) override
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
int n = ::bind(_sockfd, Convert(&local), sizeof(local));
if (n < 0) exit(BindError);
}
// listen
void ListenSocketOrDie(int backlog) override
{
int n = listen(_sockfd, backlog);
if (n < 0) exit(ListenError);
}
// accept
Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
if (newsockfd < 0) return nullptr;
*peerport = ntohs(peer.sin_port);
*peerip = inet_ntoa(peer.sin_addr);
Socket *s = new TcpSocket(newsockfd);
return s;
}
// connect
bool ConnectServer(std::string &serverip, uint16_t serverport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
int n = ::connect(_sockfd, Convert(&server), sizeof(server));
if (n == 0) return true;
else return false;
}
int GetSocket() override
{
return _sockfd;
}
void SetSocket(int sockfd) override
{
_sockfd = sockfd;
}
void CloseSocket() override
{
if (_sockfd > defaultsockfd)
::close(_sockfd);
}
// recv
bool Recv(std::string *buffer, int size) override
{
char inbuffer[size];
ssize_t n = recv(_sockfd, inbuffer, size-1, 0);
if(n > 0)
{
inuffer[n] = 0;
*buffer += inbuffer;
return true;
}
else if(n < 0) return false;
else return false;
}
// send
void Send(std::string &send_str) override
{
send(_sockfd, send_str.c_str(), send_str.size(), 0);
}
private:
int _sockfd;
};
在封装Socket都是我们之前学习常见的方式,我们熟悉之后,就可以很轻松的的使用了。我们可以用前面的知识,建立一个服务端,用户端进行连接访问
📜2. 自定义应用层协议
🌞应用层
Linux网络应用层是网络通信架构中的关键组成部分,它直接与用户应用交互,并负责数据的格式化和传输控制。
在应用层,数据通常以结构化的形式存在,在传输之前,这些数据需要被 序列化
成字符串形式,以便在网络中传输。接收方在收到数据后,再进行 反序列化
操作,将数据还原为原始的结构化形式。
⭐自定义协议 — TCP
- 协议设计:根据业务需求设计协议的数据格式、传输方式和控制信息。确保协议具有足够的灵活性和可扩展性。
- 协议实现:使用编程语言(如C、C++、Python等)实现协议的数据封装、解析和传输功能。通常需要使用套接字编程接口来与底层网络通信机制进行交互。
- 协议结构
- 协议头:通常包含版本号和协议长度等信息。我们这里简单实现一下,所以用的是协议长度
- 协议体:包含实际传输的数据。
- 序列化和反序列化
- 序列化:将计算机语言中的内存对象转换为网络字节流的过程。
- 反序列化:将网络字节流转换为计算机语言中的内存对象的过程。
我们今天自定义应用层协议是针对网络版计算器而言的,所以我们依据 “先描述,再组织”
,先构建两个结构体来描述输入(Request) 和 输出(Response) 以及 工厂(Factory) 来快速构建输入输出
输入(Request):
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
class Request
{
public:
Request() : _data_x(0), _data_y(0), _oper(0)
{}
Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op)
{}
// 结构化数据 -> 字符串
bool Serialize(std::string *out)
{
*out = std::to_string(_data_x) + ProtSep + _oper + ProtSep + std::to_string(_data_y);
return true;
}
// 字符串 -> 结构化数据
bool Deserialize(std::string &in) // "x op y"
{
auto left = in.find(ProtSep);
if (left == std::string::npos)
return false;
auto right = in.rfind(ProtSep);
if (right == std::string::npos)
return false;
_data_x = std::stoi(in.substr(0, left));
_data_y = std::stoi(in.substr(right + ProtSep.size()));
std::string oper = in.substr(left + ProtSep.size(), right - (left + ProtSep.size()));
if (oper.size() != 1) return false;
_oper = oper[0];
return true;
}
int GetX() { return _data_x; }
int GetY() { return _data_y; }
char GetOper() { return _oper; }
private:
// _data_x _oper _data_y
// 报文的自描述字段
// "len\nx op y\n" : \n不属于报文的一部分, 约定
int _data_x; // 第一个参数
int _data_y; // 第二个参数
char _oper; // + - * / %
};
输出(Response):
class Response
{
public:
Response() : _result(0), _code(0)
{}
Response(int result, int code)
: _result(result), _code(code)
{}
// 结构化数据 -> 字符串
bool Serialize(std::string *out)
{
*out = std::to_string(_result) + ProtSep + std::to_string(_code);
return true;
}
bool Deserialize(std::string &in) // "_result _code"
{
auto pos = in.find(ProtSep);
if (pos == std::string::npos)
return false;
_result = std::stoi(in.substr(0, pos));
_code = std::stoi(in.substr(pos + ProtSep.size()));
return true;
}
void SetResult(int result) { _result = result; }
void SetCode(int code) { _code = code; }
int GetResule() { return _result; }
int GetCode() { return _code; }
private:
int _result; // 运算结果
int _code; // 运算状态
};
工厂(Factory):
class Factory
{
public:
std::shared_ptr<Request> BuildRequest()
{
std::shared_ptr<Request> req = std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuildRequest(int x, int y, char op)
{
std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
return req;
}
std::shared_ptr<Response> BuildResponse()
{
std::shared_ptr<Response> resp = std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuildResponse(int result, int code)
{
std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
return resp;
}
};
在解决完协议主要的步骤后,我们需要解决协议结构,报头 + 报文
,因此我们需要让它们变成我们想要的样子,在Protocol.hpp
中封装两个解决协议结构的函数
给报文加一个内容长度的报头:
// "len\nx op y\n" : \n不属于报文的一部分, 约定
// 给报文加一个内容长度的报头
std::string Encode(const std::string &message)
{
std::string len = std::to_string(message.size());
std::string package = len + LineBreakSep + message + LineBreakSep;
return package;
}
无法保证package就是一个完整独立的报文:
bool Decode(std::string &package, std::string *message)
{
// 判断报文的完整性
auto pos = package.find(LineBreakSep);
if (pos == std::string::npos)
return false;
std::string lens = package.substr(0, pos);
int messagelen = std::stoi(lens);
int total = lens.size() + messagelen + 2 * LineBreakSep.size();
if (package.size() < total)
return false;
// 至少package中有一个完整的报文
*message = package.substr(pos + LineBreakSep.size(), messagelen);
package.erase(0, total);
return true;
}
📚3. 网络版计算器
Calculate:
enum{
Success = 0,
DivZeroErr,
ModZeroErr,
UnKnowOper,
};
class Calculate
{
public:
Calculate()
{}
std::shared_ptr<Protocol::Response> Cal(std::shared_ptr<Protocol::Request> req)
{
std::shared_ptr<Protocol::Response> resp = factory.BuildResponse();
resp->SetCode(Success);
switch(req->GetOper())
{
case '+':
resp->SetResult(req->GetX() + req->GetY());
break;
case '-':
resp->SetResult(req->GetX() - req->GetY());
break;
case '*':
resp->SetResult(req->GetX() * req->GetY());
break;
case '/':
{
if(req->GetY() == 0)
{
resp->SetCode(DivZeroErr);
}
else
{
resp->SetResult(req->GetX() / req->GetY());
}
}
break;
case '%':
{
if(req->GetY() == 0)
{
resp->SetCode(ModZeroErr);
}
else
{
resp->SetResult(req->GetX() % req->GetY());
}
}
break;
default:
resp->SetCode(UnKnowOper);
break;
}
return resp;
}
~Calculate()
{}
private:
Protocol::Factory factory;
};
在构建好以上的代码时,我们只需要在服务器与客户端中展开使用即可,这里就不过多展开,唯一值得注意的就是我们在使用线程时的回调函数,以及我们对客户请求的处理
TcpServerMain.cc
TcpServer.hpp
TcpClientMain.cc
自定义应用层协议代码仓库
网络版计算器
📖4. 总结
回顾整个学习过程,我们不难发现,自定义应用层协议是一项复杂而细致的工作。它要求我们具备全面的知识体系,从底层网络通信到高层应用逻辑,每一个环节都需要我们投入大量的时间和精力去学习和实践。然而,正是这份努力和坚持,让我们在解决问题的过程中不断成长,收获了宝贵的知识和经验。
在此,我们衷心希望本文能够为你提供一份有价值的参考和启示,帮助你在Linux下自定义应用层协议的道路上走得更远、更稳。
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!