Linux网络——自定义协议

news2024/11/20 4:45:54

目录

一.什么是协议

二.协议与报文

三.自定义协议

1.封装套接字

2.构建请求与响应

3.序列化和反序列化

4.报头添加和去除

5.报文读取

四.服务器端程序

五.客户端程序


一.什么是协议

协议在生活中泛指:双方或多方为了完成某项任务或达成某种目的而制定的共同遵守的规定、标准或约定。

在计算机网络中:就是一种约定,约定了通信的双方,怎么发数据,怎么读数据,双方使用早就已经约定好的方式来进行数据的通信,这种早已经约定好的方式,就是一种协议。

协议主要作用是定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送或接受一条报文或其他事件所采取的动作。

面对不同的场景,通信的方式自然也是不同的,在双方的面对不同的场景做出的约定自然也是不同的。所以在计算机网络中,协议的种类非常多,且是以层状的结构展现。

二.协议与报文

报文是网络中交换与传输的数据单元,包含了将要发送的完整的数据信息。因此可以看出,协议主要规定了如何进行通信和数据传输的规则,而报文则是这些规则下实际传输的数据内容,协议也决定了报文的格式和顺序等特性。

简单来说,协议是一种类型,报文就是这种类型下的对象。

下图每一个传输的都是一个报文,报文格式不同,因为所处的协议不同。

报文整体格式:

报头:包含了该报文的元数据,例如源地址、目标地址、长度等信息。

有效载荷:这是实际的数据内容,可能是一个文件、一个数据库记录,或者其他任何类型的数据。

三.自定义协议

今天我们自己定义的协议的隶属于,传输层之上的应用层协议。我们将完成对协议的请求报文,响应报文的构建。以及如何,设计添加报头和去除报头,对报文的序列化和反序列化。

今天我们实现的服务器功能是计算器的功能。

报文格式:

1.封装套接字

关于套接字上一篇已经有详细的说明,这里不多介绍。

Sock.hpp

#pragma once
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
const static int backlog = 32;

enum
{
    SOCK_ERR = 10,
    BING_ERR,
    LISTEN_ERR,
    CONNECT_ERR
};

class Udp
{
public:
    Udp(int SOCK)
    {
        _listensock = socket(AF_INET, SOCK, 0);
        if (_listensock == -1)
        {
            Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
            exit(SOCK_ERR);
        }
    }
    Udp(uint16_t port, int SOCK)
        : _port(port)
    {
        _listensock = socket(AF_INET, SOCK, 0);
        if (_listensock == -1)
        {
            Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
            exit(10);
        }
    }

    void Bind()
    {
        struct sockaddr_in host;
        host.sin_family = AF_INET;
        host.sin_port = htons(_port);
        host.sin_addr.s_addr = INADDR_ANY; // #define INADDR_ANY 0x00000000
        socklen_t hostlen = sizeof(host);
        int n = bind(_listensock, (struct sockaddr *)&host, hostlen);
        if (n == -1)
        {
            Logmessage(Fatal, "bind err ,error code %d,%s", errno, strerror(errno));
            exit(BING_ERR);
        }
    }

    int FD()
    {
        return _listensock;
    }
    ~Udp()
    {
        close(_listensock);
    }

protected:
    int _listensock;
    uint16_t _port;
};

class Tcp : public Udp
{
public:
    Tcp(uint16_t port)
        : Udp(port, TCP)
    {
    }

    Tcp()
        : Udp(TCP)
    {
    }

    void Listen()
    {
        int n = listen(_listensock, backlog);
        if (n == -1)
        {
            Logmessage(Fatal, "listen err ,error code %d,%s", errno, strerror(errno));
            exit(LISTEN_ERR);
        }
    }

    int Accept(string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in client;
        socklen_t clientlen;
        int sock = accept(_listensock, (struct sockaddr *)&client, &clientlen);
        if (sock < 0)
        {
            Logmessage(Warning, "bind err ,error code %d,%s", errno, strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(client.sin_addr);
            *clientport = ntohs(client.sin_port);
        }
        return sock;
    }

    void Connect(string ip, uint16_t port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = inet_addr(ip.c_str());
        socklen_t hostlen = sizeof(server);
        int n = connect(_listensock, (struct sockaddr *)&server, hostlen);
        if (n == -1)
        {
            Logmessage(Fatal, "Connect err ,error code %d,%s", errno, strerror(errno));
            exit(CONNECT_ERR);
        }
    }

    ~Tcp()
    {
    }
};

2.构建请求与响应

请求:有待计算的数据,和运算符,以及对应将请求序列化,反序列化的函数。

class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char op)
        : _x(x), _y(y), _op(op)
    {
    }

    // 序列化
    std::string serialize()
    {
    }
    // 反序列化"123+321"
    void deserialize(const std::string &str)
    {
    }

public:
    int _x;
    int _y;
    char _op;
};

响应:有请求的计算结果,和退出码(标识计算结果的正确性),以及对应的序列化,和反序列化函数。

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

    Responce()
    {
    }
    // 序列化
    std::string serialize()
    {
    }
    // 反序列化
    void deserialize(const std::string &str)
    {
    }

public:
    int _result;
    int _code;
};

3.序列化和反序列化

序列化:将某种结构化的数据,变为方便传输的序列,可以是字符串,也可以是二进制字节流。

反序列化:将序列化的数据,变为结构化的数据。

序列化和反序列化这个工作我们可以自己做也可以使用第三放工具——json。

json:头文件<jsoncpp/json/json.h>

请求序列化和反序列化:

//序列化
std::string serialize()
    {
#ifdef MYSELF
        string strresult = to_string(_result);
        string strcode = to_string(_code);
        return strresult + SEP + strcode;
#else
        // 使用json序列化
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::StyledWriter writer;
        return writer.write(root);
#endif
    }
    // 反序列化
    void deserialize(const std::string &str)
    {
#ifdef MYSELF
        string strresult;
        string strcode;
        bool isleft = 1;
        for (auto e : str)
        {
            if (e >= '0' && e <= '9' && isleft)
            {
                strresult += e;
            }
            else if (e <= '0' || e >= '9')
            {
                isleft = 0;
            }
            else if (e >= '0' && e <= '9' && !isleft)
            {
                strcode += e;
            }
        }
        _result = atoi(strresult.c_str());
        _code = atoi(strcode.c_str());
#else
        // 使用json反序列化
        Json::Value root;
        Json::Reader reader; // Reader: 用来进行反序列化的
        reader.parse(str, root);
        _result = root["result"].asInt();
        _code = root["code"].asInt();
#endif
    }

响应序列化和反序列化:

// 序列化
    std::string serialize()
    {
#ifdef MYSELF
        string strresult = to_string(_result);
        string strcode = to_string(_code);
        return strresult + SEP + strcode;

#else
        // 使用json序列化
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::StyledWriter writer;
        return writer.write(root);

#endif
    }
    // 反序列化
    void deserialize(const std::string &str)
    {
#ifdef MYSELF
        string strresult;
        string strcode;
        bool isleft = 1;
        for (auto e : str)
        {
            if (e >= '0' && e <= '9' && isleft)
            {
                strresult += e;
            }
            else if (e <= '0' || e >= '9')
            {
                isleft = 0;
            }
            else if (e >= '0' && e <= '9' && !isleft)
            {
                strcode += e;
            }
        }
        _result = atoi(strresult.c_str());
        _code = atoi(strcode.c_str());
#else
        // 使用json反序列化
        Json::Value root;
        Json::Reader reader; // Reader: 用来进行反序列化的
        reader.parse(str, root);
        _result = root["result"].asInt();
        _code = root["code"].asInt();
#endif
    }

4.报头添加和去除

在对报头部分我们添加有效载荷的长度,和固定的分隔符。以便将报头和有效载荷分离。

#define SEP "\r\n"
#define SEPLEN strlen(SEP)

// str:报文;len:有效载荷的长度
std::string Rehead(std::string str, int len)
{
    return str.substr(str.length() - 2 - len, len);
}

// 报文=报头+有效载荷——————"有效载荷长度"\r\n"有效载荷"\r\n
std::string Addhead(std::string str)
{
    std::string len = to_string(str.length());
    //"7\r\n123+123\r\n"
    return len + SEP + str + SEP;
}

5.报文读取

判断是否读取到一个完整的报文,只有读取到一个完整的报文,才将报文输出。

int ReadFormat(int fd, std::string &inputstr, std::string *target)
{
    // 从流中读取—————— "7"+\n\r+"123+321"+\n\r
    char buff[1024];
    int n = recv(fd, buff, sizeof(buff), 0);
    if (n < 0)
    {
        return n;
    }

    string readstr = buff;
    // cout << readstr << endl;
    // 解析判断读取的字符串是否完整
    // 尝试读取报头
    int pos = readstr.find(SEP, 0);
    if (pos == std::string::npos) // 没有找到分割"\n\r"
        return 0;
    // 找到报头分隔符,提取报头—————得到有效载荷的长度
    string headstr = readstr.substr(0, pos);
    int len = atoi(headstr.c_str());
    // 计算出整个报文应该有的长度——————报头+分割符+有效载荷
    int formatlen = headstr.length() + len + 2 * SEPLEN;
    if (readstr.length() < formatlen) // 读取的报文长度小于报文应该有的长度,没有读取完整
        return 0;
    // 读取到一个完整的报文了
    *target = readstr.substr(0, formatlen);
    inputstr.erase(0, formatlen);
    // cout << *target << endl;
    return len;
}

四.服务器端程序

服务器程序采用多线程处理请求的方式执行。

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"

using fun_t = std::function<Responce(const Request &)>;//处理函数
class server;
//线程函数参数
struct Args
{
    Args(server *ser, string ip, uint16_t port, int fd)
        : _ip(ip), _port(port), _pserver(ser), _fd(fd)
    {
    }
    int _fd;
    uint16_t _port;
    string _ip;
    server *_pserver;
};

class server
{
public:
    server(fun_t func, int port)
        : _func(func)
    {
        tcp = new Tcp(port);
        tcp->Bind();
        tcp->Listen();
        cout << "服务器创建成功" << endl;
    }
    void start()
    {
        while (1)
        {
            string clientip;
            uint16_t clientport;
            cout << "start accept" << endl;
            int sock = tcp->Accept(&clientip, &clientport);
            cout << "get a new connect" << endl;
            // 多线程处理请求
            pthread_t t;
            Args *args = new Args(this, clientip, clientport, sock);
            pthread_create(&t, nullptr, ThreadRun, args);
        }
    }

    ~server()
    {
        delete tcp;
    }

private:
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        Args *ts = static_cast<Args *>(args);
        ts->_pserver->serverIO(ts->_fd);
        delete ts;
        return nullptr;
    }

    void serverIO(int fd)
    {
        
    }

private:
    Tcp *tcp;
    fun_t _func;
};

服务器处理读取请求处理请求返回响应:

void serverIO(int fd)
    {
        // 由于使用tcp面向数据流传输数据,所以我们并不能知道我们读取的是不是一个完整的报文。

        // 1.读取一个完整的请求报文
        string inputstr;
        string message;
        while (1)
        {
            int len = ReadFormat(fd, inputstr, &message);
            if (len == 0) // 读取的不是完整报文,继续读取
                continue;
            if (len < 0) // 读取出错
            {
                break;
            }
            cout << "得到一个完整的报文:" << message << endl;
            // 2.去除报头——将报头和有效载荷分离
            message = Rehead(message, len);
            cout << "去除报头:" << message << endl;
            // 3.有效载荷反序列化
            Request request;
            request.deserialize(message);
            cout << "有效载荷反序列化后" << request._x << " " << request._op << " " << request._y << endl;
            // 4.处理业务逻辑
            Responce responce = _func(request);
            cout << "响应序列化前" << responce._result << ":" << responce._code << endl;
            // 5.有效载荷序列化
            message = responce.serialize();
            cout << "响应序列化后:" << message << endl;
            // 6.有效载荷添加报头
            message = Addhead(message);
            cout << "响应添加报头后:" << message << endl;
            // 7.发送响应
            send(fd, message.c_str(), message.length(), 0);
        }
        close(fd);
    }

 服务器主调用逻辑:

// 请求处理函数,返回响应
Responce calculate(Request request)
{
    int result;
    int exitcode;
    switch (request._op)
    {
    case '+':
        result = request._x + request._y;
        exitcode = 0;
        break;
    case '-':
        result = request._x - request._y;
        exitcode = 0;
        break;
    case '*':
        result = request._x * request._y;
        exitcode = 0;
        break;
    case '/':
        if (request._y == 0)
            exitcode = 1;
        else
        {
            result = request._x / request._y;
            exitcode = 0;
        }
        break;
    case '%':
        if (request._y == 0)
            exitcode = 2;
        else
        {
            result = request._x % request._y;
            exitcode = 0;
        }
        break;
    default:
        break;
    }
    return Responce(result, exitcode);
}

int main()
{
    server Server(calculate, 8081);
    Server.start();

    return 0;
}

五.客户端程序

#include <iostream>
#include "Protocol.hpp"
#include "Sock.hpp"

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    Tcp tcp;
    tcp.Connect(serverip, serverport);
    while (1)
    {
        // 构建一个请求
        int x;
        int y;
        char op;
        cout << "Input operand 1: ";
        cin >> x;
        cout << "Input operand 2: ";
        cin >> y;
        cout << "Input operand op: ";
        cin >> op;
        // 1.构建请求
        Request request(x, y, op);
        // 2.有效载荷序列化
        string message = request.serialize();
        // 3.添加报头
        message = Addhead(message);
        // 4.发送给服务器
        send(tcp.FD(), message.c_str(), message.length(), 0);
        int formatlen = 0;
        string input;
        string target;
        // 1.构建响应
        Responce respon;
        while (1)
        {
            // 2.读取响应
            int formatlen = ReadFormat(tcp.FD(), input, &target);
            if (formatlen == 0)
                continue;
            if (formatlen < 0)
                break;
            if (formatlen > 0)
            {
                // 读取到一个完整的报文
                cout << "读取到一个完整的报文:" << target << endl;
                // 3.去报头
                string format = Rehead(target, formatlen);
                cout << "报文去报头后:" << format << endl;
                // 4.有效载荷反序列化
                respon.deserialize(format);
                cout << "反序列化:" << respon._result << ":" << respon._result << endl;
                break;
            }
        }
        cout << "Result :" << respon._result << ",Exit code:" << respon._code << endl;
    }
    return 0;
}

效果展示:

自己序列化:

使用json序列化:

 

 本次自定义协议主要体现在:

  • 对报文格式的整体设计。
  • 构建合理的请求和响应。
  • 如何将报头和有效载荷分离。
  • 如何读取完整的报文。
  • 如何将序列化和反序列化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1204176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文件操作(上)

目录 为什么使用文件什么是文件&#xff1f;程序文件数据文件文件名 二进制文件和文本文件文件的打开和关闭流和标准流流标准流 文件指针文件的打开和关闭 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &…

DMDEM部署说明-详细步骤-(DM8达梦数据库)

DMDEM部署说明-详细步骤-DM8达梦数据库 环境介绍1 部署DM8 数据库1.1 创建一个数据库作为DEM后台数据库1.2 创建数据库用户 DEM1.3 使用DEM用户导入dem_init.sql 2 配置tomcat2.1 配置/tomcat/conf/server.xml2.2 修改jvm启动参数 3 配置JAVA 1.8及以上版本的运行时环境3.1 配置…

数据分析 - 思考题

上班路上刷到的有趣题

基于教与学算法优化概率神经网络PNN的分类预测 - 附代码

基于教与学算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于教与学算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于教与学优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

详解IP安全:IPSec协议簇 | AH协议 | ESP协议 | IKE协议

目录 IP安全概述 IPSec协议簇 IPSec的实现方式 AH&#xff08;Authentication Header&#xff0c;认证头&#xff09; ESP&#xff08;Encapsulating Security Payload&#xff0c;封装安全载荷&#xff09; IKE&#xff08;Internet Key Exchange&#xff0c;因特网密钥…

eNSP启动路由器一直出#号、以为是安装配置winpcap的问题。。。。(以为是win10安装winpcap失败的问题。。。)

问题描述&#xff1a;eNSP启动一直出#号的一种参考方法_ensp一直#_Hong的博客-CSDN博客 原因是看了这篇博客&#xff0c;觉得ensp启动路由器的时候一直出现&#xff03;号是因为winpcap安装的时候出现的问题。查看自己的winpcap安装成功之后的目录是&#xff1a; 然后因为那篇…

【C++】类和对象(2)--构造函数

目录 一 概念 二 构造函数特性 三 默认构造函数 一 概念 对于以下Date类&#xff1a; class Date { public:void Init(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout << _year << "-" << _month <…

Apache Airflow (五) :DAG调度触发时间

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

CLIP:万物分类(视觉语言大模型)

本文来着公众号“AI大道理” ​ 论文地址&#xff1a;https://arxiv.org/abs/2103.00020 传统的分类模型需要先验的定义固定的类别&#xff0c;然后经过CNN提取特征&#xff0c;经过softmax进行分类。然而这种模式有个致命的缺点&#xff0c;那就是想加入新的一类就得重新定义…

Lasso回归和岭回归详解

当数据特征存在多重共线性&#xff0c;特征矩阵不满秩&#xff0c;或者用普通线性回归过拟合的状况时&#xff0c;我们需要用lasso回归或岭回归来构建模型。 左边是lasso回归&#xff0c;右边是岭回归。 Lasso使用的是系数 的L1范式&#xff08;L1范式则是系数 的绝对值&#…

PCL安装与使用

1 apt安装 ubuntu20.04及以上版本下可以直接通过apt方式安装pcl编译好的二进制文件,二进制安装的版本为1.10。 sudo apt update sudo apt install libpcl-dev 2 源码安装 在pcl的github上下载对应的版本进行安装&#xff1a; https://github.com/PointCloudLibrary/pcl/rel…

分组取每组数据的最大值和最小值的方法思路,为类似场景的数据分析提取提供思路,例如提取宗地内建筑的最高层数等可参考此方法思路

目录 一、实现效果 二、实现过程 1.读取并剔除无效数据 2.数据分组 3.提取最大值 4.提取最小值 三、总结 使用FME实现批量分组取每组数据的最大值和最小值&#xff0c;为类似场景的数据分析提取提供思路&#xff0c;例如提取宗地内建筑的最高层数等可参考此方法思路。关…

【数据结构】非递归实现二叉树的前 + 中 + 后 + 层序遍历(听说面试会考?)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&…

mysql之正则表达式匹配

题目&#xff1a; 今天在牛客网看到一道关于数据库正则表达式匹配的问题&#xff0c;发现自己一点不会做。 正则表达式&#xff1a; 一、正则表达式 MySQL 正则表达式通常是在检索数据库记录的时候&#xff0c;根据指定的匹配模式匹配记录中 符合要求的特殊字符串。MySQL 的…

利用角色roles上线wordpress项目

角色订制&#xff1a;roles ① 简介 对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha 时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。   roles 用于层次性、结构化地组织playbook。roles 能够根据层次型结构自动装载变量文…

linux下使用Docker Compose部署Spug实现公网远程访问

&#x1f4d1;前言 本文主要是linux下使用Docker Compose部署Spug实现公网远程访问的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &am…

openlayers 注册投影

注册投影 openlayers 默认支持的坐标系有4326&#xff08;基于美国wgs84坐标系&#xff09;和3857&#xff08;墨卡托投影&#xff09;两种。 所以如果我们想要使用比如4490坐标系&#xff0c;或者4547坐标系&#xff0c;就需要先注册&#xff0c; 注册4490示例代码如下 如…

【Linux进程】进程控制

目录 一、进程创建 1.2 fork函数初识 1.2 fork函数返回值 1.3 写时拷贝 1.4 fork常规用法 1.5 fork调用失败的原因 二、进程终止 2.1 进程退出场景 2.2 进程退出码 2.2.1 用strerror打印错误信息 2.2.2 errno全局变量 2.3 进程常见退出方法 2.3.1 进程正常退出 2…

力扣刷题-二叉树-对称二叉树

101 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 思路 我的思路…

RK3568笔记五:基于Yolov5的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 一. 部署概述 环境&#xff1a;Ubuntu20.04、python3.8 芯片&#xff1a;RK3568 芯片系统&#xff1a;buildroot 开发板&#xff1a;ATK-DLRK3568 开发主要参考文档&#xff1a;《Rockchip_Quick_Start_RKNN_Toolkit2_C…