0


【在Linux世界中追寻伟大的One Piece】应用层自定义协议|序列化

1 -> 应用层

应用层是OSI模型或TCP/IP模型中的最高层,它直接为用户的应用程序提供网络服务。应用层的主要功能包括:

  • 用户交互:提供用户与计算机网络交互的界面,允许用户访问网络资源、发送和接收数据、运行应用程序等。
  • 数据处理:允许用户在端系统上进行文档编辑、数据存储和处理等操作。
  • 网络通信:通过网络接口与计算机网络进行通信,发送和接收数据,与其他端系统或网络设备进行交互。
  • 支持多种网络应用模型:如客户/服务器模型(C/S模型)和对等网络模型(P2P模型),这些模型定义了应用程序之间通信和服务提供的方式。
  • 提供网络服务:如域名解析系统(DNS)、文件传输协议(FTP)、电子邮件传输协议(SMTP、POP3、IMAP)和超文本传输协议(HTTP)等。
  • 数据表示和转换:确保不同系统和应用程序之间的数据能够正确理解和处理,包括数据格式转换、字符编码、数据压缩和数据加密。
  • 会话管理:管理应用程序之间的会话,包括会话的建立、维护和终止。
  • 错误处理和恢复:处理通信过程中的错误,并提供相应的恢复机制。
  • 用户接口:提供图形用户界面(GUI)或命令行界面(CLI),使用户能够方便地使用网络服务。

2 -> 网络版计算器

例如,我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去,然后由服务器进行计算,最后再把结果返回给客户端。

约定方案一:

  • 客户端发送一个形如"1+1"的字符串。
  • 这个字符串中有两个操作数,都是整形。
  • 两个数字之间会有一个字符是运算符,运算符只能是+。
  • 数字和运算符之间没有空格。

约定方案二:

  • 定义结构体来表示我们需要交互的信息。
  • 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体。
  • 这个过程叫做"序列化"和"反序列化"。

3 -> 序列化与反序列化

无论我们采用方案一,还是方案二,还是其他的方案,只要保证,一端发送时构造的数据,在另一端能够正确的进行解析,就是OK的。这种约定,就是应用层协议

但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

  • 采用方案2,我们也要体现协议定制的细节。
  • 引入序列化和反序列化。
  • 要对socket进行字节流的读取处理。

4 -> 重新理解read、write、recv、send和tcp为什么支持全双工

  • 在任何一台主机上,TCP连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个tcp sockfd读写都是它的原因。
  • 实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议

5 -> 开始实现

代码结构

Calculate.hpp Makefile Socket.hpp TcpServer.hpp
Daemon.hpp Protocol.hpp TcpClientMain.cc TcpServerMain.cc
// 简单起见,可以直接采用自定义线程
// 建议不用用户输入,直接 client<<->>server 通信,这样可以省去编写没有干货的代码

Socket封装

socket.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace Net_Work
{
    const static int defaultsockfd = -1;
    const int backlog = 5;
    enum
    {
        SocketError = 1,
        BindError,
        ListenError,
    };
    // 封装一个基类,Socket 接口类
    // 设计模式:模版方法类
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocketOrDie() = 0;
        virtual void BindSocketOrDie(uint16_t port) = 0;
        virtual void ListenSocketOrDie(int backlog) = 0;
        virtual Socket* AcceptConnection(std::string* peerip,
            uint16_t* peerport) = 0;
        virtual bool ConnectServer(std::string& serverip, uint16_t
            serverport) = 0;
        virtual int GetSockFd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(std::string* buffer, int size) = 0;
        virtual void Send(std::string& send_str) = 0;
        // TODO
    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)
        {
            SetSockFd(sockfd);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
        {
        }

        ~TcpSocket()
        {
        }

        void CreateSocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SocketError);
        }

        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);
        }

        void ListenSocketOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
        }

        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;
        }

        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_addr.s_addr = inet_addr(serverip.c_str());
            server.sin_port = htons(serverport);

            int n = ::connect(_sockfd, Convert(&server),
                sizeof(server));
            if (n == 0)
                return true;
            else
                return false;
        }

        int GetSockFd() override
        {
            return _sockfd;
        }

        void SetSockFd(int sockfd) override
        {
            _sockfd = sockfd;
        }

        void CloseSocket() override
        {
            if (_sockfd > defaultsockfd)
                ::close(_sockfd);
        }

        bool Recv(std::string* buffer, int size) override
        {
            char inbuffer[size];
            ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);
            if (n > 0)
            {
                inbuffer[n] = 0;
                *buffer += inbuffer; // 故意拼接的
                return true;
            }
            else if (n == 0) 
                return false;
            else 
                return false;
        }

        void Send(std::string& send_str) override
        {
            send(_sockfd, send_str.c_str(), send_str.size(), 0);
        }

    private:
        int _sockfd;
    };
}

5.1 -> 定制协议

基本结构

定制基本的结构化字段,这个就是协议。

class Request
{
private:
    // _data_x _oper _data_y
    // 报文的自描述字段
    // "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
    // 很多工作都是在做字符串处理!
    int _data_x; // 第一个参数
    int _data_y; // 第二个参数
    char _oper; // + - * / %
};

class Response
{
private:
    // "len\r\n_result _code\r\n"
    int _result; // 运算结果
    int _code; // 运算状态
};

protocol.hpp

#pragma once
#include <iostream>
#include <memory>
#include <jsoncpp/json/json.h>

namespace Protocol
{
    // 问题
    // 1. 结构化数据的序列和反序列化
    // 2. 还要解决用户区分报文边界 --- 数据包粘报问题
    // 讲法
    // 1. 自定义协议
    // 2. 成熟方案序列和反序列化
    // "protocol_code\r\nlen\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
    const std::string ProtSep = " ";
    const std::string LineBreakSep = "\r\n";

    // "len\r\nx op y\r\n" : \r\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;
    }
    // "len\nx op y\n" : \n 不属于报文的一部分,约定
    // 我无法保证 package 就是一个独立的完整的报文
    // "l
    // "len
    // "len\r\n
    // "len\r\nx
    // "len\r\nx op
    // "len\r\nx op y
    // "len\r\nx op y\r\n"
    // "len\r\nx op y\r\n""len
    // "len\r\nx op y\r\n""len\n
    // "len\r\nx op
    // "len\r\nx op y\r\n""len\nx op y\r\n"
    // "len\r\nresult code\r\n""len\nresult code\r\n"
    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;
    }

    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)
        {
        }

        void Debug()
        {
            std::cout << "_data_x: " << _data_x << std::endl;
            std::cout << "_data_y: " << _data_y << std::endl;
            std::cout << "_oper: " << _oper << std::endl;
        }

        void Inc()
        {
            _data_x++;
            _data_y++;
        }

        // 结构化数据->字符串
        bool Serialize(std::string* out)
        {
            Json::Value root;
            root["datax"] = _data_x;
            root["datay"] = _data_y;
            root["oper"] = _oper;
            Json::FastWriter writer;
            *out = writer.write(root);

            return true;
        }

        bool Deserialize(std::string & in) // "x op y" [)
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (res)
            {
                _data_x = root["datax"].asInt();
                _data_y = root["datay"].asInt();
                _oper = root["oper"].asInt();
            }

            return res;
        }

        int GetX() 
        { 
            return _data_x; 
        }

        int GetY() 
        { 
            return _data_y; 
        }

        char GetOper() 
        { 
            return _oper; 
        }

    private:
        // _data_x _oper _data_y
        // 报文的自描述字段
        // "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
        // 很多工作都是在做字符串处理!
        int _data_x; // 第一个参数
        int _data_y; // 第二个参数
        char _oper; // + - * / %
    };

    class Response
    {
    public:
        Response() : _result(0), _code(0)
        {
        }

        Response(int result, int code) : _result(result),
            _code(code)
        {
        }

        bool Serialize(std::string * out)
        {
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            Json::FastWriter writer;
            *out = writer.write(root);

            return true;
        }

        bool Deserialize(std::string & in) // "_result _code" [)
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (res)
            {
                _result = root["result"].asInt();
                _code = root["code"].asInt();
            }

            return res;
        }

        void SetResult(int res) 
        { 
            _result = res; 
        }

        void SetCode(int code) 
        { 
            _code = code; 
        }

        int GetResult() 
        { 
            return _result; 
        }

        int GetCode() 
        { 
            return _code; 
        }

    private:
        // "len\r\n_result _code\r\n"
        int _result; // 运算结果
        int _code; // 运算状态
    };

    // 简单的工厂模式,建造类设计模式
    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> req =
                std::make_shared<Response>(result, code);

            return req;
        }
    };
}

期望的报文格式

5.2 -> 关于流式数据的处理

  • 如何保证你每次读取就能读完请求缓冲区的所有内容?
  • 怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?
  • 处理TCP缓冲区中的数据,一定要保证正确处理请求。
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
// "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;
}

// "len\nx op y\n" : \n 不属于报文的一部分,约定
// 我无法保证 package 就是一个独立的完整的报文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"
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;
}

所以,完整的处理过程应该是:


感谢各位大佬支持!!!

互三啦!!!

标签: linux 服务器 网络

本文转载自: https://blog.csdn.net/weixin_74809706/article/details/143063920
版权归原作者 枫叶丹4 所有, 如有侵权,请联系我们删除。

“【在Linux世界中追寻伟大的One Piece】应用层自定义协议|序列化”的评论:

还没有评论