【Linux网络】自定义应用层协议 (序列化)

server/2024/11/25 19:33:29/

📝个人主页🌹: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


  1. 协议设计:根据业务需求设计协议的数据格式、传输方式和控制信息。确保协议具有足够的灵活性和可扩展性。
  2. 协议实现:使用编程语言(如C、C++、Python等)实现协议的数据封装、解析和传输功能。通常需要使用套接字编程接口来与底层网络通信机制进行交互。
  3. 协议结构
  • 协议头:通常包含版本号和协议长度等信息。我们这里简单实现一下,所以用的是协议长度
  • 协议体:包含实际传输的数据。
  1. 序列化和反序列化
  • 序列化:将计算机语言中的内存对象转换为网络字节流的过程。
  • 反序列化:将网络字节流转换为计算机语言中的内存对象的过程。

在这里插入图片描述
我们今天自定义应用层协议是针对网络版计算器而言的,所以我们依据 “先描述,再组织”,先构建两个结构体来描述输入(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下自定义应用层协议的道路上走得更远、更稳。

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述


http://www.ppmy.cn/server/144878.html

相关文章

【Linux】冯诺依曼体系结构

目录 一、冯诺依曼体系结构二、冯诺依曼体系结构的基本组成三、关于冯诺依曼体系结构的一些问题结尾 一、冯诺依曼体系结构 冯诺依曼体系结构&#xff0c;也称为普林斯顿结构&#xff0c;是现代计算机设计的基础框架。这一体系结构由数学家冯诺依曼在20世纪40年代提出&#xf…

前端css 实现 背景渐变,边框渐变

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>CSS 渐变背景和边框</title><…

怎么在宿主机上通过ssh连接虚拟机 VirtualBox 中的linux系统

通过 Xshell 连接 VirtualBox 中的 linux 虚拟机&#xff0c;您需要确保以下几个步骤都正确配置&#xff1a; 1. 配置 VirtualBox 网络 您需要将 VirtualBox 虚拟机的网络适配器设置为支持 SSH 连接的模式&#xff1a; 打开 VirtualBox&#xff0c;选择您的 Ubuntu 虚拟机&am…

详细教程-Linux上安装单机版的Hadoop

1、上传Hadoop安装包至linux并解压 tar -zxvf hadoop-2.6.0-cdh5.15.2.tar.gz 安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1u59OLTJctKmm9YVWr_F-Cg 提取码&#xff1a;0pfj 2、配置免密码登录 生成秘钥&#xff1a; ssh-keygen -t rsa -P 将秘钥写入认…

C0034.在Ubuntu中安装的Qt路径

Qt安装路径查询 在终端输入qmake -v如上中/usr/lib/x86_64-linux-gnu就是Qt的安装目录&#xff1b;

索贝融媒体 Sc-TaskMonitoring/rest/task/search SQL注入漏洞复现

0x01 产品简介 索贝融媒体产品是成都索贝数码科技股份有限公司(简称索贝)为各级电视台和媒体机构打造的一套集互联网和电视融合生产的解决方案。其代表产品为MCH2.0融合媒体生产业务系统,该系统带来了媒体领域一种全新的融合生产流程和工作机制,具有全方位的资源汇聚能力、…

Java开发经验——SpringRestTemplate常见错误

摘要 本文分析了在使用Spring框架的RestTemplate发送表单请求时遇到的常见错误。主要问题在于将表单参数错误地以JSON格式提交&#xff0c;导致服务器无法正确解析参数。文章提供了错误案例的分析&#xff0c;并提出了修正方法。 1. 表单参数类型是MultiValueMap RestControl…

Linux 进程概念与进程状态

目录 1. 冯诺依曼体系结构2. 操作系统&#xff08;Operator System&#xff09;2.1 概念2.2 设计OS的目的2.3 系统调用和库函数概念 3. 进程概念3.1 描述进程 - PCB3.2 task_struct3.3 查看进程3.4 通过系统调用获取进程标识符PID&#xff0c; PPID3.5 通过系统调用创建fork 4.…