计算机网络—TCP协议详解:特性、应用(2)

 

                                        🎬慕斯主页修仙—别有洞天

                                       ♈️今日夜电波:マリンブルーの庭園—ずっと真夜中でいいのに。

                                                           0:34━━━━━━️💟──────── 3:34
                                                                🔄   ◀️   ⏸   ▶️    ☰  

                                 💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

协议的定制

协议定制的概念

封装套接字

序列化和反序列化

序列化(Serialization)

反序列化(Deserialization)

我们该如何保证数据的完整性?

实现一个自定义协议

根据上面实现的封装实现网络版计算器

封装计算方法

服务器的封装实现

服务器的实现

客户端的实现

json

1. 创建JSON对象

2. 添加成员到JSON对象

3. 创建JSON数组

4. 嵌套JSON对象

5. 生成JSON字符串

注意事项

示例:生成一个完整的JSON对象

1. 包含头文件

2. 解析JSON字符串

3. 访问JSON对象成员

4. 访问JSON数组元素

5. 访问嵌套JSON对象

6. 类型检查和转换

注意事项

示例:接收一个完整的JSON对象

使用json序列化和反序列化的版本

再谈OSI七层模型


协议的定制

协议定制的概念

        协议定制是指在应用程序中实现的特定通信规则。这些协议根据特定需求进行设计,以实现不同的功能和性能。在计算机网络中,协议是通信双方约定好的一种方式,用于数据的发送、读取以及双方之间的数据通信。这种约定的方式就构成了一种协议,它定义了通信双方之间交换的报文的格式、顺序以及报文发送或接收一条报文或其他事件所采取的动作。

        协议定制主要涉及以下几个关键步骤(非常重要!!!)

  1. 封装套接字:这是自定义协议的基础,通过套接字实现数据的传输。
  2. 构建请求与响应:根据应用程序的需求,定义请求和响应的字段,这些字段本身就是协议的一部分。
  3. 序列化和反序列化:发送数据时,将结构体按照一定规则转换成字符串;接收数据时,按照相同的规则将字符串转换回结构体。这个过程确保了数据的完整性和准确性。
  4. 报头添加和去除:根据协议规范,在数据包的特定位置添加或去除报头信息。
  5. 报文读取:从接收缓冲区中读取数据,并根据协议规范解析报文内容。

        自定义协议的优势在于其灵活性、可靠性和高效性。它可以根据应用程序的需求进行设计,实现特定的功能和性能;同时,通过协议的定制,可以确保数据在传输过程中的完整性和安全性;此外,根据应用程序的需求优化协议,还可以提高通信效率。

        在实际应用中,自定义协议广泛用于各种场景,如游戏开发、物联网设备通信以及数据传输等。通过自定义协议,可以实现更高效、更安全的数据通信,满足不同应用场景的需求。

        接下来我们按照上述的步骤一步一步实现协议的定制,通过编写一个网络版计算器来理解:

封装套接字

        前面的文章中我们已经详细的介绍了TCP和UDP常用的套接字,对于他们的封装可以根据自身的需求来,这里结合了之前文章的日志功能来实现:

Socket.hpp

#pragma once


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

const int backlog = 10;

enum
{
    SocketErr = 2,
    BindErr,
    ListenErr,
};

class Socke
{

public:
    Socke() : _sockfd(-1) {}

    ~Socke() {}

public:
    void Socket()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg.LogMessage(Fatal, "socker error, %s: %d", strerror(errno), errno);
            exit(SocketErr);
        }
    }

    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_port = htons(port);
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.LogMessage(Fatal, "bind error,%s,%d", strerror(errno), errno);
            exit(BindErr);
        }
    }

    void Listen()
    {
        if (listen(_sockfd, backlog) < 0)
        {
            lg.LogMessage(Fatal, "listen error,%s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }

    int Accept(string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len;
        int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg.LogMessage(Warning, "accept error, %s: %d", strerror(errno), 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;
    }

    bool Connect(const string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        bzero(&peer, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(_sockfd, (const struct sockaddr *)&peer, sizeof(peer));

        if (n < 0)
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false;
        }
        return true;
    }

    void Close()
    {
        close(_sockfd);
    }

    int Fd()
    {
        return _sockfd;
    }

private:
    int _sockfd;
};

序列化和反序列化

        在Linux中,序列化和反序列化是数据处理中的两个重要概念,尤其在涉及到进程间通信、网络通信或数据存储时。下面将详细解释这两个概念:

序列化(Serialization)

        序列化是将数据结构或对象状态转换为可以存储或传输的形式的过程。在Linux(以及大多数其他编程环境中),序列化通常意味着将数据结构转换为字节流,这样它就可以被写入文件、通过网络发送,或者作为其他程序或进程可以读取的数据。

        序列化的主要目的是:

  1. 持久化:将对象或数据结构保存到磁盘或其他存储介质中,以便以后恢复。
  2. 传输:通过网络发送对象或数据结构。

        在Linux中,你可以使用多种方法进行序列化,包括但不限于:

  • 使用标准库:例如,C++的boost::serialization
  • 自定义格式:例如,将数据结构转换为JSON、XML或Protocol Buffers等格式。
  • 使用系统调用:如fwrite等用于将数据写入文件。

大致的图解:

反序列化(Deserialization)

        反序列化是序列化的逆过程,即从字节流中恢复数据结构或对象状态。在Linux中,当你从文件或网络中读取数据时,通常需要将这些数据反序列化为原始的数据结构或对象,以便程序可以理解和使用这些数据。

反序列化的主要目的是:

  1. 恢复状态:从持久化存储中读取数据并恢复对象或数据结构的状态。
  2. 接收数据:从网络接收数据并将其转换为程序可以处理的数据结构或对象。

        在Linux中反序列化的方法通常与序列化时使用的方法相对应。例如,如果你使用boost::serialization库进行序列化,那么你也会使用相同的库进行反序列化。同样,如果你将数据保存为JSON格式,你将使用JSON解析器进行反序列化。

大致的图解:

我们该如何保证数据的完整性?

        实际上,前面的文章中提到的write、read像读写文件一样操作网络是不准确的,我们的write实际上是将应用层的数据拷贝到传输层中的缓冲区中,而read则是将传输层的数据拷贝到应用层中!由于TCP和UDP都是全双工的,发送和接收的缓冲区是不会冲突的,因此这两项操作是可以同时进行的!

        但是如果这个时候,我们网络波动或者服务器压力变大等等原因导致服务器读取的速度跟不上客户端发送的速度(根据各种各样的原因)导致我们的缓冲区积攒了大量的保文,或者并没有得到完整的报文,那该怎么办呢?这时,就需要使用协议来解决这样的问题了!我们根据协议来读取数据,比如报头中包含着数据的长度、数据的类型等等,以及我们按照一定的分割符来分隔了报文,只有遵循协议规则才会将指定范围的报文读取!或者将报文舍弃,回复重新发送报文的信息等等操作!

实现一个自定义协议

        我们就按照如上所提到的规则制定一个协议,这个协议包含了序列化和反序列化、对于序列化和反序列化的“大字符串”进行相应的添加报头、加工或者卸下报头、解析等等操作:

        根据上图的报文所示,我们分为请求报文以及回应报文两种类来定义协议。需要注意的是我们这两个类Request、Response需要做的仅仅是提供报文数据,而其中对与长度以及\n的添加和去除都是通过两个共用的函数Encode和Decode来实现的,我们的服务器以及客户端都是共用这样一套协议规则的!!!也就是说我们都清楚如何发送和接收报文!!!

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
using namespace std;

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

// "len"\n"x op y"\nXXXXXX
string Encode(string &content)
{
    string package = to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;

    return package;
}

bool Decode(string &package, string *content)
{
    size_t pos = package.find(protocol_sep);
    if (pos == string::npos)
        return false;
    string len_str = package.substr(0, pos);
    size_t len = stoi(len_str);
    size_t total_len = len + len_str.size() + 2;
    if (total_len > package.size())
        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() {}
    //  "x op y"
    bool Serialize(string *out)
    {
        string str = to_string(_x);
        str += blank_space_sep;
        str += _op;
        str += blank_space_sep;
        str += to_string(_y);
        *out = str;
        return true;
    }

    bool Deserialize(const string &in)
    {
        size_t left = in.find(blank_space_sep);
        if (left == string::npos)
            return false;
        string p_x = in.substr(0, left);

        size_t right = in.rfind(blank_space_sep);
        if (right == string::npos)
            return false;
        string p_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;

        _op = in[left + 1];
        _x = stoi(p_x);
        _y = stoi(p_y);

        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()
    {
    }

    bool Serialize(string *out)
    {
        string str = to_string(_result);
        str += blank_space_sep;
        str += to_string(_code);
        *out = str;
        return true;
    }
    // "result code"
    bool Deserialize(const std::string &in)
    {
        ssize_t pos = in.find(blank_space_sep);
        if (pos == string::npos)
            return false;
        string p_l = in.substr(0, pos);
        string p_r = in.substr(pos + 1);

        _result = stoi(p_l);
        _code = stoi(p_r);

        return true;
    }

    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << _result << ", code: " << _code << std::endl;
    }

public:
    int _result;
    int _code;
};

根据上面实现的封装实现网络版计算器

封装计算方法

        实际上就是处理服务器从网络读取到了“大字符串”后我们先进行报头的去除(这一步也可以认为是验证的数据完整性的过程)、解析出其中的数据(反序列化),将得到的数据进行计算。计算完成后,将数据进行序列化,然后添加报头,再将处理好的结果的“大字符串”推送到网络中,最后由客户端接收。实现如下:

ServerCal.hpp

#pragma once
#include <iostream>
#include "Protocol.hpp"

enum
{
    Div_Zero = 1,
    Mod_Zero,
    Other_Oper
};

class ServerCal
{
    public:
    ServerCal()
    {}

    Response CalculatorHelper(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 = Div_Zero;
            else
                resp._result = req._x / req._y;
        }
        break;
        case '%':
        {
            if (req._y == 0)
                resp._code = Mod_Zero;
            else
                resp._result = req._x % req._y;
        }
        break;
        default:
            resp._code = Other_Oper;
            break;
        }

        return resp;
    }

    string Calculator(string &package)
    {
        string content;
        bool r = Decode(package, &content);
        if (!r)
            return "";

        Request req;
        r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20
        if (!r)
            return "";

        Response resp = CalculatorHelper(req); // result=30 code=0;

        resp.Serialize(&content);  // "30 0"
        content = Encode(content); // "len"\n"30 0"

        return content;
    }

    ~ServerCal()
    {
    }
};

服务器的封装实现

        我们实现一个基于TCP协议的多进程服务器的封装:

TcpServer.hpp

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

using func_t = function<string(string &)>;

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback)
        : _port(port), _callback(callback)
    {
    }

    bool InitServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
        lg.LogMessage(Info, "init server .... done");
        return true;
    }
    void Start()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        while (true)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = _listensock.Accept(&clientip, &clientport);
            if (sockfd < 0)
                continue;
            lg.LogMessage(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
            // 提供服务
            if (fork() == 0)
            {
                _listensock.Close();
                std::string inbuffer_stream;
                // 数据计算
                while (true)
                {
                    char buffer[1280];
                    ssize_t n = read(sockfd, buffer, sizeof(buffer));
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        inbuffer_stream += buffer;

                        lg.LogMessage(Debug, "debug:\n%s", inbuffer_stream.c_str());

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

                exit(0);
            }
            close(sockfd);
        }
    }

     ~TcpServer()
    {
    }

private:
    uint16_t _port;
    Socke _listensock;
    func_t _callback;
};

服务器的实现
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include <unistd.h>

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

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

    return 0;
}

客户端的实现
#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"

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

// ./clientcal ip port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    Socke sockfd;
    sockfd.Socket();
    bool r = sockfd.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);

        write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: " << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;


        char buffer[128];
        ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文
        if(n > 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer; // "len"\n"result code"\n
            std::cout << inbuffer_stream << std::endl;
            std::string content;
            bool r = Decode(inbuffer_stream, &content); // "result code"
            assert(r);

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

            resp.DebugPrint();
        }

        std::cout << "=================================================" << std::endl;
        sleep(1);

        cnt++;
    }


    sockfd.Close();
    return 0;
}

json

        在C++中,使用jsoncpp库生成JSON数据主要遵循JSON数据的语法规则,并结合jsoncpp提供的API来构建JSON对象。以下是一些关于使用jsoncpp生成JSON数据的基本规则和示例:

1. 创建JSON对象

        你可以使用Json::Value对象来表示一个JSON实体(对象、数组、字符串、数字、布尔值或null)。

Json::Value root; // 创建一个空的JSON对象

2. 添加成员到JSON对象

        你可以使用operator[]add()方法来向JSON对象添加成员。

root["key"] = "value"; // 添加一个字符串成员
root["number"] = 123;  // 添加一个数字成员
root["boolean"] = true; // 添加一个布尔成员

3. 创建JSON数组

        你可以将Json::Value对象设置为数组类型,并使用append()方法或operator[]来添加元素。

Json::Value array; // 创建一个空的JSON数组
array.append("element1"); // 添加一个字符串元素
array.append(123); // 添加一个数字元素
root["array"] = array; // 将数组添加到JSON对象中

4. 嵌套JSON对象

        你可以在JSON对象内部添加其他JSON对象作为成员。

Json::Value nestedObject;
nestedObject["nestedKey"] = "nestedValue";
root["nested"] = nestedObject; // 将嵌套对象添加到JSON对象中

5. 生成JSON字符串

        使用Json::StreamWriterBuilderJson::writeString()方法将Json::Value对象转换为JSON格式的字符串。

Json::StreamWriterBuilder writer;
std::string jsonString = Json::writeString(writer, root);

注意事项
  • 当你向Json::Value对象添加成员时,如果该成员已经存在,那么新值将覆盖旧值。
  • jsoncpp不会自动转义字符串中的特殊字符。如果需要转义,你可能需要手动处理或确保你的字符串是安全的。
  • 在处理JSON数据时,确保你的数据类型与JSON类型相匹配。例如,不要将字符串赋值给期望为数字的JSON成员。
  • 生成的JSON字符串将遵循标准的JSON格式,包括使用双引号包围字符串、使用逗号分隔成员等。

示例:生成一个完整的JSON对象
#include <json/json.h>
#include <iostream>

int main() {
    Json::Value root;
    root["name"] = "John Doe";
    root["age"] = 30;
    root["isStudent"] = false;

    Json::Value hobbies;
    hobbies.append("reading");
    hobbies.append("swimming");
    root["hobbies"] = hobbies;

    Json::Value address;
    address["street"] = "123 Main St";
    address["city"] = "Anytown";
    root["address"] = address;

    Json::StreamWriterBuilder writer;
    std::string jsonString = Json::writeString(writer, root);
    std::cout << jsonString << std::endl;

    return 0;
}

输出可能类似于:

{
   "name": "John Doe",
   "age": 30,
   "isStudent": false,
   "hobbies": [
      "reading",
      "swimming"
   ],
   "address": {
      "street": "123 Main St",
      "city": "Anytown"
   }
}

        在C++中,使用jsoncpp库接收JSON数据主要涉及到解析JSON字符串以创建一个Json::Value对象,然后从这个对象中读取和访问数据。以下是关于使用jsoncpp接收JSON数据的一些基本规则和步骤:

1. 包含头文件

        首先,你需要包含jsoncpp的头文件。

#include <json/json.h>

2. 解析JSON字符串

        使用Json::Reader类来解析JSON字符串。

std::string jsonString = /* JSON字符串 */;
Json::Reader reader;
Json::Value root;
bool parsingSuccessful = reader.parse(jsonString, root);

if (!parsingSuccessful) {
    // 解析失败,处理错误
    std::cerr << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
    // 退出或采取其他错误处理措施
}

3. 访问JSON对象成员

        使用[]运算符或get()方法来访问JSON对象的成员。

if (root.isMember("key")) {
    std::string value = root["key"].asString();
    // 或者使用get方法,并提供默认值以防成员不存在
    std::string valueWithDefault = root.get("key", "default_value").asString();
} else {
    // 成员不存在,处理这种情况
}

4. 访问JSON数组元素

        使用size()方法获取数组大小,并使用索引来访问数组元素。

if (root.isArray()) {
    for (Json::Value::ArrayIndex i = 0; i < root.size(); ++i) {
        std::string element = root[i].asString();
        // 处理每个元素
    }
}

5. 访问嵌套JSON对象

        如果JSON对象包含嵌套的对象或数组,你可以通过链式访问来读取它们。

if (root.isMember("nested") && root["nested"].isObject()) {
    std::string nestedValue = root["nested"]["nestedKey"].asString();
}

6. 类型检查和转换

        在访问JSON值之前,最好先检查其类型,以确保你正在处理正确的数据类型。jsoncpp提供了各种is...()方法来检查值的类型。

if (root["key"].isString()) {
    std::string value = root["key"].asString();
} else if (root["key"].isInt()) {
    int value = root["key"].asInt();
}
// ... 其他类型检查

 

注意事项

  • 确保你传入的JSON字符串是有效的,否则Json::Reader将无法正确解析它。
  • 在访问成员或元素之前,总是进行类型检查和存在性检查,以避免程序崩溃或产生意外的结果。
  • 使用默认值可以避免因成员不存在而导致的程序错误,但你应该清楚地了解使用默认值的含义和后果。
  • jsoncpp不会自动转换数据类型。例如,如果你尝试将一个数字作为字符串读取,而该值实际上不是字符串,那么程序将不会按预期工作。

示例:接收一个完整的JSON对象
#include <json/json.h>
#include <iostream>

int main() {
    std::string jsonString = R"({
        "name": "John Doe",
        "age": 30,
        "isStudent": false,
        "hobbies": ["reading", "swimming"],
        "address": {
            "street": "123 Main St",
            "city": "Anytown"
        }
    })";

    Json::Reader reader;
    Json::Value root;
    bool parsingSuccessful = reader.parse(jsonString, root);

    if (!parsingSuccessful) {
        std::cerr << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
        return 1;
    }

    if (root.isMember("name")) {
        std::cout << "Name: " << root["name"].asString() << std::endl;
    }
    
    if (root.isMember("age") && root["age"].isInt()) {
        std::cout << "Age: " << root["age"].asInt() << std::endl;
    }
    
    if (root.isMember("hobbies") && root["hobbies"].isArray()) {
        std::cout << "Hobbies:" << std::

使用json序列化和反序列化的版本

#pragma once

#include <iostream>
#include <string>
#include <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;
}

// "len"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
bool Decode(std::string &package, std::string *content)
{
    std::size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos) return false;
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content_str + 2
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;

    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);

    return true;
}


// json, protobuf
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"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);
        *out = s;
        return true;
#else
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        std::size_t left = in.find(blank_space_sep);
        if (left == std::string::npos)
            return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if (right == std::string::npos)
            return false;
        std::string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        op = in[left + 1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;
    }
public:
    // x op y
    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
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);
        *out = s;
        return true;
#else
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        std::size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
            return false;
        std::string part_left = in.substr(0, pos);
        std::string part_right = in.substr(pos+1);

        result = std::stoi(part_left);
        code = std::stoi(part_right);

        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
        return true;
#endif

    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
    }
public:
    int result;
    int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

再谈OSI七层模型

        我们在大致的实现了网络计算器后,接下来再次感受一下下面的这张OSI七层模型,我们可以很惊喜的发现,传输层实际上就包含了我们封装的socket。而会话层则对应着我们封装的服务器,他负责维护整个链接的状况。而表示层不就是对应着我们的序列化和反序列化->添加报头等处理->定制固有的数据格式,不就是我们制定的协议吗?应用层不就是对应着我们的计算、处理方法吗?

 


                       感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

基于卷积神经网络的苹果等级分类系统(pytorch框架)【python源码+UI界面+前端界面+功能源码详解】

功能演示&#xff1a; 苹果等级分类系统&#xff0c;基于vgg16&#xff0c;resnet50卷积神经网络&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神经网络的苹果等级分类系统是在pytorch框架下实现的&#xff0c;系统中有两…

LangChain-05 RAG Conversational 增强检索会话

安装依赖 pip install --upgrade --quiet langchain-core langchain-community langchain-openai编写代码 from langchain_core.messages import AIMessage, HumanMessage, get_buffer_string from langchain_core.prompts import format_document from langchain_core.runn…

腾讯云轻量服务器8核16G服务器价格1668元一年送3个月,18M大带宽

腾讯云轻量应用服务器8核16G配置租用优惠价格1668元15个月&#xff0c;买一年送3个月&#xff0c;配置为&#xff1a;轻量8核16G18M、270GB SSD盘、3500GB月流量、18M带宽&#xff0c;腾讯云优惠活动 yunfuwuqiba.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云8核16G服务器…

基于java+SpringBoot+Vue的网上订餐系统设计与实现

基于javaSpringBootVue的网上订餐系统设计与实现 开发语言: Java 数据库: MySQL技术: Spring Boot JSP工具: IDEA/Eclipse、Navicat、Maven 系统展示 前台展示 菜品浏览与选择&#xff1a;用户可以浏览不同的菜品分类&#xff0c;并选择心仪的菜品。 订单创建与管理&…

多线程--深入探究多线程的重点,难点以及常考点线程安全问题

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

SpringBoot登录校验(四)过滤器Filter

JWT令牌生成后&#xff0c;客户端发的请求头中会带有JWT令牌&#xff0c;服务端需要校验每个请求的令牌&#xff0c;如果在每个controller方法中添加校验模块&#xff0c;则十分复杂且冗余&#xff0c;所以引入统一拦截模块&#xff0c;将请求拦截下来并做校验&#xff0c;这块…

100道面试必会算法-18-岛屿问题(数量、周长、面积)

100道面试必会算法-18-岛屿问题&#xff08;数量、周长、面积&#xff09; 题目描述 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平…

银行数字化转型导师坚鹏:银行数字化转型给支行带来的8大价值

银行数字化转型给支行带来的8大价值 银行数字化转型对不仅对总行、分行产生了深远影响&#xff0c;给总行、分行带来了新质生产力&#xff0c;对银行支行&#xff08;包括网点&#xff09;也会产生重要价值&#xff0c;银行数字化转型导师坚鹏从以下8个方面进行详细分析&#…

Linux多进程通信(4)——消息队列从入门到实战!

Linux多进程通信总结——进程间通信看这一篇足够啦&#xff01; 1.基本介绍 1&#xff09;消息队列的本质其实是一个内核提供的链表&#xff0c;内核基于这个链表&#xff0c;实现了一个数据结构&#xff0c;向消息队列中写数据&#xff0c;实际上是向这个数据结构中插入一个…

keil创建工程 芯源半导体CW32F003E4P7

提前下载keil 安装步骤 1、下载CW32F003固件库 芯源半导体官网下载固件库 下载好后右键解压 CW32F003_StandardPeripheralLib_V1.5\IdeSupport\MDK 进入MDK文件夹 双击WHXY.CW32F003_DFP.1.0.4.pack安装固件库 点击next然后finish安装结束 keil创建工程 点击new uVision P…

【软件工程】详细设计(一)

1. 引言 1.1 编写目的 该文档的目的是描述《学生成绩管理系统》项目的详细设计&#xff0c;其主要内容包括&#xff1a; 系统功能简介 系统详细设计简述 各个模块的实现逻辑 最小模块组件的伪代码 本文档的预期的读者是&#xff1a; 开发人员 项目管理人员 测试人员 …

插入排序---算法

1、算法概念 插入排序&#xff1a;它的工作原理是通过构建有序排序&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置插入。 2、算法步骤 将第一待排序序列第一个元素看作一个有序序列&#xff0c;把第二个元素到最后一个元素当成是…

Exchanger 怎么用J.U.C

Exchanger简介 Exchanger通常用来解决以下类似场景的问题&#xff0c;如下&#xff1a;两个线程间需要交换数据的问题&#xff0c;在多线程编程中&#xff0c;经常会有这样的场景&#xff1a;两个线程各自持有一些数据&#xff0c;并且需要在某个点上交换这些数据&#xff0c;…

【项目实战】【Docker】【Git】【Linux】部署V2rayA项目

今天着手了一个全新领域的项目&#xff0c;从完全没有头绪到成功运行&#xff0c;记录一下具体的部署流程 github项目链接V2rayA 一开始拿到以后完全没有抓手&#xff0c;去阅读了一下他的帮助文档 写着能用docker运行&#xff0c;就去下载了一个Docker配置了一下 拉取代码到…

输入url到页面显示过程的优化

浏览器架构 线程&#xff1a;操作系统能够进行运算调度的最小单位。 进程&#xff1a;操作系统最核心的就是进程&#xff0c;他是操作系统进行资源分配和调度的基本单位。 一个进程就是一个程序的运行实例。启动一个程序的时候&#xff0c;操作系统会为该程序创建一块内存&a…

基于java+SpringBoot+Vue的学生心理咨询评估系统设计与实现

基于javaSpringBootVue的学生心理咨询评估系统设计与实现 开发语言: Java 数据库: MySQL技术: Spring Boot MyBatis工具: IDEA/Eclipse、Navicat、Maven 系统展示 后台展示 用户管理模块&#xff1a;管理员可以查看、添加、编辑和删除用户信息。 试题管理模块&#xff1a…

光伏智慧管理技术创新,提高能源利用率!

光伏电站的建设规模正在不断扩大&#xff0c;运维与管理成为了一个重要的问题。随着科技的迅速发展&#xff0c;智慧光伏将成为光伏发电系统的发展趋势。智慧光伏主要是通过传感器、通信设备和数据处理技术&#xff0c;实现对光伏电站的检测、控制和优化管理&#xff0c;从而提…

Head First Design Patterns -代理模式

什么是代理模式 代理模式为另一个对象提供替身或者占位符&#xff0c;以便控制客户对对象的访问&#xff0c;管理访问的方式有很多种。例如远程代理、虚拟代理、保护代理等。 远程代理&#xff1a;管理客户和远程对象之间的交互。 虚拟代理&#xff1a;控制访问实例化开销大的对…

利用Lora调整和部署 LLM

使用 NVIDIA TensorRT-LLM 调整和部署 LoRA LLM 大型语言模型 (LLM) 能够从大量文本中学习并为各种任务和领域生成流畅且连贯的文本&#xff0c;从而彻底改变了自然语言处理 (NLP)。 然而&#xff0c;定制LLM是一项具有挑战性的任务&#xff0c;通常需要完整的培训过程&#xf…

论文阅读:Walk These Ways: 通过行为多样性调整机器人控制以实现泛化

Walk These Ways: 通过行为多样性调整机器人控制以实现泛化 摘要&#xff1a; 通过学习得到的运动策略可以迅速适应与训练期间经历的类似环境&#xff0c;但在面对分布外测试环境失败时缺乏快速调整的机制。这就需要一个缓慢且迭代的奖励和环境重新设计周期来在新任务上达成良…