Linux_应用层自定义协议与序列化——网络计算器

✨✨ 欢迎大家来到小伞的大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:LInux**
小伞的主页:**************xiaosan_blog****************

gitee:许星让 (xu-xingrang) - Gitee.com****

制作不易!点个赞吧!!谢谢喵!!

0.本节目录gitee链接

完整代码:

https://gitee.com/xu-xingrang/linux-gcc/tree/master/lesson41/NetCal

1.序列化与反序列化

例如:网络计算器

我们需要实现一个服务器版的加法器,我们需要将客户端把要计算的两个加数发过去,然后服务器进行计算,最后在把结果返回给客户端;

我们存在两个方案

方案一:

  • 客户端发来"1+1"的字符串;

  • 这个字符串中有两个操作数,都是整形

  • 两个数字之间的字符是运算符,运算符只能是"+"

  • 数字和运算符之间没有空格
    方案二:

  • 定义结构体来表示我们需要交互的信息

  • 发送数据时这个结构体根据规则转换成字符串,接收的数据转换成结构体

  • 这个过程就叫做序列化与反序列化

无论是方案一,还是方案二,还是其他方案,只要保证,一端发送时构造的数据,在另一端能够正确的解析,就是正确的,这种约定就是应用层协议。

这里我们实现采用方案二:

方案二对发送的理解,有序,容易理解,且进行数据发送时,搭配更好编凑;

2.网络计算机的实现

代码结构

C++

|---------------|--------------|------------------|------------------|
| Calculate.hpp | Makefile | Socket.hpp | TcpServer.hpp |
| Daemon.hpp | Protocol.hpp | TcpClientMain.cc | TcpServerMain.cc |

2.1 Socket 封装

socket.hpp

cpp 复制代码
#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 backlog = 16;
    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 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;
        virtual void close() = 0;

    public:
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }

        void BuildTcpSocketMethod(uint16_t port, int _backlog = backlog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(_backlog);
        }
    };

    const static int defaultfd = -1;

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(defaultfd) {}
        TcpSocket(int sockfd) : _sockfd(sockfd)
        {
        }
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "sockfd error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "sockfd 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);
        }
        void close() override
        {
            if (_sockfd >= 0)
                ::close(_sockfd);
        }

        int Send(const std::string &message) override
        {
            return send(_sockfd, message.c_str(), sizeof(message), 0);
        }
        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; // Tcp接收发送缓冲区,可能存在未读完,或者多读取的问题
            }
            return n;
        }

        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

    public:
        int _sockfd;
    };
}

我们所规定的报文格式

2.2 关于字节流式的处理存在的问题

  • 我们如何保证每次读取就能读完请求缓冲区的所有内容?
  • 怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢
  • 处理TCP缓存区中的数据,一定要保证正确处理请求
cpp 复制代码
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
// "len\nx op y\n" : \n 不属于报文的一部分, 约定
std::string Encode(const std::string &message)
{
    std::string len = std::to_string(message.size());
    std::string package = len + LineBreakSep + message +
    LineBreakSep;
    return package;
} 

// "len\nx op y\n" : \n 不属于报文的一部分, 约定
// 我无法保证 package 就是一个独立的完整的报文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"

bool Decode(std::string &package, std::string *message)
{
    // 除了解包, 我还想判断报文的完整性, 能否正确处理具有"边界"的报文
    auto pos = package.find(LineBreakSep);
    if (pos == std::string::npos)
        return false;
    std::string lens = package.substr(0, pos);
    int messagelen = std::stoi(lens);
    int total = lens.size() + messagelen + 2 *
    LineBreakSep.size();
    if (package.size() < total)
        return false;
    // 至少 package 内部一定有一个完整的报文了!
    *message = package.substr(pos + LineBreakSep.size(),
    messagelen);
    package.erase(0, total);
    return true;
}

2.3 Jsoncpp

为了更方便进行字符串的格式化处理,我们借用C++库。

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

特性:

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

当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
安装:

ubuntu: sudo apt-get install libjsoncpp-dev

Centos: sudo yum install jsoncpp-devel

2.3.1 序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:

1.使用 Json::Value 的 toStyledString 方法:

优点:将 Json::Value 对象直接转换为格式化的 JSON字符串。

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    std::string s = root.toStyledString();
    std::cout << s << std::endl;
    return 0;
} 

$./test.exe
{
"name" : "joe",
"sex" : "男"
}
  1. 使用 Json::StreamWriter:

优点: 提供了更多的定制选项, 如缩进、 换行符等

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂
    std::unique_ptr<Json::StreamWriter>
    writer(wbuilder.newStreamWriter());
    std::stringstream ss;
    writer->write(root, &ss);
    std::cout << ss.str() << std::endl;
    return 0;
}
 
$./test.exe
{
"name" : "joe",
"sex" : "男"
}
  1. 使用 Json::FastWriter:

优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符。

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::FastWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;
    return 0;
} 

$./test.exe
{"name":"joe","sex":"男"}

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    // Json::FastWriter writer;
    Json::StyledWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;
    return 0;
}
 
$./test.exe
{
    "name" : "joe",
    "sex" : "男"
}
2.3.2 反序列化

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

  1. 使用 Json::Reader:

优点:提供详细的错误信息和位置,方便调试。

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main() {
    // JSON 字符串
    std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";
    // 解析 JSON 字符串
    Json::Reader reader;
    Json::Value root;
    // 从字符串中读取 JSON 数据
    bool parsingSuccessful = reader.parse(json_string,root);
    if (!parsingSuccessful) {
        // 解析失败, 输出错误信息
        std::cout << "Failed to parse JSON: " <<
        reader.getFormattedErrorMessages() << std::endl;
        return 1;
    } 
    // 访问 JSON 数据
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    // 输出结果
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;
    return 0;
} 

$./test.exe
Name: 张三
Age: 30
City: 北京
Json::Value

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

1.构造函数
  • Json::Value():默认构造函数,创建一个空的 Json::Value 对象。
  • Json::Value(ValueType type, bool allocated = false): 根据给定的ValueType (如 nullValue, intValue, StringValue 等) 创建一个 Json::Value 对象。
2.访问元素
  • 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类型的键。

3.代码实现:

NetCal.hpp(计算操作)

cpp 复制代码
#pragma once

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

class Cal
{
public:
    Response Execute(Request &req)
    {
        Response resp(0, 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;
    }
};

Common.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <memory>
#include <unistd.h>

enum ExitCode // 退出码
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR,
    OPEN_ERR
};

class Nocopy
{
public:
    Nocopy() {}
    // 服务器禁止拷贝
    Nocopy(const Nocopy &) = delete;            // 拷贝构造
    Nocopy &operator=(const Nocopy &) = delete; // 赋值
    ~Nocopy() {}
};

#define CONV(addr) ((struct sockaddr *)&addr)

InetAddr.hpp(网络与主机地址之间的相互转换)

cpp 复制代码
#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类

class InetAddr
{
public:
    InetAddr() {}
    // 网络转主机
    InetAddr(struct sockaddr_in &addr)
    {
        SetAddr(addr);
    }
    // 主机转网络
    InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
    }
    InetAddr(uint16_t port) : _port(port)
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_addr.s_addr = INADDR_ANY;
        _addr.sin_port = htons(_port);
    }
    void SetAddr(struct sockaddr_in &addr)
    {
        _addr = addr;
        // 网络转主机
        _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
        // _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }
    const struct sockaddr_in &NetAddr() { return _addr; }
    const struct sockaddr *NetAddrPtr()
    {
        return CONV(_addr);
    }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }

    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

Log.hpp(日志系统)

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;

    const std::string gsep = "\r\n";
    // 策略模式,C++多态特性
    // 2. 刷新策略 a: 显示器打印 b:向指定的文件写入
    //  刷新策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器打印日志的策略 : 子类
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }
        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 文件打印日志的策略 : 子类
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path),
              _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);

            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log"
            std::ofstream out(filename, std::ios::app);                              // 追加写入的 方式打开
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }
        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式

    // 1. 形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
            curr_tm.tm_year+1900,
            curr_tm.tm_mon+1,
            curr_tm.tm_mday,
            curr_tm.tm_hour,
            curr_tm.tm_min,
            curr_tm.tm_sec
        );
        return timebuffer;
    }

    // 1. 形成日志 && 2. 根据不同的策略,完成刷新
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),
                  _level(level),
                  _pid(getpid()),
                  _src_name(src_name),
                  _line_number(line_number),
                  _logger(logger)
            {
                // 日志的左边部分,合并起来
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            // LogMessage() << "hell world" << "XXXX" << 3.14 << 1234
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // a = b = c =d;
                // 日志的右半部分,可变的
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _curr_time;
            LogLevel _level;
            pid_t _pid;
            std::string _src_name;
            int _line_number;
            std::string _loginfo; // 合并之后,一条完整的信息
            Logger &_logger;
        };

        // 这里故意写成返回临时对象
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }
        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    // 全局日志对象
    Logger logger;

    // 使用宏,简化用户操作,获取文件名和行号
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

#endif

Mutex.hpp(互斥锁)

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
    private:
        Mutex &_mutex;
    };
}

Protocol.hpp(协议)

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

using namespace SocketModule;

// client -> server

class Request
{
public:
    Request() {}
    Request(int x, int y, int 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;

        Json::FastWriter write;
        std::string s = write.write(root);
        return s;
    }
    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;
    }

    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    int Oper()
    {
        return _oper;
    }

    ~Request() {}

private:
    int _x;
    int _y;
    int _oper; // 运算符
};
// 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;
    }

    void SetResult(int result)
    {
        _result = result;
    }
    void SetCode(int code)
    {
        _code = code;
    }
    void ShowResult()
    {
        std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
    }

    ~Response() {}

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

// 协议(基于TCP的)
// 读取的时候,读到完整的请求(TCP, UDP不用考虑)

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

const std::string sep = "\r\n"; // 报文分割符

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
    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);
        int target_len = package_len_str.size() + 2 * sep.size() + package_len_int;
        if (buffer.size() != target_len)
            return false;
        *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请求,如果不完整,就让服务器继续读取
                // bool ret = Decode(buffer_queue, &json_package);
                // if (!ret)
                // {
                //     continue;
                // }
                while (Decode(buffer_queue, &json_package))
                {
                    // 2.请求json串,反序列化
                    Request req;
                    bool ok = req.Deserialize(json_package);
                    if (!ok)
                        continue;
                    // 3.计算
                    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);
                }
            }
            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)
    {
        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))
                {
                    // 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:
    func_t _func;
};

Socket.hpp(通信Socket)

cpp 复制代码
#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 backlog = 16;
    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 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;
        virtual void close() = 0;

    public:
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }

        void BuildTcpSocketMethod(uint16_t port, int _backlog = backlog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(_backlog);
        }
    };

    const static int defaultfd = -1;

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(defaultfd) {}
        TcpSocket(int sockfd) : _sockfd(sockfd)
        {
        }
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "sockfd error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "sockfd 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);
        }
        void close() override
        {
            if (_sockfd >= 0)
                ::close(_sockfd);
        }

        int Send(const std::string &message) override
        {
            return send(_sockfd, message.c_str(), sizeof(message), 0);
        }
        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; // Tcp接收发送缓冲区,可能存在未读完,或者多读取的问题
            }
            return n;
        }

        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

    public:
        int _sockfd;
    };
}

TcpClient.cc(客户端)

cpp 复制代码
#include "Socket.hpp"
#include "Common.hpp"
#include <iostream>
#include <string>
#include <memory>
#include "Protocol.hpp"
using namespace SocketModule;

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}

void GetDataFromStdin(int *x, int *y, char *oper)
{
    std::cout << "Please Enter x: ";
    std::cin >> *x;
    std::cout << "Please Enter y: ";
    std::cin >> *y;
    std::cout << "Please Enter oper: ";
    std::cin >> *oper;
}

// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);
    std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();

    client->BuildTcpClientSocketMethod();
    if (client->Connect(server_ip, server_port) != 0)
    {
        // 失败
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
    std::string resp_buffer;
    while (true)
    {
        int x;
        int y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);

        std::string req_str = protocol->BuildRequestString(x, y, oper);

        client->Send(req_str);

        Response resp;
        bool res = protocol->GetResponse(client, resp_buffer, &resp);
        if (res == false)
            break;
        resp.ShowResult();
    }
    client->close();
    return 0;
}

Daemon.hpp(守护进程)

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include "Log.hpp"
#include <fcntl.h>
#include "Common.hpp"
#include <signal.h>
using namespace LogModule;

const std::string dev = "/dev/null";

void Daemon(int nochdir, int noclose)
{

    // 1. 忽略IO,子进程        退出等相关的信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    if (fork() > 0) // 父进程退出
        exit(0);

    // 子进程
    setsid(); // 创建独立的会话
    if (nochdir == 0)
        chdir("/");

    // 守护进程,不从键盘输入,也不需要向显示器打印
    //   方法1:关闭0,1,2 -- 不推荐
    //   方法2:打开/dev/null, 重定向标准输入,标准输出,标准错误到/dev/null
    /// dev/null 向文件写入会被丢弃,读取为null
    if (noclose == 0)
    {
        int fd = ::open(dev.c_str(), O_RDWR);
        if (fd < 0)
        {
            LOG(LogLevel::FATAL) << "open " << dev << " errno";
            exit(OPEN_ERR);
        }
        else
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}

TcpServer.cc(服务端)

cpp 复制代码
#pragma once

#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>

using namespace LogModule;
using namespace SocketModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket> &socket, InetAddr &client)>;

class TcpServer
{
public:
    TcpServer(uint16_t port, ioservice_t service) : _port(port),
                                                    _listensockptr(std::make_unique<TcpSocket>()),
                                                    _isrunning(false),
                                                    _service(service)
    {
        _listensockptr->BuildTcpSocketMethod(_port);
    }

    void start()
    {
        _isrunning = true;
        // while (1)
        // {
        //     sleep(10);
        //     LOG(LogLevel::DEBUG) << "accept success ...";
        // }
        while (_isrunning)
        {
            InetAddr client;
            auto sock = _listensockptr->Accept(&client);
            if (sock == nullptr)
            {                               
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success ...";
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error ...";
                exit(FORK_ERR);
            }
            else if (id == 0)
            {
                _listensockptr->close();
                if (fork() > 0)
                {
                    exit(OK);
                }
                _service(sock, client);
                exit(OK);
            }
            else
            {
                sock->close();
                pid_t rid = ::waitpid(rid, nullptr, 0);
                (void)rid;
            }
        }
        _isrunning = false;
    }
    ~TcpServer() {}

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensockptr;
    bool _isrunning;
    ioservice_t _service;
};

4.总结

以上是对于之前完成的代码的引用和增添。

相关推荐
袁袁袁袁满2 小时前
Linux怎么创建Shell脚本.sh文件
linux·运维·服务器·shell·shell脚本.sh文件·创建shell脚本·创建.sh文件
济6172 小时前
I.MX6U 开发板网络环境搭建---- NFS环境搭建-- Ubuntu20.04
linux
FIT2CLOUD飞致云2 小时前
支持OpenClaw智能体管理,新增企业微信、钉钉和飞书告警通知,1Panel v2.1.0版本发布
服务器·ai·开源·1panel
天空属于哈夫克32 小时前
企微API自动化:高效安全的群运营方案
linux·运维·服务器
dcmfxvr2 小时前
【无标题】
java·linux·前端
灰子学技术2 小时前
istio从0到1:如何解决同一个应用不同功能的路由聚合问题
运维·服务器·网络·云原生·istio
步步为营DotNet2 小时前
深入剖析.NET中Span:零拷贝内存操作的基石
服务器·php·.net
llz_1122 小时前
蓝桥杯备赛-搜索(DFS/BFS)
c++·算法·蓝桥杯·深度优先·宽度优先
敲上瘾2 小时前
磁盘到 inode:深入理解 Linux ext 文件系统底层原理
android·linux·运维·文件系统