当前位置: 首页 > article >正文

【计网】自定义序列化反序列化(二) —— 实现网络版计算器【上】

🌎 实现网络版计算器【上】


文章目录:

实现网络版计算器【上】

    自定义协议
      制定自定义协议

    Jsoncpp序列化反序列化
      Json::Value类
      Jsoncpp序列化
      Jsoncpp反序列化

    自定义协议序列化反序列化
      Service 服务改写
      服务器端完结


🚀自定义协议

✈️制定自定义协议

  在上一篇我们说了,两台机器想要通过网络进行通信,那么通信双方所遵循的协议必须相同,应用层也是如此,大部分情况,双方首先约定好数据传输格式,那么一端计算机发送的数据,另外一端也就能相应的解析。

  • 那为什么我们需要序列化和反序列化呢

  我们既然定义好了双方的通信协议,那么我们直接按照通信协议进行发送不就行了?这是因为,在很多时候,大型项目里一般底层代码都不会改变,但是上层就说不准了,双方的结构化数据很容易会因为业务需求的变动而变动,每一次变动都可能需要去处理跨平台问题。
  比如Linux x64平台与Linux x32平台的内存对齐方式就不同,如果双方协议一直在改变,那么就必须要一同处理这种平台差异,是一种费时费力不讨好的表现。
  但是我们在双发发数据之前进行了序列化,进行了字符串式的处理,相当于添加了一层软件层。这样上层无论怎么变,底层收到的代码都是字符串,把业务逻辑的改变抛给上层解决,这样就不用因为上层的变动而更改底层代码逻辑

  所以序列化与反序列化问题显得尤为重要,就上一篇所谈论的计算问题进行序列化与反序列化处理。处理两个模块,一个是客户端发来的请求,一个是服务器端处理请求后返回的响应。所以在这里设计两个类 Request 和 Response。

在这里插入图片描述

  我们目的是实现网络版计算器,客户端发送Request,其中包含左操作数,右操作数,以及运算符。服务器端需要对Request进行处理,但是不排除客户端发送的数据是非法运算,所以Response类不仅记录结果,还需要记录运算错误方式。同时,双方都需要进行序列化和反序列化操作:

namespace protocol_ns
{
    class Request
    {
    public:
        Request()
        {}

        Request(int x, int y, char oper):_x(x), _y(y), _oper(oper)
        {}

        bool Serialize(const std::string *message)// 序列化
        {}

        bool Deserialize(const std::string &in)// 反序列化
        {}
    private:
        int _x;
        int _y;
        char _oper;// + - * / %, _x _oper _y
    };

    class Response
    {
    public:
        Response()
        {}

        Response(int result, int code):_result(result), _code(code)
        {}

        bool Serialize(const std::string *message)// 序列化
        {}

        bool Deserialize(const std::string &in)// 反序列化
        {}
    private:
        int _result;
        int _code;// 0: success, 1: 除0, 2: 非法操作
    };
} // namespace protocol_ns

  上述的Request与Response就是双方约定的协议,那么具体的协议内容如何进行规定呢?我们知道,我们发送的数据很可能会积压在发送缓冲区,而Tcp一旦发送有可能一次发送的是多个序列化之后的字符串,那么服务器端在收到这些数据之后需要对每一条完整的数据进行区分。甚至发送过去的数据不完整,需要等待下一次发送,哪些能处理哪些需要延迟处理,都是我们需要考虑的。

  既然是协议,我们就采用其他网络协议那样,定义为 报文 = 报头 + 有效载荷,我们进行如下定义:"有效载荷长度"\r\n"有效载荷" 如果你愿意,你也可以在报头部分加上类型等判定比如 "有效载荷长度""数据类型"\r\n"有效载荷" ,不过这里我们不搞这么麻烦了,就采用前面一种报文方式。

在这里插入图片描述

  其中 \r\n 代表分隔符,将报头部分与 有效载荷进行分离。比如,一个客户端发来请求的格式就是:"有效载荷长度"\r\n"_x _oper _y"\r\n有效载荷长度不记录分隔符,只记录引号内有效载荷的长度

  那么,如果服务器端收到的字符串进行解析,报头部分显示有效载荷长度是100,但是现在只有50,所以我们就需要在等数据完整再进行处理。


🚀Jsoncpp序列化反序列化

  Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。

  • Jsoncpp特性
  1. 简单易用Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单
  2. 高性能Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据
  3. 全面支持支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null
  4. 错误处理在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便开发者调试

其中在Linux环境下安装Jsoncpp库的命令如下

ubuntu: sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

✈️Json::Value类

  Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表:

构造函数

  • Json::Value()默认构造函数,创建一个空的 Json::Value 对象
  • Json::Value(ValueType type, bool allocated = false)根据给定的ValueType(如 nullValue, intValue, stringValue 等)创建一个 Json::Value 对象

访问元素:

  • Json::Value& operator[](const char* key)通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素
  • Json::Value& operator[](const std::string& key)同上,但使用std::string 类型的键
  • Json::Value& operator[](ArrayIndex index)通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素
  • Json::Value& at(const char* key)通过键访问对象中的元素,如果键不存在则抛出异常
  • Json::Value& at(const std::string& key)同上,但使用 std::string类型的键

类型检查

  • bool isNull()检查值是否为 null
  • bool isBool()检查值是否为布尔类型
  • bool isInt()检查值是否为整数类型
  • bool isInt64()检查值是否为 64 位整数类型
  • bool isUInt()检查值是否为无符号整数类型
  • bool isUInt64()检查值是否为 64 位无符号整数类型
  • bool isIntegral()检查值是否为整数或可转换为整数的浮点数
  • bool isDouble()检查值是否为双精度浮点数
  • bool isNumeric()检查值是否为数字(整数或浮点数)
  • bool isString()检查值是否为字符串
  • bool isArray()检查值是否为数组
  • bool isObject()检查值是否为对象(即键值对的集合)

赋值和类型转换:

  • Json::Value& operator=(bool value)将布尔值赋给 Json::Value 对象
  • Json::Value& operator=(int value)将整数赋给 Json::Value 对象
  • Json::Value& operator=(unsigned int value)将无符号整数赋给Json::Value 对象
  • Json::Value& operator=(Int64 value)将 64 位整数赋给 Json::Value对象
  • Json::Value& operator=(UInt64 value)将 64 位无符号整数赋给Json::Value 对象
  • Json::Value& operator=(double value)将双精度浮点赋给Json::Value 对象
  • Json::Value& operator=(const char* value)将 C 字符串赋给Json::Value 对象
  • Json::Value& operator=(const std::string& value)将std::string赋给Json::Value 对象
  • bool asBool()将值转换为布尔类型(如果可能)
  • int asInt()将值转换为整数类型(如果可能)
  • Int64 asInt64()将值转换为 64 位整数类型(如果可能)
  • unsigned int asUInt()将值转换为无符号整数类型(如果可能)
  • UInt64 asUInt64()将值转换为 64 位无符号整数类型(如果可能)
  • double asDouble()将值转换为双精度浮点数类型(如果可能)
  • std::string asString()将值转换为字符串类型(如果可能)

数组和对象操作:

  • size_t size()返回数组或对象中的元素数量
  • bool empty()检查数组或对象是否为空
  • void resize(ArrayIndex newSize)调整数组的大小
  • void clear()删除数组或对象中的所有元素
  • void append(const Json::Value& value)在数组末尾添加一个新元素
  • Json::Value& operator[](const char* key, const Json::Value&defaultValue = Json::nullValue)在对象中插入或访问一个元素,如果键不存在则使用默认值
  • Json::Value& operator[](const std::string& key, const Json::Value& defaultValue = Json::nullValue)同上,但使用 std::string类型

✈️Jsoncpp序列化

  序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化,这里不再做详细解释,直接使用最简单的两种展示给大家:

使用 Json::FastWriter 进行Json格式序列化

  首先,我们先定义结构化数据Stu,结构体内记录的是name,age,weight,首先我们需要使用 Json::Value 对象将结构化数据转化为字符串:

#include <iostream>
#include <jsoncpp/json/json.h>

struct stu
{
    std::string name;
    int age;
    double weight;
};

int main()
{
    // 结构化数据
    struct stu zs = {"阿熊", 20, 80};

    // 转换为字符串
    Json::Value root;
    root["name"] = zs.name;
    root["age"] = zs.age;
    root["weight"] = zs.weight;
    
    // to do ...
    return 0;
}

  接着使用 Json::Writer 对象将root的各个分散字符串转化为一个字符串:

int main()
{
    // 结构化数据
    struct stu zs = {"阿熊", 20, 80};

    // 转换为字符串
    Json::Value root;
    root["name"] = zs.name;
    root["age"] = zs.age;
    root["weight"] = zs.weight;

    Json::FastWriter writer;
    std::string str = writer.write(root);

    std::cout << str;
    return 0;
}

  这里还有一个需要注意的点,当我们在Linux下进行编译的时候,直接编译会报如下错误:

在这里插入图片描述
  这是因为Jsoncpp库属于第三方库,要想使用Jsoncpp库就必须在编译时带上 -ljsoncpp 选项,表示链接到Jsoncpp库:

在这里插入图片描述

  上面的数据实际上就是结构化数据进行序列化之后的字符串,其原本应该是:"{"age":20,"name":"阿熊","weight":80}"

使用 Json::StyleWriter 进行Json格式序列化

  代码还是上述的代码,只是把Json::FastWriter类型替换为 Json::StyleWriter 类型:

#include <iostream>
#include <jsoncpp/json/json.h>

struct stu
{
    std::string name;
    int age;
    double weight;
};

int main()
{
    // 结构化数据
    struct stu zs = {"阿熊", 20, 80};

    // 转换为字符串
    Json::Value root;
    root["name"] = zs.name;
    root["age"] = zs.age;
    root["weight"] = zs.weight;

    // Json::FastWriter writer;
    Json::StyledWriter writer;
    std::string str = writer.write(root);

    std::cout << str;
    return 0;
}

在这里插入图片描述

  这两种序列化方式我们采用任何一种即可。


✈️Jsoncpp反序列化

  反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:

  首先,我们预先将Jsoncpp序列化后的字符串信息放在了一个txt文件当中,将来只需要从文件中读取信息并进行反序列化即可,向out.txt文件中读取信息:

#include <iostream>
#include <fstream>
#include <jsoncpp/json/json.h>

struct stu
{
    std::string name;
    int age;
    double weight;

public:
    void Debug()
    {
        std::cout << name << std::endl;
        std::cout << age << std::endl;
        std::cout << weight << std::endl;
    }
};

int main()
{
	// 读取字符串信息
    std::ifstream in("out.txt");
    if(!in.is_open()) return 1;

    char buffer[1024];
    in.read(buffer, sizeof(buffer));
    in.close();

	return 0;
}

  接下来就是进行反序列化的过程,我们使用 Json::Reader 对象调用 parse() 接口把序列化的字符串进行分割到 Json::Value 的对象当中,最后再将Stu对象的各个值拷贝。

#include <iostream>
#include <fstream>
#include <jsoncpp/json/json.h>

struct stu
{
    std::string name;
    int age;
    double weight;

public:
    void Debug()
    {
        std::cout << name << std::endl;
        std::cout << age << std::endl;
        std::cout << weight << std::endl;
    }
};

int main()
{
    std::ifstream in("out.txt");
    if(!in.is_open()) return 1;

    char buffer[1024];
    in.read(buffer, sizeof(buffer));
    in.close();

    std::string json_str = buffer;
    Json::Value root;
    Json::Reader reader;
    bool ret = reader.parse(json_str, root);
    (void)ret;

    struct stu zs;
    zs.name = root["name"].asString();
    zs.age = root["age"].asInt();
    zs.weight = root["weight"].asDouble();

    zs.Debug();
	
	return 0;
}

在这里插入图片描述


🚀自定义协议序列化反序列化

  经过上述的json序列化和反序列化的过程,我们可以将此应用到我们自定义协议 Request 和 Response类当中的序列化和反序列化:

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <jsoncpp/json/json.h>

namespace protocol_ns
{
    class Request
    {
    public:
        Request()
        {
        }
        Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
        {
        }
        bool Serialize(std::string *out)
        {
            // 转换成为字符串
            Json::Value root;
            root["x"] = _x;
            root["y"] = _y;
            root["oper"] = _oper;

            Json::FastWriter writer;
            // Json::StyledWriter writer;
            *out = writer.write(root);
            return true;
        }
        bool Deserialize(const std::string &in) // 你怎么知道你读到的in 就是完整的一个请求呢?
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (!res)
                return false;

            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
            return true;
        }

    public:
        int _x;
        int _y;
        char _oper; // "+-*/%" _x _oper _y
    }; // --- "字符串"

    class Response
    {
    public:
        Response()
        {
        }
        Response(int result, int code) : _result(result), _code(code)
        {
        }
        bool Serialize(std::string *out)
        {
            // 转换成为字符串
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;

            Json::FastWriter writer;
            // Json::StyledWriter writer;
            *out = writer.write(root);
            return true;
        }
        bool Deserialize(const std::string &in)
        {
            Json::Value root;
            Json::Reader reader;
            bool res = reader.parse(in, root);
            if (!res)
                return false;

            _result = root["result"].asInt();
            _code = root["code"].asInt();
            return true;
        }

    public:
        int _result; // 结果
        int _code;   // 0:success 1: 除0 2: 非法操作 3. 4. 5
    }; // --- "字符串"
}

  序列化之后的字符串还不够,我们还需要给字符串添加报头以及分隔符,组成一个网络报文发送给对端,所以在制定协议当中我们需要添加Encode()接口对有效载荷进行封装:

const std::string SEP = "\r\n"; // 分隔符

std::string Encode(const std::string &json_str)
{
    int json_str_len = json_str.size();
    std::string proto_str = std::to_string(json_str_len);
    proto_str += SEP;
    proto_str += json_str;
    proto_str += SEP;
    return proto_str;
}

  那么既然有对有效载荷的封装,就一定存在对网络报文的解包,所以Decode()接口也是必须的接口,用来对我们自定义网络报文进行解包,首先我们需要寻找分隔符,如果连报头都找不到就说明这批数据并不是自己的数据,直接返回一个空串。那么接着就一定会带有\r\n。

  除了完整的分隔符以外,我们还必须得收到报头部分,也就是有效载荷长度信息,如果没有找到报头部分,直接返回空串。这个时候往后执行就必定能拿到报头信息,接下来就是有效载荷部分,我们知道,有效载荷两边都有分隔符,如果想要Decode()接口确认一个完整的请求,应该至少有 初始分隔符长度 + 有效载荷的长度 + 两个分隔符的长度,这样才能保证,Decode的数据至少有一个完整报文:

td::string Decode(std::string &inbuffer)
{
    auto pos = inbuffer.find(SEP);
    if (pos == std::string::npos)// 未发现分隔符的位置,直接返回一个空串
        return std::string();
     
    std::string len_str = inbuffer.substr(0, pos);
    if (len_str.empty())
        return std::string();
    int packlen = std::stoi(len_str);

    int total = packlen + len_str.size() + 2 * SEP.size();// 一个完整报文长度
    if (inbuffer.size() < total)// 如果没有一个完整报文直接返回空串
        return std::string();

    std::string package = inbuffer.substr(pos + SEP.size(), packlen);// 有效载荷进行分离
    inbuffer.erase(0, total);// 报文已经处理完成,将处理完成后的报文删除
    return package;
}

  这样,一个简单的序列化和反序列化过程我们就已经完成了。


✈️Service 服务改写

  那么简单的协议框架我们就已经搭建完毕,接着将视角转回到服务器端,TcpServer 我们已经改写完毕,那么就需要再main函数中将Service 接口进行完善并编写启动服务器的demo。

#include <iostream>
#include <functional>
#include <memory>

#include "Log.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "CalCulate.hpp"

using namespace protocol_ns;

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_port\n"
              << std::endl;
}

void Service(socket_sptr sockptr, InetAddr client)
{
	LOG(DEBUG, "get a new link, info %s:%d, fd : %d", client.IP().c_str(), client.Port(), sockfd);
    std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";
    while(true)
    {
        char inbuffer[1024];
        ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::cout << clientaddr << inbuffer << std::endl;

            std::string echo_string = "[server echo]# ";
            echo_string += inbuffer;

            write(sockfd, echo_string.c_str(), echo_string.size());
        }
        else if(n == 0)
        {
            //client 退出 && 关闭链接
            LOG(INFO, "%s quit", clientaddr.c_str());
            break;
        }
        else
        {
            LOG(ERROR, "read error", clientaddr.c_str());
            break;
        }
    }
    ::close(sockfd);
}

// ./tcpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, Service);

    tsvr->Loop();
    return 0;
}

  我们将Service 接口中的close放在线程回调当中,具体的服务不用管是否需要关闭文件描述符,而在 HandlerSock 中,没有具体的sockfd,虽然 ThreadData类内有构造 Socket 的智能指针,但是我们并没有对应的Get函数将私有成员变量返回出来,所以在模版方法模式中我们应该添加一些常用的接口:

class Socket
{
    public:
        virtual void CreateSocketOrDie() = 0;             // 创建套接字
        virtual void BindSocketOrDie(InetAddr &addr) = 0; // 绑定套接字
        virtual void ListenSocketOrDie() = 0;             // 监听套接字
        virtual socket_sptr Accepter(InetAddr *addr) = 0;
        virtual bool Connector(InetAddr &addr) = 0;
        virtual int SockFd() = 0;// 返回sockfd
        virtual int Recv(std::string *out) = 0;// 接收消息
        virtual int Send(std::string &in) = 0; // 发送消息

	// to do...
};

  父类构建了抽象函数,那么子类 TcpSocket 必须对父类抽象函数进行重写:

int SockFd() override
{
    return _sockfd;
}

int Recv(std::string *out) override
{
    char inbuffer[1024];
    ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
    if (n > 0)
    {
        inbuffer[n] = 0;
        *out += inbuffer;// 接收文件采用的是 += 表示在out中追加数据
    }

    return n;
}

int Send(std::string &in) override
{
    int n = ::send(_sockfd, in.c_str(), in.size(), 0);
    return n;
}

  注意在Recv()接口中我们将读取的数据追加到out中,这是因为我们每次读取的并不一定是完整的序列化字符串,所以我们需要对每一次来的数据进行追加,尽量组成完整的请求。那么线程的回调函数就可以通过ThreadData 对象调用 TcpSocket 的 SockFd() 接口了:

static void* HandlerSock(void* args)
{
    pthread_detach(pthread_self());
    ThreadData* td = static_cast<ThreadData*>(args);
    td->self->_service(td->sockfd, td->clientaddr);
    ::close(td->sockfd->SockFd());// 不关闭会导致 文件描述符泄漏的问题(文件描述符不归还)
    delete td;
    return nullptr;
}

  这个时候Service 服务我们不再直接使用原生 recv接口了,所以我们要对Service 进行改写,我们需要思考一个问题,我们怎么能保证自己读取的是一个完整的客户端请求(在原本的Service接口中我们并没有做这样的处理,也没关心过这样的问题,所以改写是必不可少的),尽管在Recv()中我们是进行追加接收信息的,但是发送信息的是Tcp,不一定会一次性发送一次完整的报文,所以我们无法保证每一次都是完整的请求。那么我们检测到如果当前的报文不完整,我们进行循环等待新的数据,直到报文完整:

void ServiceHelper(socket_sptr sockptr, InetAddr client)
{
    int sockfd = sockptr->SockFd();
    LOG(DEBUG, "get a new link, info %s:%d, fd : %d", client.IP().c_str(), client.Port(), sockfd);
    std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";

    std::string inbuffer;
    while (true)
    {
        sleep(5);
        Request req;
        // 1. 读取数据
        int n = sockptr->Recv(&inbuffer);
        if (n < 0)
        {
            LOG(DEBUG, "client %s quit", clientaddr.c_str());
            break;
        }
        
        // 2. 分析数据
        std::string package = Decode(inbuffer);// 使用了Decode()接口,那么就一定能保证读取到一个完整的json串
        if(package.empty()) continue;
        req.Deserialize(package);// 反序列化有效载荷,就能够得到完整的信息了
}

  我们将Service封装为一个类,这样方便将来进行回调,而回调函数就是具体的对已经反序列化的结果进行算术运算,参数应当是收到Request,返回一个Response:

using callback_t = std::function<Response(const Request &req)>;// 我们发送一个Request返回一个Response

class Service
{
public:
    Service(callback_t cb)
        : _cb(cb)
    {
    }

    void ServiceHelper(socket_sptr sockptr, InetAddr client)
    {
        int sockfd = sockptr->SockFd();
        LOG(DEBUG, "get a new link, info %s:%d, fd : %d", client.IP().c_str(), client.Port(), sockfd);
        std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";

        std::string inbuffer;
        while (true)
        {
            sleep(5);
            Request req;
            // 1. 读取数据
            int n = sockptr->Recv(&inbuffer);
            if (n < 0)
            {
                LOG(DEBUG, "client %s quit", clientaddr.c_str());
                break;
            }
            
            // 2. 分析数据
	        std::string package = Decode(inbuffer);// 使用了Decode()接口,那么就一定能保证读取到一个完整的json串
	        if(package.empty()) continue;
	
			// 3. 反序列化
	        req.Deserialize(package);// 反序列化有效载荷,就能够得到完整的信息了
        }
    }

private:
    callback_t _cb;// 回调
};

  以上,属于读取与分析数据的部分,以及反序列化获取完整报文。获取了完整的报文之后,我们就可以拿着客户端发来的数据做业务处理,我们的业务是模拟简单计算器,我们设置的回调就是本次的业务代码,我们单独将业务代码封装为一个简单的类:

#pragma once

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

using namespace protocol_ns;

class Calculate
{
public:
    Calculate()
    {}

    Response Excute(const Request& req)// 参数为 Request类型,返回值为Response类型的服务
    {
        Response resp(0, 0);

        switch (req._oper)
        {
        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 = 1;
            }
            else
            {
                resp._result = req._x / req._y;
            }
            break;
        } 
        case '%':
        {
            if(req._y == 0)
            {
                resp._code = 2;
            }
            else
            {
                resp._result = req._x % req._y;
            }
            break;
        } 
        default:
            resp._code = 3;
            break;
        }
        return resp;
    }

    ~Calculate()
    {}
private:

};

  反序列化之后就需要处理客户端发来的请求,处理完请求我们就可以得到一个Response,也就是处理之后得到的结果,接着,服务器端就需要把这个结果返回给客户端,所以对Response进行序列化,并添加报头,最后再发送到对端,服务器端这次的工作就完成了:

while (true)
{
    sleep(5);
    Request req;
    // 1. 读取数据
    int n = sockptr->Recv(&inbuffer);
    if (n < 0)
    {
        LOG(DEBUG, "client %s quit", clientaddr.c_str());
        break;
    }
    
	// 2. 分析数据
	std::string package = Decode(inbuffer);// 使用了Decode()接口,那么就一定能保证读取到一个完整的json串
	if(package.empty()) continue;
	
	// 3. 反序列化
	req.Deserialize(package);// 反序列化有效载荷,就能够得到完整的信息了

	// 4. 业务处理
    Response resp = _cb(req);

    // 5. 对应答进行序列化
    std::string send_str;
    resp.Serialize(&send_str);
    std::cout << send_str << std::endl;

    // 6. 添加长度报头
    send_str = Encode(send_str);

    // 7. 发送到对端
    sockptr->Send(send_str);
}

✈️服务器端完结

  这样,我们将Service服务改写完成,而在main函数当中,我们需要运行我们的服务器,并且创建线程去处理相关的任务:

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);

    EnableScreen();
    Calculate cal;// 构造计算服务对象
    Service calservice(std::bind(&Calculate::Excute, &cal, std::placeholders::_1));// Calservice类内实现的Excute是我们用来对客户端请求处理的函数,但是属于类内函数,带有隐藏this指针,所以我们需要对其进行绑定,将this 指针绑定进来
    io_service_t service = std::bind(&Service::ServiceHelper, &calservice, std::placeholders::_1, std::placeholders::_2);// 同样,service也是封装为了一个类,线程想要对其进行回调,每次都得传Service类的构造当做第一个参数,为了避免这种麻烦,使用bind将this绑定
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, service);// 正常创建对象

    tsvr->Loop();// 进行循环
    return 0;
}

在这里插入图片描述
  这样,服务器端的工作我们就准备完毕。



http://www.kler.cn/a/419740.html

相关文章:

  • 泛化调用 :在没有接口的情况下进行RPC调用
  • 针对Qwen-Agent框架的Function Call及ReAct的源码阅读与解析:Agent基类篇
  • spring boot mapper测试类优化
  • HTTP 探秘之旅:从入门到未来
  • DevOps工程技术价值流:GitLab源码管理与提交流水线实践
  • Python实现网站资源批量下载【可转成exe程序运行】
  • 匹配 变量的类型
  • 前端API自动化构建工具:讲述 FlyHttp 设计思想
  • 微信小程序开发入门 笔记一 2024/11/29
  • 网页端五子棋对战(一)---websocket引入前后端交互的实现
  • LangGPT社区创始人云中江树:用热爱与坚持点燃实战营课堂
  • 物理机上的Navicat连接不上centos7虚拟机中mysql的解决办法
  • C++_详解多态
  • Base64.cv:高效安全的在线Base64转换工具详解
  • 高效集成:将聚水潭数据导入MySQL的实战案例
  • PostgreSQL17.x创建用户与授权命令
  • 具身智能高校实训解决方案——从AI大模型+机器人到通用具身智能
  • Oracle DataGuard 主备正常切换 (Switchover)
  • 《沉积与特提斯地质》
  • PD虚拟机启动Windows系统突然黑屏的解决方法
  • 小程序-基于java+SpringBoot+Vue的养老院管理系统设计与实现
  • 【datasheet】LTC4412 (2)
  • 阿里重磅开源 Fluss: Flink Unified Streaming Storage
  • 宠物领养网络:SpringBoot的实现之道
  • Android studio 签名加固后的apk文件
  • YOLOv1 (You Only Look Once)