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

news2024/11/17 1:34:37

 

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

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

                                                           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! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

我愿把这个网站成为全球最强AI网站!弄100多个AI伺候你??

家人们&#xff0c;你们猜我发现了什么牛逼的AI网站&#xff1f;&#xff1f; 直接上图&#xff1a; 这个网站&#xff0c;聚合了国内外100多个顶尖的AI&#xff0c;包括了OpenAI家的GPT3.5、GPT4、GPT4V、GPT4.5系列&#xff0c;以及Anthropic家的Claude3 Opus、Claude3 Sone…

Spark-Scala语言实战(11)

在之前的文章中&#xff0c;我们学习了如何在spark中使用RDD中的cartesian,subtract最终两种方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scal…

如何系统地自学Python

1、如何系统地自学Python 小白的话可以快速过一下某马&#xff0c;某谷。 主要关注Python有什么集合&#xff0c;里面的集合怎么使用 然后再找一个Python爬虫实战视频&#xff0c;先跟着视频敲一遍代码&#xff0c;然后再尝试自己做一遍 然后再找一个Python服务开发视频&am…

redis乱码\xac\xed\x00\x05t\x00H解决

发现数据库乱码&#xff1a; 这数据库是来自rdids队列list实现的一个简单队列&#xff0c;停止使用该list的服务&#xff0c;查看里面的值&#xff0c;发现 乱码\xac\xed\x00\x05t\x00H&#xff0c;如下图&#xff1a; 很明发送数据端的问题&#xff0c;检查代码&#xff1a; …

如何保持数据一致性

如何保持数据一致性 数据库和缓存&#xff08;比如&#xff1a;redis&#xff09;双写数据一致性问题&#xff0c;是一个跟开发语言无关的公共问题。尤其在高并发的场景下&#xff0c;这个问题变得更加严重。 问题描述&#xff1a; 1.在高并发的场景中&#xff0c;针对同一个…

【python】python新闻内容zhua取分析词云可视化(源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Redis的基础操作

目录 一、Redis命令工具 1.redis-cli 命令行工具 2.redis-benchmark测试工具 3.Redis数据库五大类型 1、String 2、List 3、Hash&#xff08;散列类型&#xff09; 4、set无序集合 5、sorted set 二、Redis数据库常用命令 1、set与get的使用 2.查看数据库中键的情况…

深度学习:神经网络模型的剪枝和压缩简述

深度学习的神经网路的剪枝和压缩&#xff0c;大致的简述&#xff0c; 主要采用&#xff1a; network slimming&#xff0c;瘦身网络... 深度学习网络&#xff0c;压缩的主要方式&#xff1a; 1.剪枝&#xff0c;nerwork pruing&#xff0c; 2.稀疏表示&#xff0c;sparse rep…

基于向量数据库搭建自己的搜索引擎

前言【基于chatbot】 厌倦了商业搜索引擎搜索引擎没完没了的广告&#xff0c;很多时候&#xff0c;只是需要精准高效地检索信息&#xff0c;而不是和商业广告“斗智斗勇”。以前主要是借助爬虫工具&#xff0c;而随着技术的进步&#xff0c;现在有了更多更方便的解决方案&…

2024-HW --->SSRF

这不是马上准备就要护网了嘛&#xff0c;如火如荼的报名ing&#xff01;&#xff01;&#xff01;那么小编就来查缺补漏一下以前的web漏洞&#xff0c;也顺便去收录一波poc&#xff01;&#xff01;&#xff01;&#xff01; 今天讲的主人公呢就是SSRF&#xff0c;以前学的时候…

QA测试开发工程师面试题满分问答5: 内存溢出和内存泄漏问题

概念阐述 内存溢出&#xff08;Memory Overflow&#xff09;和内存泄漏&#xff08;Memory Leak&#xff09;是与计算机程序中的内存管理相关的问题&#xff0c;它们描述了不同的情况。 内存溢出是指程序在申请内存时&#xff0c;要求的内存超出了系统所能提供的可用内存资源…

不到2000字,轻松带你搞懂STM32中GPIO的8种工作模式

大家好&#xff0c;我是知微&#xff01; 学习过单片机的小伙伴对GPIO肯定不陌生&#xff0c;GPIO &#xff08;general purpose input output&#xff09;是通用输入输出端口的简称&#xff0c;通俗来讲就是单片机上的引脚。 在STM32中&#xff0c;GPIO的工作模式被细分为8种…

N1912A安捷伦N1912A功率计

181/2461/8938产品概述&#xff1a; 安捷伦N1912A双通道P系列宽带功率传感器为R&D和制造工程师提供精确和可重复的功率测量&#xff0c;应用市场包括航空航天和国防&#xff08;雷达&#xff09;、无线通信和无线802.11a/b/g网络。该仪表/传感器组合提供的测量包括峰值功率…

XXLJob中GLUE模式实现在线编写java/shell/python/php/nodejs/powerShell---SpringCloud工作笔记202

1.起因: 之前就一直想实现类似的功能,今天总于找到有可以参考的东西了,这个思路可以帮助实现这种功能. 2.获得灵感 就是:我想实现通过在线编写代码,来扩展我们平台的能力,这样随着业务的扩展,不用我们每次都修改了代码,再去部署,这样就比较麻烦,今天偶尔发现,对于xxljob来说.有…

React 入门

一、官网地址 英文官网: https://reactjs.org/中文官网: https://react.docschina.org/ 二、React 特点 声明式编码组件化编码React Native 编写原生应用高效&#xff08;优秀的 Diffing 算法&#xff09;高效的原因&#xff1a;1.使用虚拟DOM&#xff0c;不总是直接操作页面…

从 Redis 开源协议变更到 ES 国产化:一次技术自主的机遇

引言 近日&#xff0c;Redis Labs 宣布其主导的开源项目 Redis 将采用双重源代码可用许可证&#xff08;RSALv2&#xff09;和服务器端公共许可证&#xff08;SSPLv1&#xff09;。这一重大决策标志着 Redis 从传统的 BSD 许可证向更加严格的控制权转变&#xff0c;同时也引发…

AlexNet网络模型

AlexNet 是一个深度卷积神经网络&#xff0c;由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在 2012 年的 ImageNet 大规模视觉识别挑战赛&#xff08;ILSVRC&#xff09;中首次提出并获得了显著的成功。它是深度学习历史上一个里程碑式的模型&#xff0c;对后来的深…

如何使用PL/SQL Developer工具导出clob字段的表?

1 准备测试数据 导出测试对象&#xff1a;表test_0102&#xff0c;others字段为clob类型 --创建中间表test_0101 create table test_0101( id number, name varchar2(20), others clob);--插入100条测试数据 beginfor i in 1..100 loopinsert into test_0101 values(i,i||_a,l…

文件批量重命名管理,一键将图片的名称进行统一重命名,高效管理文件

在数字时代&#xff0c;我们的生活中充满了各种文件&#xff0c;特别是图片文件。随着时间的推移&#xff0c;我们可能会遇到这样的问题&#xff1a;文件命名不规范&#xff0c;难以快速找到需要的图片。这时&#xff0c;一款强大的文件批量重命名管理工具就显得尤为重要。 首…

JavaScript(一)基础

文章目录 一、JS介绍JavaScript是什么JavaScript书写位置JavaScript的注释输入输出语法字面量 二、变量变量是什么变量基本使用变量的本质变量命名规则与规范变量拓展-数组var与let的区别 三、常量四、数据类型数据类型检测数据类型数据类型转换隐式转换显式转换 简单运算符断点…