【Linux网络编程】应用层自定义协议与序列化

【Linux网络编程】应用层自定义协议与序列化

1. 应用层

我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层.

1.1 再谈"协议"

协议是一种"约定".socketapi的接口,在读写数据时,都是按"字符串"的方式来发送接收的.如果我们要传输一些"结构化的数据"怎么办呢?

其实,协议就是双方约定好的结构化的数据

1.2 网络版计算器

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

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数,都是整形;
  • 两个数字之间会有一个字符是运算符,运算符只能是+;
  • 数字和运算符之间没有空格;
  • ...

约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做"序列化"和"反序列化"

1.3 序列化和反序列化

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

但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

  • 我们采用方案2,我们也要体现协议定制的细节
  • 我们要引入序列化和反序列化,只不过我们直接采用现成的方案--jsoncpp库
  • 我们要对socket进行字节流的读取处理

2. 重新理解read、write、recv、send和tcp为什么支持全双工

所以:

  • 在任何一台主机上,TCP连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个tcpsockfd读写都是它的原因
  • 实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议

3. 代码实现

Calculator.hpp

cpp 复制代码
#pragma once

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

class Calculator
{
public:
    Response Excute(Request &req)
    {
        Response resp;
        switch (req._oper)
        {
        case '+':
        resp._result = req._data_x + req._data_y;
            break;
        case '-':
        resp._result = req._data_x - req._data_y;
            break;
        case '*':
        resp._result = req._data_x * req._data_y;
            break;
        case '/':
        {
            if(req._data_y == 0)
                resp._code = 1; // div 0 error
            else
                resp._result = req._data_x / req._data_y;
        }
            break;
        case '%':
        {
            if(req._data_y == 0)
                resp._code = 2; // mod 0 error
            else
                resp._result = req._data_x % req._data_y;
        }
            break;
        default:
            resp._code = 3; // 非法操作
            break;
        }
        return resp;
    }
};

InetAddr.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define CONV(address) ((struct sockaddr *)(address))

// 对客户端进行先描述
class InetAddr
{
public:
    InetAddr()
    {
    }
    InetAddr(const struct sockaddr_in &address) : _address(address), _len(sizeof(address))
    {
        _port = ntohs(_address.sin_port);
        // _ip = inet_ntoa(_address.sin_addr);
        char ip[32];
        inet_ntop(AF_INET, &_address.sin_addr.s_addr, ip, sizeof(ip));
        _ip = ip;
    }
    InetAddr(uint16_t port, const std::string &ip = "0.0.0.0") : _port(port), _ip(ip)
    {
        bzero(&_address, sizeof(_address)); // 将_address清零
        _address.sin_family = AF_INET;      // 这里的AF_INET说明这个结构体子类的类型是网络通信的类型
        _address.sin_port = htons(_port);   // h->n 主机到网络的转化
        // _address.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 字符串IP转4字节IP 2. h->n
        inet_pton(AF_INET, ip.c_str(), &_address.sin_addr.s_addr);
        _len = sizeof(_address);
    }
    bool operator==(const InetAddr &addr)
    {
        return (this->_ip == addr._ip) && (this->_port == addr._port);
    }
    void operator=(struct sockaddr_in &addr)
    {
        _address = addr;
        _len = sizeof(addr);

        _port = ntohs(_address.sin_port);
        // _ip = inet_ntoa(_address.sin_addr);
        char ip[32];
        inet_ntop(AF_INET, &_address.sin_addr.s_addr, ip, sizeof(ip));
        _ip = ip;
    }
    std::string ToString()
    {
        return "[" + _ip + ":" + std::to_string(_port) + "]";
    }
    struct sockaddr *NetAddress()
    {
        return CONV(&_address);
    }
    socklen_t Len()
    {
        return _len;
    }
    ~InetAddr()
    {
    }

private:
    // net address
    struct sockaddr_in _address;
    socklen_t _len;
    // host address
    std::string _ip;
    uint16_t _port;
};

Logger.hpp

cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

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

namespace NS_LOG_MODULE
{
    enum class LogLevel
    {
        INFO,
        WARNING,
        ERROR,
        FATAL,
        DEBUG
    };
    LogLevel ExtractLevelFromMessage(const std::string &message)
    {
        // 寻找第二个 '[' 和对应的 ']' 之间的内容
        size_t first_bracket = message.find('[');
        if (first_bracket == std::string::npos)
            return LogLevel::INFO; // 默认
        size_t second_bracket = message.find('[', first_bracket + 1);
        if (second_bracket == std::string::npos)
            return LogLevel::INFO;
        size_t end_bracket = message.find(']', second_bracket);
        if (end_bracket == std::string::npos)
            return LogLevel::INFO;

        std::string level_str = message.substr(second_bracket + 1, end_bracket - second_bracket - 1);

        // 转换为 LogLevel
        if (level_str == "Info")
            return LogLevel::INFO;
        if (level_str == "Warning")
            return LogLevel::WARNING;
        if (level_str == "Error")
            return LogLevel::ERROR;
        if (level_str == "Fatal")
            return LogLevel::FATAL;
        if (level_str == "Debug")
            return LogLevel::DEBUG;
        return LogLevel::INFO; // 默认
    }

    // 根据等级获取对应的文件名
    std::string GetLevelFileName(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::INFO:
            return "log.info";
        case LogLevel::WARNING:
            return "log.warning";
        case LogLevel::ERROR:
            return "log.error";
        case LogLevel::FATAL:
            return "log.fatal";
        case LogLevel::DEBUG:
            return "log.debug";
        default:
            return "log.txt";
        }
    }
    std::string LogLevel2Message(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::INFO:
            return "Info";
        case LogLevel::WARNING:
            return "Warning";
        case LogLevel::ERROR:
            return "Error";
        case LogLevel::FATAL:
            return "Fatal";
        case LogLevel::DEBUG:
            return "Debug";
        default:
            return "Unknown";
        }
    }
    std::string GetCurrentTime()
    {
        struct timeval current_time;
        gettimeofday(&current_time, nullptr);

        struct tm struct_time;
        localtime_r(&(current_time.tv_sec), &struct_time); // r: 可重入函数
        char timestr[128];
        snprintf(timestr, sizeof(timestr), "%04d-%02d-%02d %02d:%02d:%02d.%ld",
                 struct_time.tm_year + 1900,
                 struct_time.tm_mon + 1,
                 struct_time.tm_mday,
                 struct_time.tm_hour,
                 struct_time.tm_min,
                 struct_time.tm_sec,
                 current_time.tv_usec);
        return timestr;
    }

    // 输出角度 -- 刷新策略
    // 1. 显示器打印
    // 2. 文件写入

    // 策略模式,策略接口
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };
    // 控制台日志刷新策略,日志将来要向显示器打印
    class ConsoleStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cerr << message << std::endl;
        }
        ~ConsoleStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log";
    const std::string defaultfilename = "log.txt";

    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &filename = defaultfilename)
            : _logpath(path), _logfilename(filename)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            if (!_logpath.empty() && _logpath.back() != '/')
            {
                _logpath += "/";
            }
            std::string targetlog = _logpath + _logfilename;
            std::ofstream out(targetlog, std::ios::app); // 追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }
        ~FileLogStrategy()
        {
        }

    private:
        std::string _logpath;
        std::string _logfilename;
        Mutex _mutex;
    };

    // const std::string defaultfilename = "log.info";
    // const std::string defaultfilename = "log.warning";
    // const std::string defaultfilename = "log.fatal";
    // const std::string defaultfilename = "log.error";
    // const std::string defaultfilename = "log.debug";
    // 文件策略&&分日志等级来进行保存
    class FileLogLevelStrategy : public LogStrategy
    {
    public:
        FileLogLevelStrategy(const std::string &path = defaultpath)
            : _logpath(path)
        {
            // 确保目录存在
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }

        void SyncLog(const std::string &message) override
        {
            // 解析等级
            LogLevel level = ExtractLevelFromMessage(message);
            std::string filename = GetLevelFileName(level);

            // 构建完整路径
            std::string fullpath = _logpath;
            if (!fullpath.empty() && fullpath.back() != '/')
                fullpath += "/";
            fullpath += filename;

            // 线程安全写入
            LockGuard lockguard(_mutex);
            std::ofstream out(fullpath, std::ios::app);
            if (out.is_open())
            {
                out << message << "\n";
                out.close();
            }
        }

        ~FileLogLevelStrategy() = default;

    private:
        std::string _logpath;
        Mutex _mutex;
    };

    // 日志类:
    // 1. 日志的生成
    // 2. 根据不同的策略,进行刷新
    class Logger
    {
    public:
        // 日志的生成
        // 构建日志字符串
        Logger() 
        {
            UseConsoleStrategy();
        }
        void UseConsoleStrategy()
        {
            _strategy = std::make_unique<ConsoleStrategy>();
        }
        void UseFileStrategy()
        {
            _strategy = std::make_unique<FileLogStrategy>();
        }
        void UseFileLevelStrategy()
        {
            _strategy = std::make_unique<FileLogLevelStrategy>();
        }
        // 内部类,标识一条完整的日志信息
        // 一条完整的日志信息 = 左半固定部分 + 右半不固定部分
        // LogMessage RAII风格的方式,进行刷新
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
                : _level(level), _curr_time(GetCurrentTime()), _pid(getppid()),
                  _filename(filename), _line(line), _logger(logger)
            {
                // 先构建出来左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LogLevel2Message(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this; // 返回当前LOgMessage对象,方便下次继续进行<<
            }
            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            LogLevel _level;
            std::string _curr_time;
            pid_t _pid;
            std::string _filename;
            int _line;
            std::string _loginfo; // 一条完整的日志信息

            // 一个引用,引用外部的Logger类对象
            Logger &_logger; // 方便后续进行策略式刷新
        };
        // 故意采用拷贝LogMessage
        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

        // void Debug(const std::string& message)
        // {
        //     if(_strategy != nullptr)
        //     {
        //         _strategy->SyncLog(message);
        //     }
        // }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _strategy; // 刷新策略
    };

    // 日志对象:全局使用
    Logger logger;

#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy()
#define ENABLE_File_LOG_STRATEGY() logger.UseFileStrategy()
#define ENABLE_FILE_LEVEL_LOG_STRATEGY() logger.UseFileLevelStrategy()

#define LOG(level) logger(level, __FILE__, __LINE__)
}

#endif

Makefile

cpp 复制代码
.PHONY:all
all: netcal_server netcal_client

netcal_client:NetCalClient.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp
netcal_server:NetCalServer.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp 
.PHONY:clean
clean:
	rm -f netcal_server netcal_client

Mutex.hpp

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

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    pthread_mutex_t* Ptr()
    {
        return &_lock;
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};

class LockGuard // RAII风格代码
{
public:
    LockGuard(Mutex& lock):_lockref(lock)
    {
        _lockref.Lock();
    }
    ~LockGuard()
    {
        _lockref.Unlock();
    }
private:
    Mutex& _lockref;
};

NetCalClient.cc

cpp 复制代码
#include "Protocol.hpp"
#include <iostream>
#include <memory>
#include "Socket.hpp"

using namespace NS_SOCKET_MODULE;

static void Usage(const std::string &proc)
{
    std::cout << "Usage:\n\t" << proc << " server_ip server_port\n";
}

static void HandlerResponse(Response &resp)
{
    std::cout << "result: " << resp._result << "[" << resp._code << "]" << std::endl;
}

// ./netcal_client 目标IP 目标Port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    std::unique_ptr<Socket> socket = std::make_unique<TcpSocket>();
    socket->BuildTcpClientSocketMethod();
    InetAddr serveraddress(server_port, server_ip);
    bool n = socket->Connect(serveraddress);
    if (!n)
    {
        std::cout << "connect error: " << serveraddress.ToString() << std::endl;
        exit(2);
    }

    Protocol protocol(HandlerResponse);
    std::string inbuffer;
    while (true)
    {
        int cnt = 3;
        std::string outbuffer;
        while (cnt--)
        {
            // 0. 获取数据
            int x, y;
            char oper;
            std::cout << "Enter Your x: ";
            std::cin >> x;
            std::cout << "Enter Your y: ";
            std::cin >> y;
            std::cout << "Enter Your oper: ";
            std::cin >> oper;

            // 1. 定义请求对象
            Request req(x, y, oper);

            // 2. 序列化
            std::string req_json;
            req.Serialize(&req_json);

            // 3. 封装包头
            outbuffer += protocol.Packet(req_json);
        }

        std::cout << "\n" << outbuffer << std::endl;

        // 4. 发送
        socket->Send(outbuffer);

        // 5. 接收
        socket->Recv(&inbuffer);

        // 6. 解析报文
        protocol.ParseResponse(inbuffer);
    }

    // Protocol protocol(HandlerResponse);
    // std::string inbuffer;
    // while (true)
    // {
    //     // 0. 获取数据
    //     int x, y;
    //     char oper;
    //     std::cout << "Enter Your x: ";
    //     std::cin >> x;
    //     std::cout << "Enter Your y: ";
    //     std::cin >> y;
    //     std::cout << "Enter Your oper: ";
    //     std::cin >> oper;

    //     // 1. 定义请求对象
    //     Request req(x, y, oper);

    //     // 2. 序列化
    //     std::string req_json;
    //     req.Serialize(&req_json);

    //     // 3. 封装包头
    //     std::string send_req_string = protocol.Packet(req_json);

    //     // 4. 发送
    //     socket->Send(send_req_string);

    //     // 5. 接收
    //     socket->Recv(&inbuffer);

    //     // 6. 解析报文
    //     protocol.ParseResponse(inbuffer);

    // }

    socket->Close();

    return 0;
}

NetCalServer.cc

cpp 复制代码
#include "TcpServer.hpp" // 基础网络层
#include "Protocol.hpp" // 协议层
#include "Calculator.hpp" // 业务层
#include <memory>

static void Usage(const std::string &proc)
{
    std::cout << "Usage:\n\t" << proc << " port\n";
}

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

    // 1. 定义计算器
    std::unique_ptr<Calculator> cal = std::make_unique<Calculator>();

    // 2. 定义协议对象
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
        [&cal](Request& req)->Response{
            return cal->Excute(req);
        }
    );

    // 3. 定义网络对象
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        [&protocol](std::string& inbuffer)->std::string{
            return protocol->ParseRequset(inbuffer);
        }, port
    );
    // 4. 启动
    tsvr->Loop();

    return 0;
}

Protocol.hpp

cpp 复制代码
#pragma once

// 自定义协议部分
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include <functional>
#include "Logger.hpp"

using namespace NS_LOG_MODULE;

// 请求报文
class Request
{
public:
    Request():_data_x(0), _data_y(0), _oper(0) {} 
    Request(int x, int y, char oper)
        : _data_x(x), _data_y(y), _oper(oper)
    {
    }
    bool Serialize(std::string *out) // 序列化
    {
        Json::Value root;
        root["left"] = _data_x;
        root["right"] = _data_y;
        root["oper"] = _oper;
        Json::FastWriter writer;
        *out = writer.write(root);
        return true;
    }
    bool Deserialize(std::string &in) // 反序列化
    {
        Json::Value root;
        Json::Reader reader;
        bool parsesuccess = reader.parse(in, root);
        if (!parsesuccess)
            return false;
        _data_x = root["left"].asInt();
        _data_y = root["right"].asInt();
        _oper = root["oper"].asInt();
        return true;
    }
    ~Request() {}

public:
    int _data_x;
    int _data_y;
    char _oper;
};

// 应答报文
class Response
{
public:
    Response(): _result(0), _code(0) {}
    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;
        *out = writer.write(root);
        return true;
    }
    bool Deserialize(std::string &in) // 反序列化
    {
        Json::Value root;
        Json::Reader reader;
        bool parsesuccess = reader.parse(in, root);
        if (!parsesuccess)
            return false;
        _result = root["result"].asInt();
        _code = root["code"].asInt();
        return true;
    }
    ~Response() {}

public:
    int _result; // 结果
    int _code;   // 状态码
};

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

using Handler_Request_t = std::function<Response(Request &)>;
using Handler_Response_t = std::function<void(Response &)>;

class Protocol
{
public:
    Protocol(Handler_Request_t handler_req) : _version("1.0"), _handler_request(handler_req)
    {
    }
    Protocol(Handler_Response_t handler_resp) : _version("1.0"), _handler_response(handler_resp)
    {
    }
    std::string Packet(const std::string &json_string) // 封包
    {
        return std::to_string(json_string.size()) + gsep + json_string + gsep;
    }
    // ret > 0, success
    // ret == 0, no error, packet不完整
    // ret < 0, error, json_string == nullptr
    int Unpack(std::string &packet, std::string *json_string)
    {
        if (json_string == nullptr)
            return -1;
        if (packet.empty())
            return 0;
        // 分析报文
        auto pos = packet.find(gsep);
        if (pos == std::string::npos)
            return 0;
        std::string lenstr = packet.substr(0, pos);
        int len = std::stoi(lenstr);
        int total = lenstr.size() + len + 2 * gsep.size();
        if (packet.size() < total)
            return 0;
        // 提取报文
        *json_string = packet.substr(pos + gsep.size(), len);
        packet.erase(0, total);
        return 1;
    }
    std::string ParseRequset(std::string &inbuffer)
    {
        std::string result;
        while (true)
        {
            // 1. 解包
            std::string json_string;
            int n = Unpack(inbuffer, &json_string);
            if (n < 0)
            {
                LOG(LogLevel::DEBUG) << "no way !!";
                return std::string();
            }
            if (n == 0)
            {
                LOG(LogLevel::INFO) << inbuffer << "parse done";
                return result;
            }

            LOG(LogLevel::DEBUG) << "json_string:\n" << json_string;
            LOG(LogLevel::DEBUG) << "unpack done, inbuffer:\n" << inbuffer;

            // 2. 请求反序列化
            // 得到一个完整的报文
            Request req;
            if (!req.Deserialize(json_string))
                return std::string();

            // 3. 业务计算
            Response resp;
            if (_handler_request)
                resp = _handler_request(req);

            // 4. 应答序列化
            std::string resp_json_string;
            resp.Serialize(&resp_json_string);

            // 5. 添加报头
            result += Packet(resp_json_string);
        }
    }
    void ParseResponse(std::string &inbuffer)
    {
        while (true)
        {
            // 1. 解包
            std::string json_string;
            int n = Unpack(inbuffer, &json_string);
            if (n < 0)
            {
                LOG(LogLevel::DEBUG) << "no way !!";
                return;
            }
            if (n == 0)
            {
                LOG(LogLevel::INFO) << inbuffer << " parse done";
                return;
            }
            // 2. 应答反序列化
            // 得到一个完整的报文
            Response resp;
            if (!resp.Deserialize(json_string))
                return;

            // 3. 回调处理
            if (_handler_response)
                _handler_response(resp);
        }
    }
    ~Protocol()
    {
    }

private:
    std::string _version;
    Handler_Request_t _handler_request;
    Handler_Response_t _handler_response;
};

Socket.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "InetAddr.hpp"
#include "Logger.hpp"

namespace NS_SOCKET_MODULE
{
    using namespace NS_LOG_MODULE;

    static const int gbacklog = 16;

    enum
    {
        OK = 0,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    // 模板方法模式
    class Socket
    {
    public:
        ~Socket() {}

    protected:
        virtual void CreateSocketOrDie() = 0;
        virtual void BindSocketOrDie(uint16_t port) = 0;
        virtual void ListenSocketOrDie() = 0;
    public:
        virtual std::shared_ptr<Socket> Accepter(InetAddr& clientaddr) = 0;
        virtual int Sockfd() = 0;
        virtual int Recv(std::string* out) = 0;
        virtual int Send(const std::string& in) = 0;
        virtual void Close() = 0;
        virtual bool Connect(InetAddr& addr) = 0;
    public:
        void BuildTcpSocketMethod(uint16_t port) // 模板方法
        {
            CreateSocketOrDie();
            BindSocketOrDie(port);
            ListenSocketOrDie();
        }
        void BuildTcpClientSocketMethod()
        {
            CreateSocketOrDie();
        }
        // void BuildUdpSocketMethod() // 模板方法
        // {
        //     CreateSocketOrDie();
        //     BindSocketOrDie();
        // }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(-1) {}
        TcpSocket(int sockfd) : _sockfd(sockfd) {}
        void CreateSocketOrDie() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "create socket error";
                exit(SOCKET_ERR);
            }
        }
        void BindSocketOrDie(uint16_t port) override
        {
            InetAddr addr(port);
            if(bind(_sockfd, addr.NetAddress(), addr.Len()) != 0)
            {
                LOG(LogLevel::FATAL) << "bind socket error";
                exit(BIND_ERR);                
            }
        }
        void ListenSocketOrDie() override
        {
            if(listen(_sockfd, gbacklog) != 0)
            {
                LOG(LogLevel::FATAL) << "listen socket error";
                exit(LISTEN_ERR);                           
            }
        }
        std::shared_ptr<Socket> Accepter(InetAddr& clientaddr) override
        {
            struct sockaddr_in addr;
            socklen_t len = sizeof(addr);
            int sockfd = accept(_sockfd, CONV(&addr), &len);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                return nullptr;                    
            }
            clientaddr = addr;
            return std::make_shared<TcpSocket>(sockfd);
        }
        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; // 追加写入的
            }
            return n;
        }
        int Send(const std::string& in) override
        {
            return send(_sockfd, in.c_str(), in.size(), 0);
        }
        void Close() override
        {
            if(_sockfd >= 0)
            {
                close(_sockfd);
                _sockfd = -1;
            }
        }
        bool Connect(InetAddr& addr) override
        {
            int n = connect(_sockfd, addr.NetAddress(), addr.Len());
            if(n < 0) return false;
            else return true;
        }
        ~TcpSocket() {}

    private:
        int _sockfd;
    };

    // class UdpSocket: public Socket
    // {

    // };
}

TcpServer.hpp

cpp 复制代码
#pragma once

#include "Logger.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include <memory>
#include <unistd.h>
#include <signal.h>
#include <functional>

using namespace NS_SOCKET_MODULE;
using namespace NS_LOG_MODULE;
using Handler_t = std::function<std::string(std::string&)>;

static uint16_t gport = 8080;

class TcpServer
{
public:
    TcpServer(Handler_t handler, uint16_t port = gport)
        : _port(port),
         _listensocket(std::make_unique<TcpSocket>()),
         _handler(handler)
    {
        _listensocket->BuildTcpSocketMethod(_port); // 使用创建listensockfd的模板方法
        LOG(LogLevel::INFO) << "create linstensocket success: " << _listensocket->Sockfd();
    }
    void Loop()
    {
        signal(SIGCHLD, SIG_IGN);
        while (true)
        {
            InetAddr clientaddr;
            auto sockfd = _listensocket->Accepter(clientaddr);
            if (!sockfd)
                continue;
            LOG(LogLevel::DEBUG) << "get a new link, socket address: " << clientaddr.ToString() << " sockfd: " << sockfd->Sockfd();
            if (fork() == 0)
            {
                // child
                service(sockfd, clientaddr);
                sockfd->Close();
                exit(0);
            }
        }
    }
    ~TcpServer() {}

private:
    void service(std::shared_ptr<Socket> sockfd, InetAddr &clientaddr)
    {
        std::string inbuffer, outbuffer;
        while (true)
        {
            outbuffer.clear();
            int n = sockfd->Recv(&inbuffer);
            if (n <= 0)
            {
                LOG(LogLevel::WARNING) << "recv: client quit, " << clientaddr.ToString();
                break;
            }

            LOG(LogLevel::DEBUG) << "inbuffer:\n" << inbuffer;

            if(_handler)
                outbuffer += _handler(inbuffer);
            
            if(outbuffer.empty())
                continue;

            LOG(LogLevel::DEBUG) << "outbuffer:\n" << outbuffer;

            n = sockfd->Send(outbuffer);
            if (n <= 0)
            {
                LOG(LogLevel::WARNING) << "send: client quit, " << clientaddr.ToString();
                break;
            }
        }
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensocket;
    Handler_t _handler;
};

附录

Jsoncpp

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

特性

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

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

安装

cpp 复制代码
ubuntu:sudoapt-getinstalllibjsoncpp-dev
Centos:sudoyuminstalljsoncpp-devel

序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件

中。Jsoncpp提供了多种方式进行序列化:

  1. 使用 Json::Value 的 toStyledString 方法:
  • 优点:将Json::Value对象直接转换为格式化的JSON字符串。

示例:

cpp 复制代码
#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::FastWriterwriter;
    Json::StyledWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;
    return 0;
}

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

反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。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 : 北京
  1. 使用 Json::CharReader 的派生类(不推荐了,上面的足够了):
  • 在某些情况下,你可能需要更精细地控制解析过程,可以直接使用 Json::CharReader 的派生类。
  • 但通常情况下,使用 Json::parseFromStream 或 Json::Reader 的parse方法就足够了。

总结

  • toStyledString、StreamWriter 和FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。
  • Json::Reader 和parseFromStream 函数是Jsoncpp中主要的反序列化工具,它们提供了强大的错误处理机制。
  • 在进行序列化和反序列化时,请确保处理所有可能的错误情况,并验证输入和输出的有效性。

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[](const std::string& key):同上,但使用std::string 类型的键。
  • Json::Value& at(const char* key):通过键访问对象中的元素,如果键不存在则抛出异常。
  • Json::Value& at(const std::string& key):同上,但使用 std::string类型的键。

3. 类型检查

• 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():检查值是否为对象(即键值对的集合)。

4. 赋值和类型转换

• 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():将值转换为字符串类型(如果可能)。

5. 数组和对象操作

• 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类型的

相关推荐
泓博1 小时前
docker ubuntu源码安装openclaw的常见问题
java·linux·开发语言·ai
小此方1 小时前
Re:Linux系统篇(十一)工具篇 · 三:三分钟学会gcc/g++编译工具&&初步认识动静态库&&重温编译基本原理
linux·运维·服务器·开发工具
小吴伴学者1 小时前
Linux TX报文处理流程解析
linux
深邃-1 小时前
【Web安全】-计算机网络协议(2):请求方法,头部字段,DNS协议详解
linux·网络·网络协议·计算机网络·安全·web安全·网络安全
Mr.H01271 小时前
C语言MQTT学习系列(3篇):第一篇:从零开始学MQTT(C语言版):入门必看,跑通最简Demo
c语言·网络·学习
无限进步_2 小时前
【Linux】gcc/g++ 编译:从源码到可执行文件的四个阶段
linux·运维·服务器
上海云盾-小余10 小时前
域名解析被劫持怎么办?DNS 安全防护与异常修复全教程
网络·安全·ddos
科技风向标go10 小时前
**2026年Q2中国消费级监控摄像头市场观察:存量时代的竞争逻辑重构**
网络·安全·监控·户外安防
原来是猿10 小时前
网络计算器:理解序列化与反序列化(中)
linux·运维·服务器·网络·tcp/ip