网络计算器:理解序列化与反序列化(中)

网络计算器:理解序列化与反序列化(上)-CSDN博客


一、Socket.hpp:模板方法模式 + TCP 实现

1.1 文件存在的意义

Socket 操作有固定流程创建 → 绑定 → 监听 → 接受连接。 但 TCP 和 UDP 的具体实现不同。模板方法模式把"流程"固化在基类,把"变化"留给子类。

1.2 抽象基类 Socket

复制代码
class Socket {
    // 纯虚函数:子类必须实现
    virtual void SocketOrDie() = 0;      // 创建套接字
    virtual void BindOrDie(uint16_t port) = 0;  // 绑定端口
    virtual void ListenOrDie(int backlog) = 0;    // 开始监听
    virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;  // 接受连接
    virtual void Close() = 0;              // 关闭套接字
    virtual int Recv(std::string *out) = 0;     // 接收数据
    virtual int Send(const std::string &message) = 0;  // 发送数据
    virtual int Connect(const std::string &server_ip, uint16_t port) = 0;  // 连接服务器
    
    // 模板方法:固化流程
    void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog) {
        SocketOrDie();      // 第1步
        BindOrDie(port);    // 第2步
        ListenOrDie(backlog); // 第3步
    }
    
    void BuildTcpClientSocketMethod() {
        SocketOrDie();  // 客户端只需要创建
    }
};

模板方法的好处

  • 服务器端不可能"先 Listen 再 Bind",流程错误在编译期就避免

  • 新增 UDP 时,只需继承 Socket,实现 7 个纯虚函数,流程自动正确

1.3 TcpSocket 实现详解

复制代码
class TcpSocket : public Socket {
    int _sockfd;  // 核心资源:文件描述符
public:
    TcpSocket() : _sockfd(defaultfd) {}   // 默认构造(监听套接字)
    TcpSocket(int fd) : _sockfd(fd) {}    // 从已有 fd 构造(连接套接字)

为什么需要两个构造函数?

  • 服务器:TcpSocket() → 创建新的监听 fd

  • Accept() 后:TcpSocket(fd) → 包装系统返回的连接 fd

    void SocketOrDie() override {
    _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); // :: 表示系统调用
    if (_sockfd < 0) {
    LOG(LogLevel::FATAL) << "socket error";
    exit(SOCKET_ERR);
    }
    }

注意 ::socket:加 :: 明确调用全局命名空间的系统函数,避免与类成员函数混淆。

复制代码
std::shared_ptr<Socket> Accept(InetAddr *client) override {
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int fd = ::accept(_sockfd, CONV(peer), &len);
    if (fd < 0) {
        LOG(LogLevel::WARNING) << "accept warning ...";
        return nullptr;
    }
    client->SetAddr(peer);  // 填充客户端地址信息
    return std::make_shared<TcpSocket>(fd);  // 返回新的连接套接字
}
  • 多态:上层代码只认识 Socket 接口,不知道具体是 TCP 还是 UDP

  • 自动内存管理:不用手动 delete

    int Recv(std::string *out) override {
    char buffer[1024];
    ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
    if (n > 0) {
    buffer[n] = 0;
    *out += buffer; // 注意是 += 不是 = !
    }
    return n;
    }

+= 是核心设计!

  • TCP 是流式协议,一次 recv 可能读不完一个完整请求

  • buffer_queue += buffer 把新读到的数据追加到累积缓冲区

  • 后续 Protocol::Decode() 从累积缓冲区中提取完整报文

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

为什么 Send 不用 +=

  • 发送方知道自己要发什么,直接发完整报文即可

  • Encode() 已经把长度+JSON 拼好了,不需要累积

    #pragma once

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

    namespace SocketModule
    {
    using namespace LogModule;
    const static int gbacklog = 16;
    // 模版方法模式
    // 基类socket, 大部分方法,都是纯虚方法
    class Socket
    {
    public:
    virtual ~Socket() {}
    virtual void SocketOrDie() = 0;
    virtual void BindOrDie(uint16_t port) = 0;
    virtual void ListenOrDie(int backlog) = 0;
    virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
    virtual void Close() = 0;
    virtual int Recv(std::string *out) = 0;
    virtual int Send(const std::string &message) = 0;
    virtual int Connect(const std::string &server_ip, uint16_t port) = 0;

    复制代码
      public:
          void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
          {
              SocketOrDie();
              BindOrDie(port);
              ListenOrDie(backlog);
          }
          void BuildTcpClientSocketMethod()
          {
              SocketOrDie();
          }
          // void BuildUdpSocketMethod()
          // {
          //     SocketOrDie();
          //     BindOrDie();
          // }
      };
    
      const static int defaultfd = -1;
    
      class TcpSocket : public Socket
      {
      public:
          TcpSocket() : _sockfd(defaultfd)
          {
          }
          TcpSocket(int fd) : _sockfd(fd)
          {
          }
          ~TcpSocket() {}
          void SocketOrDie() override
          {
              _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
              if (_sockfd < 0)
              {
                  LOG(LogLevel::FATAL) << "socket error";
                  exit(SOCKET_ERR);
              }
              LOG(LogLevel::INFO) << "socket success";
          }
          void BindOrDie(uint16_t port) override
          {
              InetAddr localaddr(port);
              int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
              if (n < 0)
              {
                  LOG(LogLevel::FATAL) << "bind error";
                  exit(BIND_ERR);
              }
              LOG(LogLevel::INFO) << "bind success";
          }
          void ListenOrDie(int backlog) override
          {
              int n = ::listen(_sockfd, backlog);
              if (n < 0)
              {
                  LOG(LogLevel::FATAL) << "listen error";
                  exit(LISTEN_ERR);
              }
              LOG(LogLevel::INFO) << "listen success";
          }
          std::shared_ptr<Socket> Accept(InetAddr *client) override
          {
              struct sockaddr_in peer;
              socklen_t len = sizeof(peer);
              int fd = ::accept(_sockfd, CONV(peer), &len);
              if (fd < 0)
              {
                  LOG(LogLevel::WARNING) << "accept warning ...";
                  return nullptr; // TODO
              }
              client->SetAddr(peer);
              return std::make_shared<TcpSocket>(fd);
          }
          // n == read的返回值
          int Recv(std::string *out) override
          {
              // 流式读取,不关心读到的是什么
              char buffer[1024];
              ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
              if (n > 0)
              {
                  buffer[n] = 0;
                  *out += buffer; // 故意
              }
              return n;
          }
          int Send(const std::string &message) override
          {
              return send(_sockfd, message.c_str(), message.size(), 0);
          }
          void Close() override //??
          {
              if (_sockfd >= 0)
                  ::close(_sockfd);
          }
          int Connect(const std::string &server_ip, uint16_t port) override
          {
              InetAddr server(server_ip, port);
              return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
          }
    
      private:
          int _sockfd; // _sockfd , listensockfd, sockfd;
      };
    
      // class UdpSocket : public Socket
      // {
      // };

    }


二、Protocol.hpp:项目的灵魂

2.1 文件存在的意义

这个文件解决了网络编程的两大核心问题:

  1. 结构化数据 ↔ 字符串:序列化/反序列化

  2. 字节流 ↔ 消息:自定义协议解决粘包

2.2 Request 类:客户端 → 服务器

复制代码
class Request {
    int _x;
    int _y;
    char _oper;
public:
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
    
    std::string Serialize() {
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;  // char 自动转为 ASCII 码整数
        
        Json::FastWriter writer;
        return writer.write(root);  // 返回如 {"x":10,"y":20,"oper":43}
    }
    
    bool Deserialize(std::string &in) {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok) {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();  // 转回 char
        }
        return ok;
    }
};

为什么 _oper 存的是 ASCII 码?

  • JSON 没有 char 类型,'+' 的 ASCII 是 43,序列化后变成 {"oper":43}

  • 反序列化时用 asInt() 取出 43,使用时直接当 char 用(C++ 会自动转换)

2.3 Response 类:服务器 → 客户端

复制代码
class Response {
    int _result;
    int _code;  // 0=成功, 1=除零, 2=模零, 3=非法操作
public:
    Response(int result, int code) : _result(result), _code(code) {}
    
    std::string Serialize() {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::FastWriter writer;
        return writer.write(root);
    }
    
    bool Deserialize(std::string &in) { ... }
    void ShowResult() {
        std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }
};

_code 的设计是点睛之笔!

  • 如果没有 coderesult=0 时客户端不知道是真的结果为 0,还是出错了

  • code业务错误计算结果彻底分离

2.4 Encode / Decode:自定义协议核心

复制代码
const std::string sep = "\r\n";

std::string Encode(const std::string &jsonstr) {
    std::string len = std::to_string(jsonstr.size());
    return len + sep + jsonstr + sep;
}

协议格式可视化

为什么用 \r\n 而不用 \n?

复制代码
bool Decode(std::string &buffer, std::string *package) {
    // 步骤1:找第一个 \r\n,确定"长度字段"的边界
    ssize_t pos = buffer.find(sep);
    if (pos == std::string::npos) return false;  // 数据不够,继续读
    
    // 步骤2:提取长度值
    std::string package_len_str = buffer.substr(0, pos);
    int package_len_int = std::stoi(package_len_str);
    
    // 步骤3:计算"一个完整报文"需要的总长度
    // 总长度 = 长度字段自身长度 + \r\n(2字节) + JSON内容长度 + \r\n(2字节)
    int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
    
    // 步骤4:判断 buffer 中是否有足够数据
    if (buffer.size() < target_len) return false;  // 数据不够,继续读
    
    // 步骤5:提取完整 JSON 内容
    *package = buffer.substr(pos + sep.size(), package_len_int);
    
    // 步骤6:从 buffer 中移除已处理的报文(关键!)
    buffer.erase(0, target_len);
    
    return true;
}

buffer 为什么用引用 &?

  • Recv() 是流式读取,可能一次读到多个报文,也可能只读到半个报文

  • buffer 作为累积缓冲区,保留未处理完的残留数据

  • 下次 Recv() 时,新数据追加到 buffer 尾部,继续尝试 Decode

循环解析的妙处

复制代码
while (Decode(buffer_queue, &json_package)) {
    // 处理第一个完整请求
    // 处理第二个完整请求(如果粘包了)
    // ...
}

2.5 GetRequest:服务器的"主循环"

复制代码
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client) {
    std::string buffer_queue;  // 累积缓冲区
    
    while (true) {
        int n = sock->Recv(&buffer_queue);  // 追加读取
        if (n > 0) {
            std::string json_package;
            
            // 循环提取所有完整报文
            while (Decode(buffer_queue, &json_package)) {
                LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
                
                // 1. 反序列化
                Request req;
                bool ok = req.Deserialize(json_package);
                if (!ok) continue;
                
                // 2. 业务处理(通过回调函数 _func)
                Response resp = _func(req);
                
                // 3. 序列化响应
                std::string json_str = resp.Serialize();
                
                // 4. 编码(加长度报头)
                std::string send_str = Encode(json_str);
                
                // 5. 发送
                sock->Send(send_str);
            }
        }
        else if (n == 0) {
            LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
            break;  // 客户端断开
        }
        else {
            LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
            break;  // 出错
        }
    }
}

2.6 GetResponse:客户端的接收

复制代码
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp) {
    while (true) {
        int n = client->Recv(&resp_buff);
        if (n > 0) {
            std::string json_package;
            while (Decode(resp_buff, &json_package)) {
                resp->Deserialize(json_package);  // 填充 resp 对象
            }
            return true;
        }
        else if (n == 0) {
            std::cout << "server quit " << std::endl;
            return false;
        }
        else {
            std::cout << "recv error" << std::endl;
            return false;
        }
    }
}

与 GetRequest 的区别

  • GetRequest服务器端需要循环处理多个请求(长连接)

  • GetResponse客户端一次请求对应一次响应,返回 bool 表示成功/失败

2.7 BuildRequestString:客户端的"打包工厂"

复制代码
std::string BuildRequestString(int x, int y, char oper) {
    Request req(x, y, oper);           // 1. 构造请求对象
    std::string json_req = req.Serialize();  // 2. 序列化为 JSON
    return Encode(json_req);           // 3. 加长度报头,返回可直接发送的字符串
}

存在价值:把"构造→序列化→编码"三步封装成一步,客户端代码更简洁。

复制代码
#pragma once
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>

// 实现一个自定义的网络版本的计算器

using namespace SocketModule;

// 约定好各个字段的含义,本质就是约定好协议!
// client -> server
// 如何要做序列化和反序列化:
// 1. 我们自己写(怎么做) ---> 往往不具备很好的扩展性
// 2. 使用现成的方案(这个是我们要写的) ---> json -> jsoncpp

// content_len jsonstring
// 50\r\n协议号\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x": 10, "y" : 20, "oper" : '+'}
class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
    {
    }
    std::string Serialize()
    {
        // _x = 10 _y = 20, _oper = '+'
        // "10" "20" '+' : 用空格作为分隔符
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper; // _oper是char,也是整数,阿斯克码值

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }

    // {"x": 10, "y" : 20, "oper" : '+'}
    bool Deserialize(std::string &in)
    {
        // "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt(); //?
        }
        return ok;
    }
    ~Request() {}
    int X() { return _x; }
    int Y() { return _y; }
    char Oper() { return _oper; }

private:
    int _x;
    int _y;
    char _oper; // + - * / % -> _x _oper _y -> 10 + 20
};

// server -> client
class Response
{
public:
    Response() {}
    Response(int result, int code) : _result(result), _code(code)
    {
    }
    std::string Serialize()
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::FastWriter writer;
        return writer.write(root);
    }
    bool Deserialize(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
        }
        return ok;
    }
    ~Response() {}
    void SetResult(int res)
    {
        _result = res;
    }
    void SetCode(int code)
    {
        _code = code;
    }
    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }

private:
    int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值
    int _code;   // 0:sucess, 1,2,3,4->不同的运算异常的情况, 约定!!!!
};

const std::string sep = "\r\n";

using func_t = std::function<Response(Request &req)>;

// 协议(基于TCP的)需要解决两个问题:
// 1. request和response必须得有序列化和反序列化功能
// 2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func) : _func(func)
    {
    }
    std::string Encode(const std::string &jsonstr)
    {
        // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep; // 应用层封装报头
    }

    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
    // 5
    // 50
    // 50\r
    // 50\r\n
    // 50\r\n{"x": 10, "
    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
    // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope
    //.....
    // packge故意是&
    // 1. 判断报文完整性
    // 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
    bool Decode(std::string &buffer, std::string *package)
    {
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos)
            return false; // 让调用方继续从内核中读取数据
        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        // buffer 一定有长度,但是一定有完整的报文吗?
        int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
        if (buffer.size() < target_len)
            return false;

        // buffer一定至少有一个完整的报文!
        *package = buffer.substr(pos + sep.size(), package_len_int);
        buffer.erase(0, target_len);
        return true;
    }
    void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 读取
        std::string buffer_queue;
        while (true)
        {
            int n = sock->Recv(&buffer_queue);
            if (n > 0)
            {
                std::cout << "-----------request_buffer--------------" << std::endl;
                std::cout << buffer_queue << std::endl;
                std::cout << "------------------------------------" << std::endl;

                std::string json_package;
                // 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
                while (Decode(buffer_queue, &json_package))
                {
                    // 我敢100%保证,我一定拿到了一个完整的报文
                    // {"x": 10, "y" : 20, "oper" : '+'} -> 你能处理吗?
                    // 2. 请求json串,反序列化
                   // std::cout << "-----------request_json--------------" << std::endl;
                   // std::cout << json_package << std::endl;
                   // std::cout << "------------------------------------" << std::endl;

                   // std::cout << "-----------request_buffer--------------" << std::endl;
                   // std::cout << buffer_queue << std::endl;
                   // std::cout << "------------------------------------" << std::endl;

                    LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
                    Request req;
                    bool ok = req.Deserialize(json_package);
                    if (!ok)
                        continue;
                    // 3. 我一定得到了一个内部属性已经被设置了的req了.
                    // 通过req->resp, 不就是要完成计算功能嘛!!业务
                    Response resp = _func(req);

                    // 4. 序列化
                    std::string json_str = resp.Serialize();

                    // 5. 添加自定义长度
                    std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\r\n{result:XXX, code:XX}\r\n"

                    // 6. 直接发送
                    sock->Send(send_str);
                }
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
                break;
            }
        }
    }
    bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
    {
        // 面向字节流,你怎么保证,你的client读到的 一个网络字符串,就一定是一个完整的请求呢??
        while (true)
        {
            int n = client->Recv(&resp_buff);
            if (n > 0)
            {
                // std::cout << "-----------resp_buffer--------------" << std::endl;
                // std::cout << resp_buff << std::endl;
                // std::cout << "------------------------------------" << std::endl;

                // 成功
                std::string json_package;
                // 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
                // bool ret = Decode(resp_buff, &json_package);
                // if (!ret)
                //     continue;

                while (Decode(resp_buff, &json_package))
                {
                    // std::cout << "----------response json---------------" << std::endl;
                    // std::cout << json_package << std::endl;
                    // std::cout << "--------------------------------------" << std::endl;

                    // std::cout << "-----------resp_buffer--------------" << std::endl;
                    // std::cout << resp_buff << std::endl;
                    // std::cout << "------------------------------------" << std::endl;
                    // 2. 走到这里,我能保证,我一定拿到了一个完整的应答json报文
                    // 2. 反序列化
                    resp->Deserialize(json_package);
                }
                return true;
            }
            else if (n == 0)
            {
                std::cout << "server quit " << std::endl;
                return false;
            }
            else
            {
                std::cout << "recv error" << std::endl;
                return false;
            }
        }
    }
    std::string BuildRequestString(int x, int y, char oper)
    {
        // 1. 构建一个完整的请求
        Request req(x, y, oper);

        // 2. 序列化
        std::string json_req = req.Serialize();

        // // 2.1 debug
        // std::cout << "------------json_req string------------" << std::endl;
        // std::cout << json_req << std::endl;
        // std::cout << "---------------------------------------" << std::endl;

        // 3. 添加长度报头
        return Encode(json_req);
    }
    ~Protocol()
    {
    }

private:
    // 因为我们用的是多进程
    // Request _req;
    // Response _resp;
    func_t _func;
};

三、Cal.hpp:纯粹的业务层

3.1 文件存在的意义

这是唯一不需要关心网络的文件!只负责:

  • 拿到 Request(已经反序列化好的结构体)

  • 计算结果

  • 返回 Response

3.2 Execute 函数详解

复制代码
Response Execute(Request &req) {
    Response resp(0, 0);  // result=0, code=0(成功)
    
    switch (req.Oper()) {
        case '+': resp.SetResult(req.X() + req.Y()); break;
        case '-': resp.SetResult(req.X() - req.Y()); break;
        case '*': resp.SetResult(req.X() * req.Y()); break;
        case '/': 
            if (req.Y() == 0) resp.SetCode(1);  // 除零错误
            else resp.SetResult(req.X() / req.Y());
            break;
        case '%':
            if (req.Y() == 0) resp.SetCode(2);  // 模零错误
            else resp.SetResult(req.X() % req.Y());
            break;
        default: resp.SetCode(3);  // 非法操作符
    }
    return resp;
}

设计亮点

  • 零耦合:不知道 JSON、不知道 Socket、不知道 TCP

  • 错误码体系code=1/2/3 让客户端能给出精确错误提示

  • 异常安全:除零不会崩溃,而是返回错误码

    #pragma once

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

    class Cal
    {
    public:
    Response Execute(Request &req)
    {
    Response resp(0, 0); // code: 0表示成功
    switch (req.Oper())
    {
    case '+':
    resp.SetResult(req.X() + req.Y());
    break;
    case '-':
    resp.SetResult(req.X() - req.Y());

    复制代码
              break;
          case '*':
              resp.SetResult(req.X() * req.Y());
              break;
          case '/':
          {
              if (req.Y() == 0)
              {
                  resp.SetCode(1); // 1除零错误
              }
              else
              {
                  resp.SetResult(req.X() / req.Y());
              }
          }
          break;
          case '%':
          {
              if (req.Y() == 0)
              {
                  resp.SetCode(2); // 2 mod 0 错误
              }
              else
              {
                  resp.SetResult(req.X() % req.Y());
              }
          }
          break;
          default:
              resp.SetCode(3); // 非法操作
              break;
          }
    
          return resp;
      }

    };

相关推荐
前端老曹1 小时前
Docker 从入门到放弃:完整指南
运维·docker·容器
AOwhisky2 小时前
虚拟化技术学习笔记
linux·运维·笔记·学习·虚拟化技术
汪汪大队u2 小时前
续:从 Docker Compose 到 Kubernetes(2)—— 服务优化与排错
网络·后端·物联网·struts·容器
rabbit_pro3 小时前
Docker compose部署Ollama使用模型
linux·运维·docker
笑洋仟4 小时前
docker的overlay2目录占用磁盘空间很大,清理办法
运维·docker·容器
m0_738120725 小时前
ctfshow靶场SSRF部分——基础绕过到协议攻击解题思路与技巧(一)
服务器·前端·网络·安全·php
木雷坞5 小时前
2026 年 5 月国内可用 Docker 镜像源列表与配置方法
运维·docker·容器
Irissgwe5 小时前
六、Ext系列文件系统(2.核心原理与应用)
linux·分区··inode·软硬连接·路径缓存·ext系列文件