Linux---自定义协议

应用层协议

一、协议定制---以网络计算器为例

网络计算机功能---进行+-*/^&|的运算并返回结果

请求和响应的结构体如下

// Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
class Request
{
public:
    Request()
    {
    }
    Request(int data_x, int data_y, char op)
        : _data_x(data_x), _data_y(data_y), _op(op)
    {
    }

private:
    int _data_x;
    int _data_y;
    char _op;
};
class Response
{
public:
    Response()
    {
    }
    Response(int result, int code)
        : _result(result), _code(code)
    {
    }

private:
    int _result;
    int _code;
};

// 建造类 --- 设计模式 --- 用来创建类对象
class Factory
{
public:
    std::shared_ptr<Request> BuiltRequest()
    {
        std::shared_ptr<Request> req = std::make_shared<Request>();
        return req;
    }
    std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op)
    {
        std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);
        return req;
    }

    std::shared_ptr<Response> BuiltResponse()
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>();
        return resp;
    }
    std::shared_ptr<Response> BuiltResponse(int result, int code)
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
        return resp;
    }
};

将请求的数据按照怎样的方式序列化成为字符串呢?

我们选择将数据转换成 "len\nx op y\n"

  • len是"x op y"的字符串长度,"len"后面的\n用来将len和后面的有效数据分开
  • ”x op y“是要计算的表达式,比如”5 + 6“,中间加空格间隔开,它后面的\n是为了调试打印,可以不加

这里我们也可以直接选择将数据转化成 "x op y\n",用\n作为数据之间的间隔,但是如果我们要传递的数据中就包含\n,这样做就会出现问题,但是加"len\n"就不会,因为我们能保证len这个字符串中不可能出现\n,更具有通用性

二、序列化和反序列化

所以,序列化和反序列化本质就是字符串相关的操作,具体的代码如下

//新增 给字符串数据,加报头和去报头的函数
const std::string BlankSep = " ";
const std::string LineSep = "\n";

// 将"有效数据" ->  "len\n有效数据\n"
std::string Encode(const std::string &message)
{
    return std::to_string(message.size()) + LineSep + message + LineSep;
}

// 将"len\n有效数据\n" ->  "有效数据",这里要注意判断数据是否完整,即是否有一个完整的请求
bool Decode(std::string &package, std::string *message)
{
    auto pos = package.find(LineSep);
    if (pos == std::string::npos)
        return false;
    int len = std::stoi(package.substr(0, pos));
    int total = len + pos + 2 * LineSep.size();
    if (total > package.size())
        return false;
    *message = package.substr(pos + LineSep.size(), len);
    package.erase(0, total);
    return true;
}

class Request
{
    //...
public:
    // 新增 --- 序列化和反序列化两个函数
    // 将数据序列化为 "x op y", 其中BlankSep是" "
    void Serialize(std::string *out)
    {
        *out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);
    }
    // 将"x op y"反序列化结构化字段, 其中BlankSep是" "
    bool Deserialize(const std::string &in)
    {
        auto l = in.find(BlankSep);
        if (l == std::string::npos)
            return false;
        _data_x = std::stoi(in.substr(0, l));
        auto r = in.rfind(BlankSep);
        if (r == std::string::npos)
            return false;
        _data_y = std::stoi(in.substr(r + BlankSep.size()));
        if (r - l - (int)BlankSep.size() != 1)
            return false;
        _op = in[l + BlankSep.size()];
        return true;
    }
};

class Response
{
    //...
public:
    // 新增 --- 序列化和反序列化两个函数
    // 将数据转序列化为"result code", 其中BlankSep是" "
    void Serialize(std::string *out)
    {
        *out = std::to_string(_result) + BlankSep + std::to_string(_code);
    }
    // 将"result code"反序列化为结构化字段, 其中BlankSep是" "
    bool Deserialize(const std::string &in)
    {
        auto pos = in.find(BlankSep);
        if (pos == std::string::npos)
            return false;
        _result = std::stoi(in.substr(0, pos));
        _code = std::stoi(in.substr(pos + BlankSep.size()));
        return true;
    }
};

当然这些字符串相关的序列化和反序列化操作过于繁琐,我们可以不用手写,可以直接用一些工具帮助我们完成这一过程,如json,protobuf等。

用json举个例子(注意要下载json相关的第三方库)

class Request
{
    //...
public:
    void Serialize(std::string *out)
    {
        Json::Value root;
        root["data_x"] = _data_x;
        root["data_y"] = _data_y;
        root["op"] = _op;
        Json::FastWriter writer;
        *out = writer.write(root);
    }

    bool Deserialize(const std::string &in)
    {
        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["op"].asInt();
        }
        return res;
    }
};

class Response
{
    //...
public:
    void Serialize(std::string *out)
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::FastWriter writer;
        *out = writer.write(root);
    }

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

 Json序列化后的表达式如下

三、完整代码(包含对socket的封装)

// Calculate.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
namespace Calcu
{
    enum
    {
        Success,
        DivZero,
        ModZero,
        Unknown
    };
    class Calculate
    {
    public:
        std::shared_ptr<Protocol::Response> cal(std::shared_ptr<Protocol::Request> req)
        {
            auto resp = factory.BuiltResponse();
            switch (req->GetOp())
            {
            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(DivZero);
                }
                else
                {
                    resp->SetResult(req->GetX() / req->GetY());
                }
            }
            break;
            case '%':
            {
                if (req->GetY() == 0)
                {
                    resp->SetCode(ModZero);
                }
                else
                {
                    resp->SetResult(req->GetX() % req->GetY());
                }
            }
            break;
            case '^':
                resp->SetResult(req->GetX() ^ req->GetY());
                break;
            case '|':
                resp->SetResult(req->GetX() | req->GetY());
                break;
            case '&':
                resp->SetResult(req->GetX() & req->GetY());
                break;
            default:
                resp->SetCode(Unknown);
                break;
            }
            return resp;
        }

    private:
        Protocol::Factory factory;
    };
}

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

// #define SelfDefine 1

namespace Protocol
{
    const std::string BlankSep = " ";
    const std::string LineSep = "\n";
    std::string Encode(const std::string &message)
    {
        return std::to_string(message.size()) + LineSep + message + LineSep;
    }

    bool Decode(std::string &package, std::string *message)
    {
        auto pos = package.find(LineSep);
        if (pos == std::string::npos)
            return false;
        int len = std::stoi(package.substr(0, pos));
        int total = len + pos + 2 * LineSep.size();
        if (total > package.size())
            return false;
        *message = package.substr(pos + LineSep.size(), len);
        package.erase(0, total);
        return true;
    }

    class Request
    {
    public:
        Request() : _data_x(0), _data_y(0), _op(0)
        {
        }

        Request(int data_x, int data_y, char op)
            : _data_x(data_x), _data_y(data_y), _op(op)
        {
        }

        void Serialize(std::string *out)
        {
#ifdef SelfDefine
            *out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);
#else
            Json::Value root;
            root["data_x"] = _data_x;
            root["data_y"] = _data_y;
            root["op"] = _op;
            Json::FastWriter writer;
            *out = writer.write(root);
#endif
        }

        bool Deserialize(const std::string &in)
        {
#ifdef SelfDefine
            auto l = in.find(BlankSep);
            if (l == std::string::npos)
                return false;
            _data_x = std::stoi(in.substr(0, l));
            auto r = in.rfind(BlankSep);
            if (r == std::string::npos)
                return false;
            _data_y = std::stoi(in.substr(r + BlankSep.size()));
            if (r - l - (int)BlankSep.size() != 1)
                return false;
            _op = in[l + BlankSep.size()];
            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["op"].asInt();
            }
            return res;
#endif
        }

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

        void DeBug()
        {
            std::cout << "data_x :" << _data_x << std::endl;
            std::cout << "data_y :" << _data_y << std::endl;
            std::cout << "op :" << _op << std::endl;
        }

        int GetX() { return _data_x; }
        int GetY() { return _data_y; }
        char GetOp() { return _op; }

    private:
        // 序列化
        // len\nx op y\n
        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)
        {
        }

        void Serialize(std::string *out)
        {
#ifdef SelfDefine
            *out = std::to_string(_result) + BlankSep + std::to_string(_code);
#else
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            Json::FastWriter writer;
            *out = writer.write(root);
#endif
        }

        bool Deserialize(const std::string &in)
        {
#ifdef SelfDefine
            auto pos = in.find(BlankSep);
            if (pos == std::string::npos)
                return false;
            _result = std::stoi(in.substr(0, pos));
            _code = std::stoi(in.substr(pos + BlankSep.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 res;
#endif
        }

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

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

        int GetResult()
        {
            return _result;
        }

        int GetCode()
        {
            return _code;
        }

    private:
        int _result;
        int _code;
    };

    // 建造类 --- 设计模式
    class Factory
    {
    public:
        std::shared_ptr<Request> BuiltRequest()
        {
            std::shared_ptr<Request> req = std::make_shared<Request>();
            return req;
        }
        std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op)
        {
            std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);
            return req;
        }

        std::shared_ptr<Response> BuiltResponse()
        {
            std::shared_ptr<Response> resp = std::make_shared<Response>();
            return resp;
        }
        std::shared_ptr<Response> BuiltResponse(int result, int code)
        {
            std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
            return resp;
        }
    };
}

// 对sockfd进行封装
//对网络套接字进行封装
// Socket.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <string.h>
#include <memory>
#include <string>
#include <unistd.h>

const int defaultsockfd = -1;
const int defaultbacklog = 5;
#define CONV(addr) ((struct sockaddr *)addr)
namespace zxws
{
    enum
    {
        SockError = 1,
        BindError,
        ListenError,
        AcceptError,
        ConnectError
    };

    // 模板方法 --- 设计模式的一种
    // 对网络套接字进行封装 --- 屏蔽内部建立连接的过程
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocket() = 0;
        virtual void BindSocket(uint16_t port) = 0;
        virtual void ListenSocketOrDie(int backlog) = 0;
        virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
        virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) = 0;
        virtual int GetSockfd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSockfd() = 0;

    public:
        void BuildListenSocket(uint16_t port, int backlog)
        {
            CreateSocket();
            BindSocket(port);
            ListenSocketOrDie(backlog);
        }

        bool BuildConnectSocket(const std::string &peerip, uint16_t peerport)
        {
            CreateSocket();
            return ConnectServer(peerip, peerport);
        }

        void BuildNormalSocket(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

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

        virtual ~TcpSocket() {}

        virtual void CreateSocket() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SockError);
            std::cout << "create success , sockfd : " << _sockfd << std::endl;
        }

        virtual void BindSocket(uint16_t port) override
        {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(port);
            local.sin_addr.s_addr = INADDR_ANY;
            int n = bind(_sockfd, CONV(&local), sizeof(local));
            if (n < 0)
                exit(BindError);
            std::cout << "bing success , sockfd: " << _sockfd << std::endl;
        }

        virtual void ListenSocketOrDie(int backlog) override
        {
            int n = listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
            std::cout << "listen success , sockfd: " << _sockfd << std::endl;
        }

        virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport)
        {
            struct sockaddr_in server;
            socklen_t len = sizeof(server);
            int sockfd = accept(_sockfd, CONV(&server), &len);
            if (sockfd < 0)
                return nullptr;
            char buffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &server.sin_addr, buffer, sizeof(buffer));
            *peerip = buffer;
            *peerport = ntohs(server.sin_port);
            return new TcpSocket(sockfd);
        }

        virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) override
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(peerport);
            inet_pton(_sockfd, peerip.c_str(), &server.sin_addr);
            int n = connect(_sockfd, CONV(&server), sizeof(server));
            return n == 0;
        }

        virtual int GetSockfd()
        {
            return _sockfd;
        }

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

        void CloseSockfd()
        {
            if (_sockfd >= 0)
                close(_sockfd);
        }

    private:
        int _sockfd;
    };
}


//TcpServer.hpp
#pragma once
#include "Socket.hpp"
#include <pthread.h>
#include <functional>

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

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

class TcpServer
{
public:
    TcpServer(int port, func_t task)
        : _port(port), _listensockfd(new zxws::TcpSocket()), _task(task)
    {
    }

    static void *ThreadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        std::string inbuffer;
        while (1)
        {
            bool ok = true;
            // 1、接收
            if(!td->_sockp->Recv(&inbuffer,1024))
                break;
            // 2、处理
            std::string send_str = td->_this->_task(inbuffer,&ok);
            if(ok)
            {
                if(!send_str.empty())
                {
                    // 3、发送
                    td->_sockp->Send(send_str);
                }
            }
            else
            {
                break;
            }
        }
        //注意顺序
        td->_sockp->CloseSockfd();
        delete td->_sockp;
        delete td;
        return nullptr;
    }

    void Loop()
    {
        _listensockfd->BuildListenSocket(_port, defaultbacklog);
        std::string ip;
        uint16_t port;
        while (true)
        {
            zxws::Socket *sockfd = _listensockfd->AcceptConnection(&ip, &port);
            if (sockfd == nullptr)
                continue;
            std::cout << "accept success , [" << ip << ":" << port << "]"
                      << " sockfd " << sockfd->GetSockfd() << std::endl;
            // sockfd->CloseSockfd();
            ThreadData *td = new ThreadData(this,sockfd);
            pthread_t tid;
            pthread_create(&tid, nullptr, ThreadRoutine, td);
        }
    }

    ~TcpServer()
    {
        delete _listensockfd;
    }

private:
    int _port;
    zxws::Socket *_listensockfd;
    func_t _task;
};

//TcpServer.cpp
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
using namespace Protocol;
using namespace zxws;
using namespace Calcu;
std::string Handler(std::string &inbuffer, bool *error_code)
{
    *error_code = true;
    std::unique_ptr<Factory> factory(new Factory);
    auto req = factory->BuiltRequest();
    Calculate calculate;
    // 1、分析字节流
    std::string message;
    std::string ret;
    while (Decode(inbuffer, &message))
    {
        // 2、反序列化
        if (!req->Deserialize(message))
        {
            *error_code = false;
            return std::string();
        }
        // 3、业务逻辑
        auto resp = calculate.cal(req);
        // 4、序列化
        std::string send_str;
        resp->Serialize(&send_str);
        // 5、添加报头
        send_str = Encode(send_str);
        ret += send_str;
    }
    return ret;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(port, Handler));
    svr->Loop();
    return 0;
}

//TcpClient.cpp
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace Protocol;
const std::string opers = "+-/*%=&^|";
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage : " << argv[0] << " ip port" << std::endl;
        return 0;
    }
    uint16_t port = std::stoi(argv[2]);
    std::string ip = argv[1];
    zxws::Socket *sock = new zxws::TcpSocket();
    if (!sock->BuildConnectSocket(ip, port))
    {
        std::cout << "connect failed" << std::endl;
        return 0;
    }
    std::cout << "connect success" << std::endl;
    std::unique_ptr<Factory> factory(new Factory);
    srand(time(nullptr));
    std::string response;
    while (1)
    {
        // 1、创建消息
        int x = rand() % 100;
        int y = rand() % 100;
        char op = opers[rand() % opers.size()];
        usleep(1000);
        std::shared_ptr<Request> req = factory->BuiltRequest(x, y, op);
        req->DeBug();
        // 2、序列化
        std::string requires;
        req->Serialize(&requires);
        std::cout<<requires<<std::endl;
        // 3、添加报头
        std::string res = Encode(requires);

        // 4、发送
        sock->Send(res);

        // 5、接收
        sock->Recv(&response, 1024);
        // 6、分析字节流
        std::string message;
        if (!Decode(response, &message))
            break;

        // 7、反序列化
        auto resp = factory->BuiltResponse();
        if (!resp->Deserialize(message))
            break;
        // 打印
        std::cout << x << op << y << " = " << resp->GetResult() << " [" << resp->GetCode() << "]" << std::endl;
    }
    sock->CloseSockfd();
    return 0;
}

四、总结和扩展

当然这只是一个协议,我们以后如果想要增加功能,可以再去定义协议,只要在序列化数据的头部加上该请求想要访问哪个服务的即可,其他操作步骤和我们制定网络计算器一样。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NB-IoT现阶段访问一个服务器只能通过IP地址加端口的方式,省去了DNS解析,如果产品以后的IP变化或者改变了服务端的IP地址,就需要一个DNS解析的功能。 通过DNS解析某个域名的IP地址,获取到IP地址后再进行通信。 如果NB-IoT模块只能绑定一个IP,则可以通过这个IP先获取其他IP列表,然后统一经过这个IP进行转发。 参考: 1.1.2 NB卡准备 模组使用的SIM卡为中国电信物联网专用NB卡,如下图正面(留意NB字样,无此字样的均为不合法的NB卡): 背面(留意其ICCID号,在让运营商开放IP白名单时可能需要此号码,相当于手机卡的手机号): 重要:收到卡后,需要致电背面的客服电话, 使其将您自己的IP地址加入访问白名单,此一步完成后,方能进行下面的步骤,切记切记 。 如何判定服务器IP已被加入访问白名单,使用如下两种方式: 1. AT+NPING 指令,通过PING自己的服务器地址,如返回ERROR,则多半(尚需继续排除防火墙因素)未就绪; 2. UDP通讯,如能与自己的服务器直接连接UDP通讯,那可以证明一定就绪; 1.1.3 服务器准备  首先您需要有一个固定IP的公网服务器,由于目前BC95暂不支持域名解析,故必须使用IP地址配置方式。  服务器可以使用阿里云服务器,目前本CoAP端暂未开源,有Windows 32位、Windows 64位、Linux CentOS 6、CentOS 7的可执行文件,请暂时选择以上指定系统;  CoAP标准协议使用 UDP 5683 端口,当然您也可以自定义此端口,必须让防火墙放通UDP 的指定端口;  CoAP网关需要使用WEB方式进行设备管理、用户管理等,默认使用 TCP 8080 端口,同 理,防火墙必须放通此端口; 1.1.4 模组准备 推荐使用 串口调试助手 sscom 来调试NB模组,如下,首先将您的NB模组上电使其启动,使用AT指令能收到OK的回复,证明已启动完毕,按如下步骤进行: 基础配置 1. 配置 NCDP 服务器,使用的指令序列为 AT+CFUN=0 +NCDP=103.37.149.19,5683 AT+NRB 留意 IP 地址必须为您自己指定的IP地址,如果暂时没有,也可以用 如上 地址临时使用(但 WEB 设备管理地址也需换成这个IP),完成后重启了设备; 2. 等待设备附着网络后,使用 AT+NPING=103.37.149.19 尝试PING自己的服务器,当返回ERROR时,极有可能是 IP 白名单未成功配置的缘故; 3. 使用 AT+CGSN=1 查询设备 IMEI 号,并将设备的 IMEI 注册到 WEB 平台,如果在上面操作 过,可忽略。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值