当前位置: 首页 > article >正文

【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


  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.kler.cn/a/404499.html

相关文章:

  • 算法编程题-排序
  • 卷积神经网络各层介绍
  • Spark RDD(弹性分布式数据集)的深度理解
  • 机器学习day6-线性代数2-梯度下降
  • 键盘上打出反引号符号(´),即单个上标的撇号(这个符号与反引号 ` 不同,反引号通常位于键盘的左上角)
  • C# Postman或者PostApi调试前端webapi接口发送带有request/body/head信息
  • ATmaga8单片机Pt100温度计源程序+Proteus仿真设计
  • C/C++ 中的类型转换方式
  • 基于Qt/C++/Opencv实现的一个视频中二维码解析软件
  • Flutter在MaterialApp中的builder初始化多个包
  • Linux环境下的基础开发工具 -- 包管理器,vim,gcc/g++,make/makefile,git,gdb/cgdb
  • EcoVadis审核是什么?EcoVadis审核流程包括什么?
  • STM32H7开发笔记(2)——H7外设之多路定时器中断
  • 实验室管理解决方案:Spring Boot技术
  • 网络安全等级保护五个保护等级
  • 经验笔记:git checkout 与 git switch
  • 【智谱开放平台-注册_登录安全分析报告】
  • 单体架构和微服务架构到底哪个好?
  • 怎么编译OpenWrt镜像?-基于Widora开发板
  • Linux驱动编程 - kmalloc、vmalloc区别
  • 多线程中Callable和Runnable的对比
  • 力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
  • MySQL45讲 第二十八讲 读写分离有哪些坑?——阅读总结
  • 第 24 章 -Golang 性能优化
  • 【C++入门(一)】半小时入门C++开发(深入理解new+List+范围for+可变参数)
  • 【GPTs】Front-end Expert:助力前端开发的智能工具