自定义协议

发布于:2025-03-11 ⋅ 阅读:(20) ⋅ 点赞:(0)

协议

什么是“协议”

  • 在计算机科学中,‌协议‌(Protocol)是对数据格式和计算机之间交换数据时必须遵守的规则的正式描述。例如,网络中的计算机要能够互相顺利通信,就必须遵守相同的协议,如Ethernet、NetBEUI、IPX/SPX以及TCP/IP协议

为什么要定制协议

  • 为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。

如何定制协议

  • 定制结构体:用该结构体来表示需要交互的信息

  • 序列化:将对象的状态信息转换为可以存储或传输的形式(字节序列)

  • 反序列化:把字节序列恢复为结构体对象

网络版本计算器

接下来我们通过自定义协议来实现一个网络版本的计算器,来真正理解一下协议

源码连接:网络版本计算器

协议定制

Request
  • _data_x:左操作数
  • _data_y:右操作数
  • _op:运算符
response
  • _result:计算结果
  • _code:状态码
class Request
    {
    public:
        Request(int x, int y, char op)
            : _data_x(x), _data_y(y), _op(op)
        {}
        Request()
            : _data_x(0), _data_y(0), _op(0)
        {}
    private:
        int _data_x;
        int _data_y;
        char _op;
    };
class Response
    {
    public:
        Response()
            : _result(0), _code(0)
        {}
        Response(int result, int code)
            : _result(result), _code(code)
        {}
    private:
        int _result;
        int _code;
    };
enum
    {
        Success = 0, //成功
    	DivZero,     //除零错误
        ModZero,     //模零错误
        UnknownOp    //未知错误
    };

客户端

  1. 客户端在给服务器发送请求时,会经过以下几个步骤:
  2. 将请求序列化,即Serialize
  3. 添加自描述报头,将响应构建成为一个完整报文,即Encode
  4. 发送请求
  5. 得到响应报文
  6. 对响应报文解析,即Decode
  7. 反序列化,即Deserialize,得到结果_result以及状态码_status
Request类

我们封装另一个Request类,类内成员就是左右操作数以及操作符,类内提供了对请求进行序列化的Serialize函数以及对请求进行反序列化的函数Deserialize

class Request
    {
    public:
        Request(int x, int y, char op)
            : _data_x(x), _data_y(y), _op(op)
        {
        }
        Request()
            : _data_x(0), _data_y(0), _op(0)
        {
        }
        bool Serialize(std::string *out) //"x op y"
        {
#ifdef SelfDefine
            *out = std::to_string(_data_x) + ProtSep + _op + ProtSep + std::to_string(_data_y);
            return true;
#else
            Json::Value root;
            root["data_x"] = _data_x;
            root["data_y"] = _data_y;
            root["oper"] = _op;
            Json::FastWriter writer;
            *out = writer.write(root);
            return true;
#endif
        }
        bool Deserialize(std::string &in)
        {
#ifdef SelfDefine

            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;
            _op = oper[0];
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in,root);
            if(res)
            {
                _data_x = root["data_x"].asInt();
                _data_y = root["data_y"].asInt();
                _op = root["oper"].asInt();
            }
            return true;
#endif
        }
        void Debug()
        {
            std::cout << "_data_x: " << _data_x << std::endl;
            std::cout << "_data_y: " << _data_y << std::endl;
            std::cout << "_op: " << _op << std::endl;
        }
        void Inc()
        {
            _data_x++;
            _data_y++;
        }
        int GetX()
        {
            return _data_x;
        }
        int GetY()
        {
            return _data_y;
        }
        char GetOp()
        {
            return _op;
        }

    private:
        int _data_x;
        int _data_y;
        char _op;
    };
#include <iostream>
#include <unistd.h>
#include <string>
#include <memory>
#include <ctime>
#include <stdlib.h>
#include "Socket.hpp"
#include "Protocol.hpp"

using namespace ProtocolNS;

//./tcpclinet serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage: \n\t" << argv[0] << " serverip serverport\n"
                  << std::endl;
        return 0;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    NetWork::Socket *conn = new NetWork::TcpSocket();
    if (!conn->BuildConnectionSocketMethod(serverip, serverport))
    {
        std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;
    }
    std::cerr << "connect " << serverip << ":" << serverport << " success" << std::endl;

    std::unique_ptr<Factory> factory = std::make_unique<Factory>();

    srand(time(nullptr) ^ getpid());
    const std::string opers = "+-*/%^&=";
    while (true)
    {
        //1.构建一个请求
        int x = rand()%100;
        usleep(1234);
        int y = rand()%100;
        char oper = opers[rand() % opers.size()];
        std::shared_ptr<Request> req = factory->BuildRequest(x,y,oper);

        //2.对请求进行序列化
        std::string requeststr;
        req->Serialize(&requeststr);
        std::string testreq = requeststr;
        testreq += " = ";

        //3.添加自描述报头
        requeststr = Encode(requeststr);

        //4.发送请求
        conn->Send(requeststr);

        std::string responsestr;
        while(true)
        {
            //5.读取响应
            conn->Recv(&responsestr,1024);

            //6.对报文进行解析
            std::string response;
            if(!Decode(responsestr,&response))
                continue;
            //7.反序列化
            auto resp = factory->BuildResponse();
            resp->Deserialize(response);
            std::cout << testreq << resp->Getresult() << "[" << resp->Getcode() << "]" << std::endl;
            break;
        }
        sleep(1);
    }

    conn->CloseFd();
    return 0;
}

服务器

  1. 服务器在收到客户端发来的请求后会经过以下几个步骤:
  2. 对收到的请求报文进行解析,即Decode
  3. 反序列化,即Deserialize,得到操作数和运算符
  4. 业务处理(计算)
  5. 序列化response,即Serialize
  6. 构建响应报文,即Encode
  7. 发送给客户端
Response类

我们封装另一个Response类,类内成员就是结果以及状态码,类内提供了对响应进行序列化的Serialize函数以及对响应进行反序列化的函数Deserialize

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

        bool Serialize(std::string *out) //"_result _code"
        {
#ifdef SelfDefine

            *out = std::to_string(_result) + ProtSep + std::to_string(_code);
            return true;
#else
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            Json::FastWriter writer;
            *out = writer.write(root);
            return true;
#endif
        }
        bool Deserialize(std::string &in)
        {
#ifdef SelfDefine

            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;
#else
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in,root);
            if(res)
            {
                _result = root["result"].asInt();
                _code = root["code"].asInt();
            }
            return true;
#endif
        }
        void SetResult(int result)
        {
            _result = result;
        }
        void SetCode(int code)
        {
            _code = code;
        }
        int Getresult()
        {
            return _result;
        }
        int Getcode()
        {
            return _code;
        }

    private:
        int _result;
        int _code;
    };
#pragma once
#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>

using func_t = std::function<std::string(std::string &, bool *)>;

class TcpServer;
class ThreadData
{
public:
    ThreadData(TcpServer *tcp_this, NetWork::Socket *sockp)
        : _this(tcp_this), _sockp(sockp)
    {
    }

public:
    TcpServer *_this;

    NetWork::Socket *_sockp;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t handler_request)
        : _port(port), _listensocket(new NetWork::TcpSocket()), _handler_request(handler_request)
    {
        _listensocket->BuildListenSocketMethod(_port, NetWork::backlog);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());

        ThreadData *td = static_cast<ThreadData *>(args);
        std::string inbufferstream;
        while (true)
        {
            bool ok = true;
            // 1.读取报文
            if (!td->_sockp->Recv(&inbufferstream, 1024))
                break;
            //2.报文处理
            std::string send_string = td->_this->_handler_request(inbufferstream, &ok);
            //3.发送数据    
            if(ok)
            {
                if(!send_string.empty())
                {
                    td->_sockp->Send(send_string);
                }
            }
            else
            {
                break;
            }

        }

        td->_sockp->CloseFd();
        delete td->_sockp;
        delete td;

        return nullptr;
    }
    void Loop()
    {
        while (true)
        {
            std::string peerip;
            uint16_t peerport;
            NetWork::Socket *newsock = _listensocket->AcceptConnection(&peerip, &peerport);
            if (newsock == nullptr)
                continue;
            std::cout << "获取一个新连接,sockfd: " << newsock->GetSocket() << "客户信息:" << peerip << ":" << peerport << std::endl;

            pthread_t tid;

            ThreadData *td = new ThreadData(this, newsock);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }
    ~TcpServer()
    {
        delete _listensocket;
    }

private:
    uint16_t _port;
    NetWork::Socket *_listensocket;

public:
    func_t _handler_request;
};
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
#include <iostream>
#include <string>
#include <memory>

using namespace NetWork;
using namespace ProtocolNS;
using namespace CalculateNs;

std::string HandlerRequest(std::string &inbufferstream, bool *error_code)
{
    *error_code = true;
    Calculate calculate;
    std::unique_ptr<Factory> factory = std::make_unique<Factory>();
    auto req = factory->BuildRequest();

    // 2.查看报文是否完整
    std::string message;
    std::string total_resp_messgae;
    while (Decode(inbufferstream, &message))
    {
        // 3.报文一定完整,反序列化req
        if (!req->Deserialize(message))
        {
            *error_code = false;
            return std::string();
        }
        // 4.业务处理
        auto resp = calculate.Cal(req);
        // 5.序列化response
        std::string send_string;
        resp->Serialize(&send_string);
        // 6.构建字符串级别的响应报文
        send_string = Encode(send_string);
        // 7.发送
        total_resp_messgae += send_string;
    }
 
    return total_resp_messgae;
}

//./tcpserver 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage: \n\t" << argv[0] << " local_port\n"
                  << std::endl;
        return 0;
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tsvr(new TcpServer(port, HandlerRequest));
    tsvr->Loop();

    return 0;
}

样例演示

服务器启动后会进入死循环,不间断为客户端提供服务,客户端会随机生成操作数以及运算符,将请求发送给服务器后,服务器会将响应发送给客户端,客户端便会把结果打印输出在屏幕上

在这里插入图片描述