📝个人主页🌹: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;// acceptvirtual 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(){} // 调用套接字接口进行封装// socketvoid CreateSocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0) exit(SocketError);}// bindvoid 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);}// listenvoid ListenSocketOrDie(int backlog) override{int n = listen(_sockfd, backlog);if (n < 0) exit(ListenError);}// acceptSocket *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;}// connectbool 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);}// recvbool 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;}// sendvoid 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下自定义应用层协议的道路上走得更远、更稳。
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!