【网络】应用层协议(自定义协议)(序列和反序列化)


一、引言–应用层的使用

在这里插入图片描述

二、应用层

协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

1、网络版本计算器

(1)协议定制和序列反序列化

在这里插入图片描述

结构化数据,所有主机都要是一样的结构体类型(协议的定制),再通过网络的序列和反序列化方便数据的收发。

(2)网络版计算器协议定制

在这里插入图片描述
在这里插入图片描述

i、封装有效载荷(默认上面图是Request的,下面图是Response的)

其实就是将这个结构体转化成为"x op y"!和"result code"!
在这里插入图片描述
在这里插入图片描述

ii、封装报头(单独出来的函数)

“len”\n"s"

在这里插入图片描述

iii、分离有效载荷(默认上面图是Request的,下面图是Response的)

将string s 进行分离出来,分离成整型或者char类型的。
在这里插入图片描述
在这里插入图片描述

iv、解析报文(单独出来的函数)

在这里插入图片描述

v、测试结果

在这里插入图片描述

(3)计算器函数

CalHelper函数是进行完加减乘除取模后返回结果的函数,Calculator是将计算结果进行封装完报头的函数。

    Response CalHelper(const Request &req)
    {
        Response resp(0, 0);
        switch (req.op)
        {
        case '+':
            resp.result = req.x + req.y;
            break;
        case '-':
            resp.result = req.x - req.y;
            break;
        case '*':
            resp.result = req.x * req.y;
            break;
        case '/':
        {
            if (req.y == 0)
            {
                resp.code = DIVERRO;
            }
            else
            {
                resp.result = req.x / req.y;
            }
            break;
        }
        case '%':
        {
            if (req.y == 0)
            {
                resp.code = MOERROR;
            }
            else
            {
                resp.result = req.x % req.y;
            }
            break;
        }
        default:
            resp.code = OTHERERROR;
            break;
        }
        return resp;
    }
    // "len"\n"10 + 20"\n
    std::string Calculator(std::string &package)
    {
        std::string content;
        bool r = Decode(package, &content); // 分离出有效载荷 // "len"\n"10 + 20"\n
        if (!r)
        {
            return "";
        }
        // 反序列化 -- 分离开每一个元素
        Request req;
        r = req.Deserialize(content); // x=10 op=+ y=20
        if (!r)
        {
            return "";
        }
        // 处理结果
        content = "";
        Response resp = CalHelper(req); // result=30 code=0
        // 序列化
        resp.Serialize(&content); // "30 0"
        // 封装报头
        content = Encode(content); // "len"\n"30 0"\n
        return content;
    }

(4)计算器与网络连接(网络服务代码)

代码逻辑为:InitTcpServer函数是用来进行监听套接字的设置绑定和监听的,RunTcpServer函数是用来将底层的监听套接字拿到上层来使用并进行提供服务的。

#pragma once

#include "Log.hpp"
#include "Socket.hpp"
#include <signal.h>
#include <functional>

using func_t = std::function<std::string(std::string &package)>;

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback)
        : _port(port), _callback(callback)
    {
    }
    bool InitTcpServer()
    {
        _listensocketfd.Socket();
        _listensocketfd.Bind(_port);
        _listensocketfd.Listen();
        lg(Info, "init servercal...");
        return true;
    }
    void RunTcpServer()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        while (true)
        {
            std::string clientip;
            uint16_t clientport;
            int socketfd = _listensocketfd.Accept(&clientip, &clientport);
            if (socketfd < 0)
            {
                continue;
            }
            lg(Info, "accept a new link..., sockfd:%d, clientip:%s, clientport:%d", socketfd, clientip.c_str(), clientport);
            // 提供服务
            // pid_t id = fork();
            if (fork() == 0)
            {
                _listensocketfd.Close();
                std::string inbuffer_stream;
                // 数据计算
                while (true)
                {
                    char buffer[128];
                    ssize_t n = read(socketfd, buffer, sizeof(buffer));
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        inbuffer_stream += buffer;

                        lg(Debug, "debug:%s", inbuffer_stream.c_str());
                        // while (true)
                        //{
                        std::string info = _callback(inbuffer_stream);
                        if (info.empty())
                            continue;
                        // break;
                        // lg(Debug, "debug, response:\n%s", info.c_str());
                        // lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
                        write(socketfd, info.c_str(), info.size());
                        //}
                    }
                    else if (n == 0)
                    {
                        break;
                    }
                    else
                        break;
                }

                exit(0);
            }
            close(socketfd);
        }
    }
    ~TcpServer()
    {
    }

private:
    Sock _listensocketfd;
    uint16_t _port;
    func_t _callback;
};

回调函数示意:
在这里插入图片描述

(5)附加:Socket封装

#pragma once

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

//extern Log lg;

enum
{
    SOCKETERR = 2,
    BINDERR,
    LISTENERR
};

const int backlog = 10;

class Sock
{
public:
    Sock()
    {
    }
    ~Sock()
    {
    }

public:
    void Socket()
    {
        _socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_socketfd < 0)
        {
            lg(Fatal, "socket create err, %d:%s", errno, strerror(errno));
            exit(SOCKETERR);
        }
    }
    void Bind(uint16_t port)
    {
        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;
        if (bind(_socketfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %d:%s", errno, strerror(errno));
            exit(BINDERR);
        }
    }
    void Listen()
    {
        if (listen(_socketfd, backlog) < 0)
        {
            lg(Fatal, "listen error, %d:%s", errno, strerror(errno));
            exit(LISTENERR);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_socketfd, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg(Warning, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &(peer.sin_addr), ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);
        return newfd;
    }
    int Connect()
    {
        return 0;
    }
    void Close()
    {
        close(_socketfd);
    }

private:
    int _socketfd;
};

(6)服务端代码

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Servercal.hpp"

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage" << proc << " port\n\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t clientport = std::stoi(argv[1]);
    ServerCal cal;
    TcpServer *tsvp = new TcpServer(clientport, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
    tsvp->InitTcpServer();
    tsvp->RunTcpServer();
    return 0;
}

(7)测试结果

在这里插入图片描述

测试结果Debug

在这里插入图片描述

解决方法:每一次都删掉规定好的报文

在这里插入图片描述

(8)客户端编写

Socket.hpp:
在这里插入图片描述
ClientCal.cc:

#include <iostream>
#include <unistd.h>
#include <cassert>
#include "Socket.hpp"
#include "Protocol.hpp"

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

// ./clientcal 127.0.0.1 8888
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];
    Sock socketfd;
    socketfd.Socket();
    bool r = socketfd.Connect(serverip, serverport);
    if (!r)
        return 1;

    srand(time(nullptr) ^ getpid());
    int cnt = 1;
    const std::string opers = "+-*/%=^";
    std::string inbuffer_stream;
    while (cnt <= 10)
    {
        std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;
        int x = rand() % 100 + 1;
        usleep(1234);
        int y = rand() % 100;
        usleep(4321);
        char oper = opers[rand() % opers.size()];
        Request req(x, y, oper);
        req.DebugPrint();

        std::string package;
        req.Serialize(&package);
        package = Encode(package);
        std::cout << "这是新的发出去的请求:\n" << package;
        write(socketfd.Fd(), package.c_str(), package.size());

        char buffer[128];
        ssize_t n = read(socketfd.Fd(), buffer, sizeof(buffer)); // 保证不了读到的是一个完整的报文
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer;
            std::string content;
            bool rqs = Decode(inbuffer_stream, &content);
            assert(rqs);

            Response resp;
            rqs = resp.Deserialize(content);
            assert(rqs);

            resp.DebugPrint();
        }
        std::cout << "=================================================" << std::endl;
        sleep(1);
        cnt++;
    }
    socketfd.Close();
    return 0;
}

测试结果:
在这里插入图片描述

(9)客户端多个请求同时发送

在这里插入图片描述

在这里插入图片描述

2、json(用来自动支持序列反序列化)

安装命令:sudo yum install -y jsoncpp-devel
在这里插入图片描述
安装成功:
在这里插入图片描述

头文件:

#include <jsoncpp/json/json.h>

使用:
FastWriter:
测试用例一(序列化):

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::FastWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;
    return 0;
}

编译:
g++ test.cc -ljsoncpp
在这里插入图片描述
测试用例二(反序列化):
FastWriter:

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::FastWriter w;
    std::string res = w.write(root);
    
    sleep(3);

    Json::Value v;
    Json::Reader r;
    r.parse(res, v);

    int x = v["x"].asInt();
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();
    Json::Value temp = v["test"];
    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;
    return 0;
}

在这里插入图片描述

测试用例三(序列化):
StyleWriter:

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::StyleWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;
    return 0;
}

在这里插入图片描述

3、用json进行序列反序列化结果

Makefile:

.PHONY:all
all:servercal clientcal

Flag=-DMySelf=1
Lib=-ljsoncpp

servercal:ServerCalculator.cc
	g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag)
clientcal:ClientCalculator.cc
	g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag)

.PHONY:clean
clean:
	rm -f servercal clientcal

Protocol.hpp:

#pragma once
// 定制协议
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// #define MySelf 1

const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";

std::string Encode(std::string &content)
{
    // 封装报头
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;
    return package;
}

bool Decode(std::string &package, std::string *content) // "len"\n"x op y"\n
{
    // package = len_str + content_str + 2 总长度等于长度字符串长度+正文长度+两个\n的长度
    size_t pos = package.find(protocol_sep);
    if (pos == std::string::npos)
    {
        return false;
    }
    std::string len_str = package.substr(0, pos);
    // 分理出长度
    size_t len = std::stoi(len_str);
    size_t total_len = len_str.size() + len + 2;
    if (package.size() < total_len)
    {
        return false;
    }
    // 完整报文--提取有效载荷
    *content = package.substr(pos + 1, len);
    // 移除报文,防止多余报文影响
    package.erase(0, total_len);
    return true;
}

class Request
{
public:
    Request(int data1, int data2, char oper)
        : x(data1), y(data2), op(oper)
    {
    }
    Request()
    {
    }

public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建有效载荷
        // struct --> string "x op y" => "len"\n"x op y"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);

        *out = s;
#else
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        Json::FastWriter w;
        *out = w.write(root);
#endif
        return true;
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        size_t leftpos = in.find(blank_space_sep);
        if (leftpos == std::string::npos)
        {
            return false;
        }
        std::string part_x = in.substr(0, leftpos);

        size_t rightpos = in.rfind(blank_space_sep);
        if (rightpos == std::string::npos)
        {
            return false;
        }
        std::string part_y = in.substr(rightpos + 1);
        if (leftpos + 1 != rightpos - 1)
        {
            return false;
        }
        op = in[leftpos + 1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();
#endif
        return true;
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:" << x << op << y << "=?" << std::endl;
    }

public:
    int x;
    int y;
    char op; // +-*/
};

class Response
{
public:
    Response(int res, int c)
        : result(res), code(c)
    {
    }
    Response()
    {
    }

public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建有效载荷
        // "len"\n"result code"
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);

        *out = s;
#else
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        Json::FastWriter w;
        *out = w.write(root);
#endif
        return true;
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
        {
            return false;
        }
        std::string res = in.substr(0, pos);
        std::string cod = in.substr(pos + 1);
        result = std::stoi(res);
        code = std::stoi(cod);
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
#endif
        return true;
    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, 结果是:" << "result:" << result << "  " << "code:" << code << std::endl;
    }

public:
    int result;
    int code; // 0表示可信,非零表示不可信,相对应的数字表示相对应的错误原因
};

在这里插入图片描述

三、重谈OSI7层模型

在这里插入图片描述
这三层不在内核中实现,是我们手动实现。

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2022horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值