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

news/2024/5/17 15:14:02 标签: 计算机网络, 网络, tcp

 

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

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

                                                           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.niftyadmin.cn/n/5469176.html

相关文章

Python学习笔记-Flask接收post请求数据并存储数据库

1.引包 from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy 2.配置连接,替换为自己的MySQL 数据库的实际用户名、密码和数据库名 app Flask(__name__) #创建应用实列 app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://ro…

WebKit结构简介

WebKit是一个开源的浏览器引擎&#xff0c;它负责在网页浏览器中渲染网页内容。作为现代浏览器的核心组件之一&#xff0c;WebKit以其高效、稳定和灵活的特性&#xff0c;在浏览器市场中占据了重要地位。本文将简要介绍WebKit的结构和工作原理。 一、WebKit的主要组成部分 We…

面试算法-138-移动零

题目 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 解 class Solution {pub…

【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)

前言 本篇博客会对排序做一个收尾&#xff0c;将最经典的七大排序介绍完毕。 这次的重点正如标题&#xff0c;主要讲的是归并排序&#xff0c;还会带过相对简单很多的冒泡排序和选择排序。在最后还会给这七大排序做出一个时间复杂度和稳定性展示的总结收尾。同时&#xff0c;这…

全局统一返数据类型封装记录

全局统一返回值封装 ​ 在Spring Boot中&#xff0c;实现全局统一返回值封装是一种常见的做法&#xff0c;它有助于保持API的一致性&#xff0c;并简化前端对响应数据的处理。创建一个响应体类&#xff0c;包含状态码、消息、数据等字段。这个类可以作为所有控制器返回值的通用…

StarRocks实战——携程火车票指标平台建设

目录 前言 一、早期OLAP架构与痛点 二、指标平台重构整体设计 2.1 指标查询过程 2.1.1 明细类子查询 2.1.2 汇总类子查询 2.1.3 “缓存” 2.2 数据同步 三、Starrocks使用经验分享 3.1 建表经验 3.2 数据查询 3.3 函数问题 四、查询性能大幅提升 五、 后续优化方…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-SPI

SPI通信 SPI&#xff0c;&#xff08;serial peripheral interface&#xff09;&#xff0c;字面翻译是串行外设接口&#xff0c;是一种通用的数据总线&#xff0c;适用于主控和外挂芯片之间的通信&#xff0c;与IIC应用领域非常相似。 IIC无论是在硬件电路还是在软件时序设计…

数据结构之二叉树和平衡二叉树

1、二叉树&#xff1a; package com.datastructure.tree; //一个常用的第三方库是Apache Commons Collections&#xff0c;它提供了一个名为BinaryTree的类&#xff0c;用于表示二叉树。 //可以使用org.apache.commons.collections4.BinaryTree类创建二叉树和进行操作。 //可以…