前面我们已经学习过利用UDP和TCP进行网络编程。本期我们就深入底层,学习网络的序列化相关的内容,并编写相关的代码。

        本期相关代码已经上传至作者的个人gitee中楼田莉子/Linux学习,喜欢请点个赞谢谢。

目录

应用层

用户层自定义协议

序列化

        为什么需要这种转换?

        为什么网路编程必须序列化?

推荐的序列化工具

日志

计算器

客户端

原型

        自定义套接字

        自定义TCP服务器

多进程改版

        自定义协议

        服务器运行

        客户端运行

测试


应用层

        应用层就是我们日常进行的各种行为的we并不是指某个具体的应用程序(如浏览器、游戏客户端),而是指为应用程序提供网络通信服务的协议和接口集合。它负责定义应用程序如何利用底层网络交换有意义的数据。

        应用层的核心使命是:将传输层提供的原始字节流(或数据报)转化为应用程序能够理解的业务语义。

        为什么read、write、recv、send等TCP socket支持全双工?

        1、因为通信双方都有接受和发送的缓冲区

        2、用户拷贝数据到内核的发送缓冲区,用户拷贝数据到内核对应的发送缓冲区,用户拷贝接受缓冲区中的数据到用户空间,本质可以看做,CP问题。

        但是这样就会导致几个问题:我们发出去的数据对方有接收吗?答案是不确定的。那么我们读到的数据一定是对方发来的吗?也有可能只是一部分信息。这就是“粘包”问题

        那么我们该怎么做呢?需要自定义协议

用户层自定义协议

        我们以一个例子来描述这个事情——网络计算器

        我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。  
        约定方案一
        客户端发送一个形如"1+2"的字符串; 
        这个字符串中有两个操作数, 都是整型; 
        两个数字之间会有一个字符是运算符, 运算符只能是 + ; 
        数字和运算符之间没有空格; 
        ......
        约定方案二: 
        定义结构体来表示我们需要交互的信息; 
        发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 
        这个过程叫做 "序列化" 和 "反序列化"

        我们先说方案二。这个可行但是不推荐的,主要原因有以下几点:

        1、客户端和服务器端可能在不同系统平台上(事实上通常如此)。不同系统对于内存对齐、大小端问题的处理不同。就是兼容性问题

        2、可拓展性很差。不同的客户端语言可能不同,如果用结构体语言兼容性低下

        应用层协议不推荐传递结构体。我们就需要一个新的东西用户自定义协议

        那么为什么内核用结构体呢?它们统一为C语言编写的

序列化

        序列化(Serialization) 是指将内存中的数据结构或对象状态,转换为一种可存储或可传输的格式(通常为字节序列)的过程。其逆过程称为反序列化(Deserialization),即从字节流中重建原始数据结构或对象。

        为什么需要这种转换?

        程序运行时,数据以复杂的形态存在于内存中:

  • 指针/引用指向堆中的其他区域;

  • 虚函数表指针(vptr)指向类型信息;

  • 不同平台对基础类型(如 int、long)的字节长度、字节序(大端/小端)可能有差异;

  • 编译器可能会在结构体成员之间插入内存对齐填充字节。

        为什么网路编程必须序列化?

        网络本质上是一个字节流传输管道,它只关心数据的顺序和完整性,而不关心数据的语义。要在这样的通道上交换结构化数据,就必须解决以下问题:

        1、不同OS、不同语言、不同编译器下对内存的理解差异

        2、处理复杂的对象数据关系

        3、序列化不仅用于网络,也用于将对象保存到文件或数据库。这与网络传输本质相同:都是将内存对象转换为可脱离进程存在的字节流。

推荐的序列化工具

        序列化通常是由现有工具完成的,不是很推荐自己写。常见的序列化工具主要有json、protobuf、xml。

        为了方便理解,我们后续使用Jsoncpp这个库来实现

        接下来我们来测试代码

#include <jsoncpp/json/json.h>
#include <iostream>
#include <string>
#include <memory>
#include <sstream>

// 1. 基本类型操作:创建、赋值、类型判断、取值
void TestValueBasics() 
{
    std::cout << "=== TestValueBasics ===" << std::endl;
    Json::Value v;
    
    // 赋值不同类型
    v["bool"] = true;
    v["int"] = 42;
    v["double"] = 3.14159;
    v["string"] = "hello";
    v["null"] = Json::nullValue;
    
    // 类型判断
    std::cout << "bool isBool: " << v["bool"].isBool() << std::endl;
    std::cout << "int isInt: " << v["int"].isInt() << std::endl;
    std::cout << "double isDouble: " << v["double"].isDouble() << std::endl;
    std::cout << "string isString: " << v["string"].isString() << std::endl;
    std::cout << "null isNull: " << v["null"].isNull() << std::endl;
    
    // 取值
    std::cout << "bool value: " << v["bool"].asBool() << std::endl;
    std::cout << "int value: " << v["int"].asInt() << std::endl;
    std::cout << "double value: " << v["double"].asDouble() << std::endl;
    std::cout << "string value: " << v["string"].asString() << std::endl;
    std::cout << std::endl;
}

// 2. 数组操作
void TestValueArray() 
{
    std::cout << "=== TestValueArray ===" << std::endl;
    Json::Value arr(Json::arrayValue);  // 显式声明为数组
    
    // 追加元素
    arr.append(10);
    arr.append(20);
    arr.append(30);
    
    // 索引访问
    std::cout << "arr[0] = " << arr[0].asInt() << std::endl;
    std::cout << "arr[1] = " << arr[1].asInt() << std::endl;
    
    // 遍历
    std::cout << "Array elements: ";
    for (const auto& elem : arr) {
        std::cout << elem.asInt() << " ";
    }
    std::cout << std::endl;
    
    // 大小和删除
    std::cout << "Size before remove: " << arr.size() << std::endl;
    Json::Value removed;
    arr.removeIndex(1, &removed);  // 删除索引1的元素,存储到removed
    std::cout << "Removed element: " << removed.asInt() << std::endl;
    std::cout << "Size after remove: " << arr.size() << std::endl;
    std::cout << std::endl;
}

// 3. 对象操作
void TestValueObject() 
{
    std::cout << "=== TestValueObject ===" << std::endl;
    Json::Value obj(Json::objectValue);
    
    // 添加键值对
    obj["name"] = "加法器";
    obj["version"] = 1;
    obj["support_ssl"] = false;
    
    // 判断键是否存在
    std::cout << "Has 'name'? " << obj.isMember("name") << std::endl;
    std::cout << "Has 'port'? " << obj.isMember("port") << std::endl;
    
    // 获取所有键名
    auto members = obj.getMemberNames();
    std::cout << "Keys: ";
    for (const auto& key : members) {
        std::cout << key << " ";
    }
    std::cout << std::endl;
    
    // 移除键
    obj.removeMember("version");
    std::cout << "After remove, has 'version'? " << obj.isMember("version") << std::endl;
    std::cout << std::endl;
}

// 4. 安全的取值方式:使用get()避免隐式创建
void TestValueGetOrDefault() 
{
    std::cout << "=== TestValueGetOrDefault ===" << std::endl;
    Json::Value obj;
    obj["x"] = 100;
    
    // 安全获取已存在的键
    int x = obj.get("x", 0).asInt();
    std::cout << "x = " << x << std::endl;
    
    // 获取不存在的键,返回默认值,且不会在obj中创建该键
    int y = obj.get("y", -1).asInt();
    std::cout << "y (default) = " << y << std::endl;
    std::cout << "obj has 'y'? " << obj.isMember("y") << std::endl;  // 仍然为false
    
    // 注意:直接使用obj["y"]会创建null值
    auto& ref = obj["z"];  // 这会创建一个键"z",值为null
    std::cout << "After obj[\"z\"], has 'z'? " << obj.isMember("z") << std::endl;
    std::cout << std::endl;
}

// 5. 序列化:Json::Value -> 字符串
void TestSerialization() 
{
    std::cout << "=== TestSerialization ===" << std::endl;
    Json::Value root;
    root["a"] = 1;
    root["b"] = 2.5;
    root["c"] = "text";
    
    // 使用StreamWriterBuilder生成紧凑格式(无缩进)
    Json::StreamWriterBuilder builder;
    builder.settings_["indentation"] = "";  // 紧凑
    std::string compact = Json::writeString(builder, root);
    std::cout << "Compact: " << compact << std::endl;
    
    // 生成美观格式(带缩进)
    builder.settings_["indentation"] = "  ";  // 两个空格缩进
    std::string styled = Json::writeString(builder, root);
    std::cout << "Styled:\n" << styled << std::endl;
    
    // 使用传统方法(toStyledString)
    std::cout << "toStyledString:\n" << root.toStyledString() << std::endl;
    std::cout << std::endl;
}

// 6. 反序列化:字符串 -> Json::Value
void TestDeserialization() 
{
    std::cout << "=== TestDeserialization ===" << std::endl;
    std::string jsonStr = R"({"x":10,"y":20,"op":"+"})";
    
    Json::CharReaderBuilder builder;
    Json::Value root;
    std::string errs;
    
    // 解析
    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
    bool ok = reader->parse(jsonStr.c_str(),
                            jsonStr.c_str() + jsonStr.size(),
                            &root,
                            &errs);
    if (!ok) {
        std::cerr << "Parse error: " << errs << std::endl;
        return;
    }
    
    // 输出解析结果
    std::cout << "Parsed: x=" << root["x"].asInt()
              << ", y=" << root["y"].asInt()
              << ", op=" << root["op"].asString() << std::endl;
    
    // 演示从流解析(使用parseFromStream)
    std::istringstream iss(R"({"result":30})");
    Json::Value resultRoot;
    if (Json::parseFromStream(builder, iss, &resultRoot, &errs)) {
        std::cout << "Result: " << resultRoot["result"].asInt() << std::endl;
    }
    std::cout << std::endl;
}

// 7. 配置CharReaderBuilder:严格模式与注释支持
void TestCharReaderConfig() 
{
    std::cout << "=== TestCharReaderConfig ===" << std::endl;
    std::string jsonWithComment = R"({
        // this is a comment
        "value": 42
    })";
    
    Json::CharReaderBuilder builderNormal;
    Json::CharReaderBuilder builderStrict;
    // 开启严格模式(不允许注释、尾部逗号等)
    builderStrict.settings_["strictMode"] = true;
    
    Json::Value root;
    std::string errs;
    
    // 普通模式可以解析带注释的JSON
    std::unique_ptr<Json::CharReader> readerNormal(builderNormal.newCharReader());
    bool okNormal = readerNormal->parse(jsonWithComment.c_str(),
                                        jsonWithComment.c_str() + jsonWithComment.size(),
                                        &root, &errs);
    std::cout << "Normal mode parse success: " << okNormal << std::endl;
    if (okNormal) {
        std::cout << "  value = " << root["value"].asInt() << std::endl;
    }
    
    // 严格模式应该失败
    std::unique_ptr<Json::CharReader> readerStrict(builderStrict.newCharReader());
    errs.clear();
    bool okStrict = readerStrict->parse(jsonWithComment.c_str(),
                                        jsonWithComment.c_str() + jsonWithComment.size(),
                                        &root, &errs);
    std::cout << "Strict mode parse success: " << okStrict << std::endl;
    if (!okStrict) {
        std::cout << "  error: " << errs << std::endl;
    }
    std::cout << std::endl;
}

// 8. 迭代器遍历(统一遍历数组或对象)
void TestValueIterators() 
{
    std::cout << "=== TestValueIterators ===" << std::endl;
    // 对象
    Json::Value obj;
    obj["name"] = "adder";
    obj["port"] = 8080;
    
    std::cout << "Iterating object:" << std::endl;
    for (auto it = obj.begin(); it != obj.end(); ++it) {
        // 对于对象,it.key() 返回键名,*it 返回值
        std::cout << "  key = " << it.key().asString()
                  << ", value = " << (*it).asString() << std::endl;
    }
    
    // 数组
    Json::Value arr(Json::arrayValue);
    arr.append(1.1);
    arr.append(2.2);
    arr.append(3.3);
    
    std::cout << "Iterating array:" << std::endl;
    for (const auto& elem : arr) {  // 基于范围的for循环
        std::cout << "  " << elem.asDouble() << std::endl;
    }
    std::cout << std::endl;
}

// 9. 拷贝与移动语义(Json::Value支持值语义)
void TestValueCopyMove() 
{
    std::cout << "=== TestValueCopyMove ===" << std::endl;
    Json::Value original;
    original["data"] = "important";
    
    // 拷贝构造(深拷贝)
    Json::Value copy(original);
    copy["data"] = "modified";
    std::cout << "original: " << original["data"].asString() << std::endl;
    std::cout << "copy: " << copy["data"].asString() << std::endl;
    
    // 移动构造
    Json::Value moved(std::move(original));
    std::cout << "moved: " << moved["data"].asString() << std::endl;
    // original 现在处于有效但未指定状态,通常应不再使用
    std::cout << "original isNull? " << original.isNull() << std::endl;
    std::cout << std::endl;
}

int main() 
{
    TestValueBasics();
    TestValueArray();
    TestValueObject();
    TestValueGetOrDefault();
    TestSerialization();
    TestDeserialization();
    TestCharReaderConfig();
    TestValueIterators();
    TestValueCopyMove();
    return 0;
}

        结果为:

        接下来我们来手动写序列化代码

日志

#pragma once

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>   
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
//RAII风格代码:资源获取即初始化
// 互斥锁类,封装了pthread_mutex_t
class Mutex
{
    public:
        Mutex()
        {
            pthread_mutex_init(&_lock, nullptr);
        }
        void Lock()
        {
            pthread_mutex_lock(&_lock);
        }
        pthread_mutex_t *Ptr()
        {
            return &_lock;
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_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;
};


//日志本体
namespace Logger
{   
    enum class LogLevel
    {
        INFO,    ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
        WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
        ERROR,   ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
        FATAL,   ///< 致命错误,表示严重的故障,通常会导致应用程序终止
        DEBUG    ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
    };
    std::string LogLevelToString(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 GetCurrentDateTime() 
    {
        auto now = std::chrono::system_clock::now();
        std::time_t now_c = std::chrono::system_clock::to_time_t(now);

        std::tm tm_info;                      // 用户提供的缓冲区
        localtime_r(&now_c, &tm_info);         // POSIX 线程安全函数

        std::ostringstream oss;
        oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
        return oss.str();
    }
    // 输出角度 -- 刷新策略
    // 1. 显示器打印
    // 2. 文件写入

    // 日志的生成:
    // 1. 构建日志字符串
    // 2. 根据不同的策略,进行刷新

    //策略模式接口
    class LogStrategy
    {
    public:
        virtual void LogRefresh(const std::string &message) = 0;
        virtual ~LogStrategy() = default;
    };
    // 控制台日志刷新策略, 日志将来要向显示器打印
    class ConsoleStrategy : public LogStrategy
    {
    public:
        // 显示器打印策略刷新
        void LogRefresh(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";
    namespace fs = std::filesystem;

    
    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
            : _logpath(path),
              _logfilename(name)
        {
            if (std::filesystem::exists(_logpath))
                return;

            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch(const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
            if(!_logpath.empty()&&_logpath.back() != '/')
                _logpath += '/';
        }
        // 文件策略刷新
        void LogRefresh(const std::string &message) override
        {
            {
                std::string targetlogfile = _logpath +  _logfilename;
                LockGuard lockguard(_mutex);
                std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
                if (!logFile.is_open())
                {
                    std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
                    return;
                }        
                logFile << message << "\n";
                logFile.close();
            }
            
            
        }

        ~FileLogStrategy()
        {
        }

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

    // 根据日志等级分类保存的策略类
    class LevelFileLogStrategy : public LogStrategy {
    public:
        /**
         * @brief 构造函数,指定日志根目录
         * @param log_dir 存放等级日志文件的目录,默认为 "./logs"
         */
        LevelFileLogStrategy(const std::string &log_dir = "./logs") : _log_dir(log_dir) {
            // 确保目录存在
            if (!std::filesystem::exists(_log_dir)) {
                std::filesystem::create_directories(_log_dir);
            }
            // 规范化目录路径,末尾添加 '/'
            if (!_log_dir.empty() && _log_dir.back() != '/') {
                _log_dir += '/';
            }
        }

        /**
         * @brief 刷新日志:根据等级写入对应文件
         * @param message 完整的日志消息(格式由 Logger::LogMessage 生成)
         */
        void LogRefresh(const std::string &message) override {
            // 1. 从消息中提取日志等级
            LogLevel level = extractLogLevel(message);
            // 2. 构造对应的文件名(例如:INFO.log)
            std::string filename = _log_dir + LogLevelToString(level) + ".log";

            // 3. 线程安全地追加写入文件
            LockGuard lockguard(_mutex);
            std::ofstream logFile(filename, std::ios::app);
            if (!logFile.is_open()) {
                std::cerr << "无法打开日志文件: " << filename << std::endl;
                return;
            }
            logFile << message << "\n";
            logFile.close();
        }

    private:
        std::string _log_dir;      // 日志根目录
        Mutex _mutex;              // 文件写入互斥锁

        /**
         * @brief 从日志消息中解析出等级
         * @param msg 完整日志消息,格式为 "[时间][等级][PID][文件:行号] 用户内容"
         * @return 对应的 LogLevel 枚举值,解析失败时默认返回 INFO
         */
        LogLevel extractLogLevel(const std::string &msg) {
            // 寻找第一个 ']' 的位置
            size_t first_close = msg.find(']');
            if (first_close == std::string::npos) {
                return LogLevel::INFO;   // 格式错误,默认 INFO
            }
            // 寻找第二个 '[' 的位置
            size_t second_open = msg.find('[', first_close);
            if (second_open == std::string::npos) {
                return LogLevel::INFO;
            }
            // 寻找第二个 ']' 的位置
            size_t second_close = msg.find(']', second_open);
            if (second_close == std::string::npos) {
                return LogLevel::INFO;
            }
            // 提取等级字符串(如 "INFO")
            std::string level_str = msg.substr(second_open + 1, second_close - second_open - 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;      // 未知等级,默认 INFO
        }
    };
    class Logger
    {
        public:
            Logger()
            {}
            void UseConsoleStrategy()
            {
                _strategy = std::make_unique<ConsoleStrategy>();
            }
            void UseFileStrategy()
            {
                _strategy = std::make_unique<FileLogStrategy>();
            }
            void UseLevelFileStrategy(const std::string &log_dir = "./logs") 
            {
                _strategy = std::make_unique<LevelFileLogStrategy>(log_dir);
            }
            void Debug(const std::string &message)
            {
                if (_strategy!= nullptr)
                {
                    _strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
                }
            }
            //日志内容
            //一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
            //我们想以RAII形式刷新日志信息
            class LogMessage
            {
                public:
                    LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
                        : _level(level), _filename(filename), _line(line),_logger(logger)
                    {
                        _cur_time = GetCurrentDateTime();
                        _pid=getpid();
                        // 构建日志左半部分信息
                        std::stringstream oss;
                        oss <<"["<<_cur_time<<"]"
                            <<"["<<LogLevelToString(_level)<<"]"
                            <<"["<<_pid<<"]"
                            <<"[" << filename << ":" 
                            << line << "]";
                        _LogInfo = oss.str();
                    }
                    template<typename T>
                    LogMessage& operator<<(const T&info)
                    {
                        std::stringstream oss;
                        oss<< info;
                        _LogInfo += oss.str();
                        return *this;
                    }
                    ~LogMessage()
                    {
                        if (_logger._strategy != nullptr)
                        {
                            _logger._strategy->LogRefresh(_LogInfo);
                        }
                    }
                private:
                    std::string _cur_time;
                    LogLevel _level; 
                    pid_t _pid;
                    std::string _filename;
                    size_t _line;
                    std::string _LogInfo;
                    Logger &_logger ;//方便进行后续策略方式刷新
            };
            //对LogMessage进行()重载
            //必须用拷贝,否则会导致<<重载的时候内容消失
            LogMessage operator()(LogLevel level, const std::string filename, size_t line)
            {
                return LogMessage(level, filename, line, *this);
            }
            ~Logger()
            {}
        private:
            std::unique_ptr<LogStrategy> _strategy;
    };
    //日志对象全局使用
    Logger logger;
    #define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
    #define ENABLE_LOG_FILE() logger.UseFileStrategy()
    #define Log(level) logger(level, __FILE__, __LINE__)
    #define ENABLE_LOG_LEVEL_FILE(log_dir) logger.UseLevelFileStrategy(log_dir)
}



计算器

        Calculater.hpp

#pragma once

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

class Calculater
{
public:
    Response Execute(const Request &req)
    {
        Response resp;
        if (req.oper_.empty()) {
            resp.flag_ = 3; // 非法操作
            return resp;
        }
        
        char oper_char = req.oper_[0]; // 获取字符串的第一个字符
        switch (oper_char)
        {
        case '+':
            resp.result_ = req.xdata_ + req.ydata_;
            break;
        case '-':
            resp.result_ = req.xdata_ - req.ydata_;
            break;
        case '*':
            resp.result_ = req.xdata_ * req.ydata_;
            break;
        case '/':
        {
            if (req.ydata_ == 0)
                resp.flag_ = 1; // div error
            else
                resp.result_ = req.xdata_ / req.ydata_;
        }
        break;
        case '%':
        {
            if (req.ydata_ == 0)
                resp.flag_ = 2; // mod error
            else
                resp.result_ = req.xdata_ % req.ydata_;
        }
        break;
        default:
            resp.flag_ = 3; // 非法操作
            break;
        }
        return resp;
    }
};

客户端

#pragma once 
#include<iostream>
#include<string>
#include<cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

//网络客户端封装
class InetAddr
{
    private:
        std::string IP_;
        uint16_t port_;
        struct sockaddr_in address_;
        socklen_t len_;
    public:
        InetAddr()=default;
        InetAddr(const struct sockaddr_in& address) : address_(address),len_(sizeof(address))
        {
            IP_ = inet_ntoa(address_.sin_addr);   // 将二进制IP转换为字符串
            port_ = ntohs(address_.sin_port);      // 端口转换正确
        }
        InetAddr(uint16_t port,const std::string ip="0.0.0.0"):IP_(ip),port_(port)
        {
            bzero(&address_, sizeof(address_));
            address_.sin_family = AF_INET;
            address_.sin_addr.s_addr = inet_addr(IP_.c_str());
            address_.sin_port = htons(port_);
            len_=sizeof(address_);
        }
        struct sockaddr *GetNetAddress()
        {
            return CONV(&address_);
        }
        ~InetAddr()=default;
        socklen_t len()
        {
            return len_;
        }
        bool operator==(const InetAddr&addrs)
        {
            return (this-> IP_==addrs.IP_)&&(this->port_==addrs.port_);
        }
        bool operator=(struct sockaddr_in &addrs)
        {
            char ipstr[32];
            inet_ntop(AF_INET, &(addrs.sin_addr), ipstr, sizeof(ipstr));
            IP_ = ipstr;
            port_ = ntohs(addrs.sin_port);
        }
        std::string ToString()
        {
            return "[" + IP_ + ":" + std::to_string(port_) + "]";
        }

};

原型

        自定义套接字

        socket.hpp

#pragma once 
#include "InetAddr.hpp"
#include "Log.hpp"
#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
namespace CustomProtocol
{
    using namespace Logger;
    enum
    {
        SUCCESS = 0,
        SOCKET_ERROR = -1,
        BIND_ERR=-2,    
        LISTEN_ERR=-3,
    };
    class Socket
    {
        private:

        public:
            
            virtual void CreateSocketOrDie() = 0;
            virtual void BindSocketOrDie(uint16_t port) = 0;
            virtual void ListenSocketOrDie() = 0;
            // virtual ssize_t Reve() = 0;
            // virtual void send() = 0;
            // virtual void Close() = 0;
            // virtual bool Connect(InetAddr &addr) = 0;
            virtual std::shared_ptr<Socket> Accept(InetAddr &addr) = 0;

        public:
            Socket()=default;
            ~Socket()=default;
            void BuildTcpSocket(uint16_t port)
            {
                CreateSocketOrDie();
                BindSocketOrDie(port);
                ListenSocketOrDie();
            }
    };

    class TcpSocket : public Socket
    {
        private:
            int sockfd_;
        public:
            TcpSocket()
                :sockfd_(0)
            {}
            TcpSocket(int sockfd)
                :sockfd_(sockfd)
            {}
            ~TcpSocket()
            {
                close(sockfd_);
            }
            void CreateSocketOrDie() override
            {
                sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
                if(sockfd_ < 0)
                {
                    Log(LogLevel::FATAL)<<"socket create error";
                    exit(SOCKET_ERROR);
                }
            }
            void BindSocketOrDie(uint16_t port)  override
            {
                InetAddr addr(port);
                if (bind(sockfd_, (struct sockaddr*)addr.GetNetAddress(), addr.len()) != 0)
                {
                    Log(LogLevel::FATAL) << "bind socket error";
                    exit(BIND_ERR);
                }
            }
            void ListenSocketOrDie()  override
            {
                if (listen(sockfd_, 5) != 0)
                {
                    Log(LogLevel::FATAL) << "listen socket error";
                    exit(LISTEN_ERR);
                }
            }

            std::shared_ptr<Socket> Accept(InetAddr &addr)  override
            {
                struct sockaddr_in clientaddr;
                socklen_t len=sizeof(clientaddr);
                int connfd = accept(sockfd_, CONV(&clientaddr), &len);
                if(connfd<0)
                {
                    Log(LogLevel::FATAL)<<"accept socket error";
                    return nullptr;
                }
                addr=clientaddr;
                return std::make_shared<TcpSocket>(connfd);
            }
            
            
    };
    class UdpSocket : public Socket
    {

    };

}

        自定义TCP服务器

        TcpServer.hpp

#include"Socket.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include<memory>

static const int cport=8080;
using namespace CustomProtocol;
class TcpServer
{
    private:
        uint16_t port_;
        std::unique_ptr<Socket> listsock_;
    public:
        TcpServer(uint16_t port=cport)
            :port_(port)
        {
            listsock_=std::make_unique<TcpSocket>();
            listsock_->BuildTcpSocket(port_);
        }
        ~TcpServer()
        {}
        void Loop()
        {
            while(1)
            {
                InetAddr clientaddr;
                auto sockfd=listsock_->Accept(clientaddr);
                if(!sockfd)
                    continue;
                Log(LogLevel::DEBUG)<<"accept one new link ,socketfd: "<<clientaddr.ToString();
                sleep(1);
            }
            
        }
};

多进程改版

        自定义协议

#pragma once
//自定义协议
#include "Log.hpp"
#include <jsoncpp/json/json.h>
#include <iostream>
#include <functional>
#include <string>
using namespace Logger;
///常见的序列化与反序列化工具:json、protobuf、xml
//请求报文
class Request
{
    public:
        Request()
            :xdata_(0),ydata_(0),oper_("")
        {

        }
        Request(int xdata,int ydata,char oper)
            :xdata_(xdata),ydata_(ydata),oper_(std::string(1, oper))
        {

        }

        ~Request()=default;
        //序列化
        bool Serialize(std::string *OutMessage)
        {
            //格式为:xdata_ oper_ ydata_
            //结构体->xdata_ oper_ ydata_
            Json::Value root;
            root["xdata_"] = xdata_;
            root["ydata_"] = ydata_;
            root["oper_"] = oper_;
            Json::FastWriter fastWriter;
            *OutMessage = fastWriter.write(root);
            return true;
        }
        //反序列化
        bool Deserialize(std::string &InMessage)
        {
            //xdata_ oper_ ydata_->结构体
            //结构体->xdata_ oper_ ydata_
            Json::Value root;
            Json::Reader reader;
            bool parsesuccess = reader.parse(InMessage, root);
            if(!parsesuccess)
                return false;

            xdata_ = root["xdata_"].asInt();
            ydata_ = root["ydata_"].asInt();
            oper_ = root["oper_"].asString();
            return true;
        }
    public:
    //格式为:xdata_ oper_ ydata_
        int xdata_;//前
        int ydata_;//后
        std::string oper_;//运算符
};
//回应报文
class Response
{
    public:
        Response()=default;
        Response(int result,int flag)
            :result_(result),flag_(flag)
        {}
        ~Response()
        {}
        //序列化
        bool Serialize(std::string *OutMessage)
        {
            //格式为:xdata_ oper_ ydata_
            //结构体->xdata_ oper_ ydata_
            Json::Value root;
            root["result_"] = result_;
            root["flag_"] = flag_;
            Json::FastWriter fastWriter;
            *OutMessage = fastWriter.write(root);
            return true;
        }
        //反序列化
        bool Deserialize(std::string &InMessage)
        {
            //xdata_ oper_ ydata_->结构体
            //结构体->xdata_ oper_ ydata_
            Json::Value root;
            Json::Reader reader;
            bool parsesuccess = reader.parse(InMessage, root);
            if(!parsesuccess)
                return false;

            result_ = root["result_"].asInt();
            flag_ = root["flag_"].asInt();
            return true;
        }
    public:
        int result_;//结果
        int flag_;//状态码
};
const std::string cap = "\r\n";

using HandlerRequest_t = std::function<Response(Request &)>;
using HandlerResponse_t = std::function<void (Response &)>;
//自定义协议
class Protocol
{
    public:
        Protocol(HandlerRequest_t handler_request) 
            : version_("1.0"), handler_request_(handler_request)
        {
        }
        Protocol(HandlerResponse_t handler_response)
            :version_("1.0"), handler_response_(handler_response)
        {
        }
        // {"left": 10, "right": 20, oper: '+'}
        // len\r\n{"left": 10, "right": 20, oper: '+'}\r\n
        std::string Pack(const std::string& JsonString)
        {
            return std::to_string(JsonString.size()) + cap + JsonString + cap;
        }
        // len\r\n{"left": 10, "right": 20, oper: '+'}\r\n
        // len\r\n{"left": 10, "right": 20, oper: '+'}\r\nlen\r\n{"left": 10, "right": 20, oper: '+'}\r\n
        // len\r\n{"left": 10, "right": 20, oper: '+'}\r\nlen\r\n{"left": 10,
        // len\r\n{"left": 10, "right": 20, o
        // le
        // ret > 0: no error, json_string != NULL
        // ret == 0: no error, json_string == NULL
        // ret < 0 : error.
        int Unpack(std::string& Packet,std::string* JsonString)
        {
            if(Packet.empty())
                return 0;
            if(JsonString==nullptr)
                return -1;
             // 分析报文
            auto pos = Packet.find(cap);
            if (pos == std::string::npos)
                return 0;
            std::string lenstr = Packet.substr(0, pos);

            // lenstr 合法性判断,lenstr -> 123 345

            int len = std::stoi(lenstr);
            size_t total = lenstr.size() + len + 2 * cap.size();
            if (Packet.size() < total)
                return 0;
            // 提取报文
            *JsonString = Packet.substr(pos + cap.size(), len);
            Packet.erase(0, total);
            return 1;
            
        }
        // 如果读到半个报文,什么都不做
        // 如果读到一个报文+,循环处理,把所有合法的报文都进行统一处理
        std::string ParseRequest(std::string &inbuffer)
        {
            std::string result;
            while (true)
            {
                std::string json_string;
                // 1. 解包
                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. 反序列化
                // 得到一个完整的报文jsonstring
                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 += Pack(resp_json_string);
            }
        }
        std::string ParseResponse(std::string &inbuffer)
        {
            while (true)
            {
                std::string json_string;
                // 1. 解包
                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 std::string();
                }
                // 2. 反序列化
                // 得到一个完整的报文jsonstring
                Response resp;
                if (!resp.Deserialize(json_string))
                    return std::string();
                
                // 3. 回调处理
                if (handler_response_)
                    handler_response_(resp);
            }
        }
        ~Protocol()=default;
    private:
        std::string version_;//版本号
        HandlerRequest_t handler_request_;
        HandlerResponse_t handler_response_;
};

        服务器运行

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculater.hpp"
#include <memory>


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

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<Calculater> cal = std::make_unique<Calculater>();

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

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

    return 0;
}

        客户端运行

#include <iostream>
#include <string>
#include <memory>
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"

using namespace CustomProtocol;

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

static void HanlderResponse(Response &resp)
{
        std::cout << "result: " << resp.result_ << "[" << resp.flag_ << "]" << std::endl;
}

// ./netcal_client 目标IP 目标主机端口
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->CreateSocketOrDie();  // 只创建套接字,不绑定和监听

    InetAddr serveraddress(server_port, server_ip);
    bool n = socket->Connect(serveraddress);
    if (!n)
    {
        std::cerr << "connect error: " << serveraddress.ToString() << std::endl;
        exit(2);
    }
    Protocol procotol(HanlderResponse);
    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. 封装报头
            std::string send_req_string = procotol.Pack(req_json);
            outbuffer += send_req_string;
        }
        std::cout << "\n" << outbuffer << std::endl;

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

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

        // 6. 解析
        procotol.ParseResponse(inbuffer);
    }
    socket->Close();
    // sock
    return 0;
}

测试

        测试结果为:

        后续我还会补充多线程版本,并且结合线程池。敬请期待!

封面图自取:

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐