Linux网络——自定义协议

目录

一.什么是协议

二.协议与报文

三.自定义协议

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序列化:

 

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS知识梳理

一、RTOS:Real time operating system,中文意思为 实时操作系统&#xff0c;它是一类操作系统&#xff0c;比如uc/OS、FreeRTOS、RTX、RT-Thread 这些都是实时操作系统。 二、移植FreeRTOS到STM32F103C8T6上 interface选择CMSIS_V1,RCC选择Crystal Ceramic Resonator 。 …

Redis应用之一自增编号

一、前言 前段时间同事用Redis实现收银台商品排行榜&#xff0c;我们都知道Redis最基础的功能是用来缓存数据&#xff0c;但其实它还有很多特性能解决很多实际问题&#xff0c;接下来几篇文章我们就聊聊Reids一些特性的应用&#xff0c;今天先聊一下借助Reids生成不会重复的订…

智能优化算法(一):伪随机数的产生

文章目录 1.伪随机数介绍1.1.伪随机产生的意义1.2.伪随机产生的过程 2.产生U(0,1)的乘除同余法2.1.原始的乘同余法2.2.改进的乘同余法 3.产生正态分布的伪随机数4.基于逆变法产生伪随机数 1.伪随机数介绍 1.1.伪随机产生的意义 1.随机数的产生是进行随机优化的第一步也是最重要…

C++ final

参考:https://blog.csdn.net/qq_45358642/article/details/124232686#t2 不想让类继承 方式一&#xff1a;将类的构造函数设置为私有 子类不能调用父类构造函数初始化来实例化对象&#xff0c;所以不能继承 缺点&#xff1a;我们自己也不能够实例化出对象 class A { privat…

跨境电商商城源码:打造全球化的多语言、多货币、多商户平台

随着全球电子商务的快速发展&#xff0c;越来越多的企业希望在跨境电子商务领域取得突破。然而&#xff0c;要实现这一目标&#xff0c;企业需要解决语言、货币和商户等多个方面的挑战。本文将探讨如何使用跨境电商商城源码打造全球化的多语言、多货币、多商户平台。 一、多语言…

扫码连接WiFi微信小程序项目(带源码下载)

微信小程序扫码连wifi(共享wifi)(WiFi地推项目)&#xff0c;2023年非常火爆全网的项目 下载: 项目源码 效果图如下 一 扫码连接WiFi如何收益 用户扫码连接WiFi时会有4-15秒的广告弹框,有效时间看完后微信会发送给项目负责人0.5-1元的广告费 (如给1元) 项目负责人(团长)(收益2…

【08】DestinationRule 高级配置功能

6.2 loadbalancer 定义demoapp v1.0和demoapp v1.1版本和subset的dr规则。参考weight中定义&#xff1b; 定义loadbalance在DestinationRule上定义规则 apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata:name: demoapp spec:host: demoapptrafficPoli…

火山引擎云原生存储加速实践

在火山引擎相关的业务中绝大部分的机器学习和数据湖的算力都运行在云原生 K8s 平台上。云原生架构下存算分离和弹性伸缩的计算场景&#xff0c;极大的推动了存储加速这个领域的发展&#xff0c;目前业界也衍生出了多种存储加速服务。但是面对计算和客户场景的多样性&#xff0c…

1 Supervised Machine Learning Regression and Classification

文章目录 Week1OverViewSupervised LearningUnsupervised LearningLinear Regression ModelCost functionGradient Descent Week2Muliple FeatureVectorizationGradient Descent for Multiple RegressionFeature ScalingGradient DescentFeature EngineeringPolynomial Regress…

C# 使用 RSA 加密算法生成证书签名产生“The system cannot find the file specified”异常

使用 C# 中 RSA&#xff08;System.Security.Cryptography.RSA&#xff09; 加密算法生成证书签名进行身份验证&#xff0c;在 VS2022 开发工具本地运行应用程序一切正常。 但将应用程序部署到远程服务器&#xff08;如&#xff1a;Azure App Services&#xff09;&#xff0c…

【数据结构】——单链表(增删查改)

目录 前言&#xff1a; 一&#xff1a;单链表的特点 ​编辑 二&#xff1a;单链表实现 单链表定义 2.1申请节点&#xff08;初始化&#xff09; 2.2单链表尾插 ​编辑 2.3单链表打印 2.4单链表头插 2.5单链表尾删 2.6单链表头删 2.7单链表查找 2.8在目标位置后面插入…

正点原子嵌入式linux驱动开发——Linux ADC驱动

在之前的笔记中&#xff0c;学习了如何给ICM20608编写IIO驱动&#xff0c;ICM20608本质就是ADC&#xff0c;因此纯粹的ADC驱动也是IIO驱动框架的。本章就学习一下如何使用STM32MP1内部的ADC&#xff0c;并且在学习巩固一下IIO驱动。 ADC简介 ADC ADC&#xff0c;Analog to D…

做一个springboot用户信息模块

目录 用户信息部分 1、获取用户详细信息 前言 代码分析 代码实现 测试 2、更新用户信息 前言 代码实现 测试 3、更新用户头像 前言 代码实现 测试 4、更新用户密码 前言 代码实现 测试 用户信息部分 1、获取用户详细信息 前言 承接上一篇博客登录注册功能…

找工作去哪个网站比较好

吉鹿力招聘网是一个专注于互联网岗位求职招聘的网站&#xff0c;提供海量的互联网人才储备。它主要覆盖了互联网类招聘&#xff0c;包括技术、产品、设计、运营、市场、销售等。吉鹿力招聘网的特点是用户量大&#xff0c;需求旺盛。如果你希望找工作&#xff0c;吉鹿力招聘网是…

[HCTF 2018]admin 1(四种解法!)

题目环境&#xff1a; 有登录和注册两个按钮 先注册一个admin用户 注册admin用户 显示admin用户已经被注册了 好&#xff0c;这就简单了&#xff0c;admin用户存在&#xff0c;但是不清楚admin用户的密码 尝试以下弱口令 第一种解法&#xff1a;密码爆破-尝试弱口令 进去login登…

基于selenium的pyse自动化测试框架

介绍&#xff1a; pyse基于selenium&#xff08;webdriver&#xff09;进行了简单的二次封装&#xff0c;比selenium所提供的方法操作更简洁。 特点&#xff1a; 默认使用CSS定位&#xff0c;同时支持多种定位方法&#xff08;id\name\class\link_text\xpath\css&#xff09…

Python+unittest+requests接口自动化测试框架搭建 完整的框架搭建过程

首先配置好开发环境&#xff0c;下载安装Python并下载安装pycharm&#xff0c;在pycharm中创建项目功能目录。如果不会的可以百度Google一下&#xff0c;该内容网上的讲解还是比较多比较全的&#xff01; 大家可以先简单了解下该项目的目录结构介绍&#xff0c;后面会针对每个文…

第 19 章 网络编程

网络可以使不同物理位置上的计算机达到资源共享和通信的目的&#xff0c;在Java中也提供了专门的网络开发程序包--java.net&#xff0c;以方便开发者进行网络程序的开发&#xff0c;本章将讲解TCP与UDP程序开发 19.1 网络编程简介 将地理位置不同的、具有独立功能的多台计算机…

uview的u-calendar日历组件,当设置了 minDate配置项后,会导致第一次打开日历弹窗,不会精准的滚动到选中的日期(设置了默认日期都没用)

发现需要给month.vue文件里的getMonth方法加一个延时器&#xff0c;猜测是因为设置最小日期后&#xff0c;日历没渲染完毕的时候就已经开始获取节点信息了

C# Onnx Yolov8 Detect 印章 指纹捺印 检测

应用场景 检测文件中的印章和指纹捺印&#xff0c;用于判断文件是否合规&#xff08;是否盖章&#xff0c;是否按印&#xff09; 效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.…