应用层自定义协议和序列化

应⽤层协议

我们程序员写的⼀个个解决我们实际问题, 满⾜我们⽇常需求的⽹络程序, 都是在应⽤层.
应⽤层协议可以概括为计算机之间通信的 "通用语言规则",核心是让不同设备的应用程序能看懂彼此传的数据、知道怎么交互。

tcp是面向字节流的的特点,就代表了它传输数据并不是一股脑将数据全部传输过去,而是一段一段的,故我们上层对数据进行提取时可能就会得不到完整的数据。也就是粘包(多个包粘一起)和半包(一个包只传了一部分)

下面我简单写了个应用层协议解决该问题:

EnCode方法明确约定了数据包结构:

长度字符串 + sep + JSON数据 + sep(比如:127\n\r{json}\n\r

cpp 复制代码
// 添加长度报头字段
// 127\n\r{json}\n\r
bool EnCode(std::string &out_buffer)
{
    if (out_buffer.size() == 0)
        return false;
    std::string result = std::to_string(out_buffer.size()) + sep + out_buffer + sep;
    out_buffer = result;

    return true;
}

而Decode则可以先通过sep找到长度字段,再通过长度计算完整包的总长度,解决 TCP 字节流无边界问题。

cpp 复制代码
// 解析长度报头字段!
bool DeCode(std::string &package, std::string *content)
{
    std::string length_str;
    int pos = package.find(sep);
    if (pos == std::string::npos)
        return false;

    length_str = package.substr(0, pos);//得出结构化数据的长度
    int length = std::stoi(length_str);
    int full_length = length + 2 * sep.size() + length_str.size();//该段结构数据的全部长度
    if (package.size() < full_length)
    {
        return false;
    }

    *content = package.substr(pos + sep.size(), length);

    package.erase(0, full_length); // 127\n\r{json}\n\r127\n\r{json}\n\r处理这种情况
    return true;
}

UDP虽然是面向数据报的,但它一次能够传输的数据也是有上限的,也会面临数据截断的问题。UDP的解决方案有:应用层主动预分片,和各种成熟的上层协议;想了解的可以去了解下。

序列化和反序列化

序列化:

  • 把代码里有结构、能直接用的数据,转换成字节流 的过程。

反序列化:

  • 把收到字节流 ,还原成代码里能直接操作的结构化数据的过程。

为什么要有序列化 / 反序列化

在计算机的底层只认字节,并不认结构化数据。

倘若传入的是一段结构化数据,应用层或者服务器要怎么读取它呢!

你可能会说用相同的结构体去接受就好了。但我们要知道在不同的OS下,结构体的内存布局是不一样的。首先就是 Windows和Linux下内存对齐规则不同。并且32位系统下int是4字节,但在64位系统下的int可能就是8字节,等等...

我们需要知道,直接传结构体二进制是 "绑死内存布局",序列化是 "只传数据本身"

故序列化为了让数据能传 ,反序列化为了让数据能用

为了解决序列化 / 反序列化问题,C++直接引入了Jsoncpp库,jsoncpp 本质是帮我们做了 3 件关键事:

  • 屏蔽内存布局差异
  • 简化序列化 / 反序列化逻辑
  • 处理 JSON 语法细节,避免手写的语法错误。

Jsoncpp

特性:

  1. 简单易⽤:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。

⾼性能:Jsoncpp 的性能经过优化,能够⾼效地处理⼤量 JSON 数据。

  1. 全⾯⽀持:⽀持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和null。
  2. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,⽅便开发者调试。

Jsoncpp的安装

cpp 复制代码
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

Jsoncpp提供了多种⽅式进⾏序列化:

序列化

Json::Value

使⽤ 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" : "男"
}

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" : "男"
}

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

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

反序列化

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: 北京

网络计算器完整代码

先将日志代码倒入:

Mutex.hpp

cpp 复制代码
#pragma once

#include<iostream>
#include<pthread.h>

namespace LockModule
{
    class Mutex
    {
        Mutex(const Mutex& m) = delete;//互斥锁是不可拷贝的资源
        Mutex& operator =(const Mutex& m) = delete;//一个互斥锁对象对应操作系统内核中的一个锁实例,拷贝它意味着创建 "两个对象对应同一个内核锁"
    public:
    Mutex()
    {
        int n = pthread_mutex_init(&_mutex, nullptr);
        (void)n;
    }

    void lock()
    {
        int n = pthread_mutex_lock(&_mutex);
        (void)n;
    }

    void unlock()
    {        
        int n = pthread_mutex_unlock(&_mutex);
        (void)n;
    }

    pthread_mutex_t* mutexptr()
    {
        return &_mutex;
    }

    ~Mutex()
    {
        int n = pthread_mutex_destroy(&_mutex);
        (void)n;
    }
    private:
    pthread_mutex_t _mutex;
    };

    //RAII风格,自动析构,无需手动释放资源
    class LockGuard
    {
    public:
        LockGuard(Mutex& mutex):_mutex(mutex)
        {
            _mutex.lock();
        }
        ~LockGuard()
        {
            _mutex.unlock();
        }
    private:
        Mutex& _mutex;
    };
}

Log.hpp

cpp 复制代码
#pragma once
#include <sstream> //stringstream
#include <unistd.h>
#include <filesystem>
#include "Mutex.hpp"
#include <iostream>
#include <string>
#include <time.h>
#include <fstream>
#include <memory> //智能指针

namespace LogModule
{
    using namespace LockModule;

    std::string CurrentTime() // 获取时间
    {
        time_t time = ::time(nullptr);
        struct tm st;
        localtime_r(&time, &st);
        char buff[1024] = {0};
        snprintf(buff, sizeof(buff), "%4d-%02d-%02d %02d:%02d:%02d",
                 st.tm_year + 1900,
                 st.tm_mon + 1,
                 st.tm_mday,
                 st.tm_hour,
                 st.tm_min,
                 st.tm_sec);

        return buff;
    }

    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 将日志等级转化成字符串
    std::string leveltostring(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 "None";
        }
    }

    // 刷新策略(基类)
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(std::string &message) = 0;
    };

    // 控制台策略
    class ConsoleLogStrategy : public LogStrategy //(继承LogStrategy)
    {
    public:
        ConsoleLogStrategy() {}

        void SyncLog(std::string &message)
        {
            LockGuard lockguard(mutex);
            std::cout << message << std::endl;
        }

        ~ConsoleLogStrategy() {}

    private:
        Mutex mutex;
    };

    const std::string defaultlpathname = "./log";
    const std::string defaultlfilename = "/log.txt";//这里需注意加上/符号
    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(std::string lpathname = defaultlpathname, std::string lfilename = defaultlfilename)
            : _lpathname(lpathname),
              _lfilename(lfilename)
        {
            LockGuard lockguard(mutex);
            if (std::filesystem::exists(lpathname))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(lpathname); // 没有该目录就尝试创造,失败则抛出异常
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }

        void SyncLog(std::string &message)
        {
            LockGuard lockguard(mutex);
            std::string log = _lpathname + _lfilename;
            std::ofstream out(log, std::ios::app); // 以追加方式打开
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close(); //// 强制刷新缓冲区到文件,并释放资源
        }

        ~FileLogStrategy() {}

    private:
        std::string _lpathname;
        std::string _lfilename;

        Mutex mutex;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>(); // 默认控制台策略
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>(); // 手动控制台策略
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>(); // 手动文件(磁盘)策略
        }

        //[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可变参数
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger& logger)
                : _level(level),
                  _filename(filename),
                  _line(line),
                  _pid(getpid()),
                  _currtime(CurrentTime()),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << leveltostring(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }

            template<class T>
            LogMessage& operator<<(const T& info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

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

        private:
            std::string _currtime;        // 该日志时间
            pid_t _pid;                   // 该进程pid
            LogLevel _level;              // 日志等级
            const std::string &_filename; // 源文件名称
            int _line;                    // 消息的行号
            std::string _loginfo;         // 一条完整的日志记录
            Logger& _logger;              // 负责根据不同的策略进行刷新
        };

        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

        ~Logger() {}

    private:
        std::shared_ptr<LogStrategy> _strategy; // 刷新方案指针
    };

    Logger logger;

#define LOG(level) logger(level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

序列化反序列化与应用层协议:

Protocol.hpp

cpp 复制代码
#pragma once

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

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

// 添加长度报头字段
// 127\n\r{json}\n\r
bool EnCode(std::string &out_buffer)
{
    if (out_buffer.size() == 0)
        return false;
    std::string result = std::to_string(out_buffer.size()) + sep + out_buffer + sep;
    out_buffer = result;

    return true;
}

// 解析长度报头字段!
bool DeCode(std::string &package, std::string *content)
{
    std::string length_str;
    int pos = package.find(sep);
    if (pos == std::string::npos)
        return false;

    length_str = package.substr(0, pos);//得出结构化数据的长度
    int length = std::stoi(length_str);
    int full_length = length + 2 * sep.size() + length_str.size();//该段结构数据的全部长度
    if (package.size() < full_length)
    {
        return false;
    }

    *content = package.substr(pos + sep.size(), length);

    package.erase(0, full_length); // 127\n\r{json}\n\r127\n\r{json}\n\r处理这种情况
    return true;
}

class Request
{
public:
    Request() : _x(0), _y(0), _oper(0)
    {
    }

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

    bool Serialize(std::string &out_string)
    {
        Json::Value root;
        root["_x"] = _x;
        root["_y"] = _y;
        root["_oper"] = _oper;

        Json::StreamWriterBuilder wb;
        std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
        std::stringstream ss;
        w->write(root, &ss);
        out_string = ss.str();
        return true;
    }

    bool Deserialize(std::string &in_string)
    {
        Json::Value root;
        Json::Reader reader;

        std::cout<<"原始数据:"<<in_string<<std::endl;
        
        bool parsingSuccessful = reader.parse(in_string, root);
        if (!parsingSuccessful)
        {
            std::cout << "Failed to parse RequestJSON: " << reader.getFormattedErrorMessages() << std::endl;
            return false;
        }

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

    ~Request()
    {
    }

    int X()const { return _x; }; // 被const修饰的对象只能调用被const修饰的成员函数
    int Y()const { return _y; };
    char OPER()const {return _oper;};
private:
    int _x;
    int _y;
    char _oper;
};

class Response
{
public:
    Response() : _result(0)
    {
    }

    Response(int result, int reval = 0) : _result(result), _reval(reval)
    {
    }

    bool Serialize(std::string &out_string)
    {
        Json::Value root;
        root["_result"] = _result;
        root["_reval"] = _reval;

        Json::StreamWriterBuilder wb;
        std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
        std::stringstream ss;
        w->write(root, &ss);
        out_string = ss.str();
        return true;
    }

    bool Deserialize(std::string &in_string)
    {
        Json::Value root;
        Json::Reader reader;

        bool parsingSuccessful = reader.parse(in_string, root);
        if (!parsingSuccessful)
        {
            std::cout << "Failed to parse ResponseJSON: " << reader.getFormattedErrorMessages() << std::endl;
            return false;
        }

        _result = root["_result"].asInt();
        _reval = root["_reval"].asInt();
        return true;
    }

    int Result() const { return _result; }
    int REVAL() const { return _reval; }

    ~Response()
    {
    }

private:
    int _result;
    int _reval;
};

Calculator.hpp

cpp 复制代码
#pragma once

#include "Protocol.hpp"

class Calculator
{
public:
    Calculator() {}

    Response Execute(const Request &reque)
    {
        int x = reque.X();
        int y = reque.Y();
        char oper = reque.OPER();
        Response resp;
        switch (oper)
        {
        case '+':
            resp = Response(x + y);
            break;
        case '-':
            resp = Response(x - y);
            break;
        case '*':
            resp = Response(x * y);
            break;
        case '/':
        {
            if(y == 0) 
            resp = Response(0, 1);
            else
            resp = Response(x / y);
        }
            break;
        case '%':
        {
            if(y == 0) 
            resp = Response(0, 2);
            else
            resp = Response(x % y);
        }
            break;
        default:
        resp = Response(0, 3);
            break;
        }

        return resp;
    }

    ~Calculator() {}
};

TcpServer.hpp

cpp 复制代码
#pragma once

#include <unistd.h>
#include <iostream>
#include <string>
#include <unistd.h>
#include <strings.h>
#include <cstring>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>

#include "Log.hpp"

using namespace ThreadPoolModule;
using namespace LogModule;

#define BACKLOG 8

const static int gsockfd = -1;
const static std::string gip = "0.0.0.0"; // 表示所有ip
const static uint16_t gport = 8888;

using handler_t = std::function<std::string(std::string command)>;

#define DIE(num)   \
    do             \
    {              \
        exit(num); \
    } while (0)

class TcpServer
{
    struct TcpData
    {
        int sockfd;
        TcpServer *self;
    };

public:
    TcpServer(handler_t handler, uint16_t port = gport, std::string ip = gip)
        : _handler(handler),
          _listensockfd(gsockfd),
          _port(gport),
          _ip(ip),
          _isrunning(false)
    {
    }
    void Init()
    {
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            DIE(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd: " << _listensockfd;

        // 允许端口复用,当TCP服务器崩溃或重启时,端口不会立即释放,而是会处于TIME_WAIT状态(默认约2分钟)
        int opt = 1;
        setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = ::htons(_port);
        local.sin_addr.s_addr = ::inet_addr(_ip.c_str());

        int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            DIE(2);
        }
        LOG(LogLevel::INFO) << "bind success";

        // 设置TCP服务器开始监听客户端连接
        n = ::listen(_listensockfd, BACKLOG);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen: " << strerror(errno);
            DIE(3);
        }
        LOG(LogLevel::INFO) << "listen success";
    }

    void HandlerRequest(int sockfd)
    {
        LOG(LogLevel::INFO) << "HandlerRequest...";
        // 接收客户端数据
        char rebuffer[1024];
        while (true)
        {
            int n = ::recv(sockfd, rebuffer, sizeof(rebuffer) - 1, 0);
            if (n > 0)
            {
                rebuffer[n] = 0;
                LOG(LogLevel::INFO) << rebuffer;

                std::string message = _handler(rebuffer);
                ::send(sockfd, message.c_str(), message.size(), 0);
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "client close...";
                break;
            }
            else
            {
                LOG(LogLevel::ERROR) << "recv: " << strerror(errno);
                break;
            }
        }
    }

    static void *ThreadEntry(void *args)
    {
        pthread_detach(pthread_self());//避免主线程阻塞等待,故将线程分离
        struct TcpData * Data = (struct TcpData* )args;
        Data->self->HandlerRequest(Data->sockfd);
        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定

            LOG(LogLevel::DEBUG) << "accept ing ...";
            // tcp服务端客户端是 "一对一" 的专属连接	udp服务端↔多个客户端是 "一对多" 的无关联通信
            // "一对一" 的专属连接: 每新增一个客户端进行来链接tcp就会监听到并连接生成新的sockfd
            int sockfd = ::accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "accept: " << strerror(errno);
                continue;
            }

            LOG(LogLevel::INFO) << "accept success, sockfd: " << sockfd;
            std::string ip = inet_ntoa(peer.sin_addr);
            uint16_t port = ntohs(peer.sin_port);
            LOG(LogLevel::INFO) << "client ip: " << ip << ", port: " << port;

            //多进程
            pid_t id = fork();
            if(id == 0)
            {
                ::close(_listensockfd);
                if(fork() == 0)
                {
                    //由孙子进程来执行操作,让父进程直接回收子进程
                    //孙子进程就会直接由PID 1 进程接管,进行资源回收
                    HandlerRequest(sockfd);
                    exit(0);
                }
                exit(0);
            }
            ::close(sockfd);
            pid_t mid = waitpid(id, nullptr, 0);
            if(mid < 0)
            {
                LOG(LogLevel::WARNING) << "waitpid error";
            }
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
        if (_listensockfd > gsockfd)
            ::close(_listensockfd);
    }

private:
    handler_t _handler;
    int _listensockfd;
    uint16_t _port;  // 服务器未来的端口号
    std::string _ip; // 服务器所对应的IP
    bool _isrunning; // 服务器运行状态
};

TcpServerMain.cc

cpp 复制代码
#include <memory>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include "Calculator.hpp"
#include "TcpServer.hpp"
#include <functional>
#include "Protocol.hpp"
#include "Log.hpp"

using namespace LogModule;
// package一定会有完整的报文吗??不一定
// 不完整->继续读
// 完整-> 提取 -> 反序列化 -> Request -> 计算模块,进行处理

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

class Phrase
{
public:
    Phrase(calfun cal) : _cal(cal)
    {
    }

    std::string Entry(std::string &package)
    {
        std::string content;
        std::string ret;
        // 解析长度报头字段!
        while (DeCode(package, &content))
        {
            LOG(LogLevel::ERROR) << "content: \n"
                                 << content;
            if (content.empty())
            {
                break;
            }
            Request res;
            // 反序列化
            if (!res.Deserialize(content))
            {
                break;
            }
            LOG(LogLevel::ERROR) << "res反序列化: ";

            // 计算
            Response resp = _cal(res);

            // 序列化
            resp.Serialize(content);
            LOG(LogLevel::ERROR) << "resp序列化: ";

            // 添加长度报头字段
            EnCode(content);
            LOG(LogLevel::ERROR) << "EnCode: 添加长度报头字段";

            ret += content;
        }

        return ret;
    }

private:
    calfun _cal;
};

int main()
{
    Calculator mycal;

    std::unique_ptr<Phrase> phrase = std::make_unique<Phrase>(
        [&mycal](const Request &reque)
        { return mycal.Execute(reque); });

    std::unique_ptr<TcpServer> svr = std::make_unique<TcpServer>(
        [&phrase](std::string package)
        { return phrase->Entry(package); });

    svr->Init();
    svr->Start();

    return 0;
}

TcpClientMain.cc

cpp 复制代码
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "Protocol.hpp"

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port[1024]"
              << std::endl;
}


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

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socker error" << std::endl;
        return 1;
    }

    //客户端发起连接
    int n = ::connect(sockfd, (struct sockaddr*)&server, len);
    if(n < 0)
    {
        std::cout<<"connect fail..."<<std::endl;
        return 3;
    }

    std::string message;
    while (true)
    {
        int x, y;
        char oper;
        std::cout << "input x: ";
        std::cin >> x;
        std::cout << "input y: ";
        std::cin >> y;
        std::cout << "input oper: ";
        std::cin >> oper;

        //序列化
        Request reque(x, y, oper);
        reque.Serialize(message);

        //添加长度报头字段
        EnCode(message);

        int m = ::send(sockfd, message.c_str(), message.size(), 0);
        char inbuffer[1024];

        if(m > 0)
        {
            std::string package;
            int n = ::recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
            if(n > 0)
            {
                inbuffer[n] = 0;
                package = inbuffer;

                std::string content;
                DeCode(package, &content);

                Response resp;
                resp.Deserialize(content);

                std::cout<< resp.Result() <<"[" << resp.REVAL() <<"]"<<std::endl;
            }
            else 
                break;
        }
        else
            break;
    }
    close(sockfd);
    return 0;
}
相关推荐
末日汐10 小时前
库的制作与原理
linux·后端·restful
ba_pi10 小时前
每天写点什么2026-01-10-深度学习和网络原理
网络·人工智能·深度学习
tmacfrank10 小时前
Binder 预备知识
linux·运维·binder
王夏奇10 小时前
python在汽车电子行业中应用2—具体包的介绍和使用
网络·python·汽车
cnstartech10 小时前
esxi-vmware 虚拟机互相打开
linux·运维·服务器
mcdx10 小时前
bootm的镜像加载地址与uImage镜像的加载地址、入口地址之间的关系
linux
不知疲倦的仄仄10 小时前
第四天:Netty 核心原理深度解析&EventLoop、Future/Promise 与 Pipeline
linux·服务器·网络
橘颂TA10 小时前
【Linux 网络编程】网络是怎么 “跑” 起来的?从协议入门到 TCP/ IP 模型的底层逻辑
linux·运维·服务器·网络
looking_for__10 小时前
【Linux】进程间通信
linux