序列化和反序列的学习

一:重谈协议

1 理解网络协议,可以把它想象成网络世界里的"交通规则"和"通用语言"。它是一套预先定义好的规则、标准和约定,使得不同设备、不同系统之间能够顺利地进行通信和数据交换。

我们从TCP协议上面理解一下,首先TCP服务是全双工的,怎么理解是全双工的呢,就是如果使用TCP服务,在客户端或者是服务端,都有两个缓冲区---一个是发送缓冲区,一个是接收缓冲区,我们在TCP接收发送文件不是用的是read,wirte吗,当要接收数据时,就从接收缓冲区里读数据交给用户空间,也就是从内核到用户,从而发送数据时,write函数就把用户空间的数据拷贝到发送缓冲区里,也就是从用户层到内核层。刷新什么的由TCP自己决定,全全交给了OS。所以TCP又叫做传输控制协议,里面有各种报头来确认数据传输的正确性。

这个确认数据的完整性或者正确性什么的,UDP和TCP就有了各自的特点,UDP是面向报文的,相当于快递,他就会要求传递报文必须是完整的,TCP是相当于自来水接水,所以呢他就有可能传过来的数据是一段一段的,我们在报头里就会有一些报文的长度啦,还有什么分隔符啦,分开报头和有效载荷了什么的。而这些要求就是我们今天要说的序列化和反序列化。

二:序列化和反序列化

我们所说的协议就是一种约定,客户端和服务端用的是同一套协议,举个例子,假设客户端发送请求,这个报文里面呢设置报文长度,有效载荷传过来的数据,性别啦,年龄了等等。而服务端接收到的数据就是已经序列化好的,就是把这些数据按照一定顺序排列好了给你发送过来了,接着就要把它反序列化,就是把这些存的东西一个一个的对应的从字符串里拿出来。说简单点了就是把字符串里的内容存放在对应的结构体里。协议是一种 "约定". socket api 的接口。

实现序列化和反序列化大体上有两种方法:

第一种就是自定义的方式,我自己规定传过来的数据 结构是什么样,从而让你读取到一个序列化好的,你可以通过反序列化拿到对应的数据,然后再返回一个序列化的结果,服务端接收到数据,也能通过反序列化拿到对应的结果

第二种方式就是用现成的 JSON 序列化 (Serialize),将内存中的数据结构(如对象、数组、字符串、数字、布尔值等)转换成符合 JSON 格式的字符串的过程。JSON 反序列(Deserialize),将一个符合 JSON 格式的字符串 解析并转换回内存中的数据结构(如对象、数组等)的过程。

三:使用序列化和反序列化实现一个简单的网络计算器

客户端和服务端没什么太大的变化。我们今天主要写的就是一个序列化和反序列化

我们直接用一个JSON的,比较方便,但是你前提的安装一下,

Ubuntu的:sudo yum install -y jsoncpp-devl

Centos的:sudo apt install -y jsoncpp-devl

1.我们需要构建两个类,一个请求,一个应答

请求在发送之前,客户端要进行序列化

请求在发送之后,服务端要进行反序列化

应答在发送之前,服务端要进行序列化

应答在发送之后,客户端要进行反序列化

我们实现的计算器比较简单 请求就是 一个数字 x 一个数字 再加一个符号,例子 30 + 20

cpp 复制代码
    int _x;
    int _y;
    char _oper;

然后把他进行序列化

cpp 复制代码
     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;
     }

这个out_string ,是作为输出型参数,把序列化的结果存进out_string

反序列化

cpp 复制代码
 bool deserialize(std::string& in_string )
    {
        Json::Value root;
        Json::Reader reader;
        bool parsingSuccessful = reader.parse(in_string, root);
        if (!parsingSuccessful)
        {
         
            return false;
        }

        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();

        return true;


    }

in_string 就是作为一个输入型参数,把对应的结果存给对方就可以

应答的话就是一个结果,还有一个结果描述符,0设为正常,1设置为 / 或% 0了等等,这些都可以自己约定。

cpp 复制代码
private:
    int _result;
    int _code;

序列化和反序列化道理一致,我们就不再分开展示了。

完整的代码Protocol.hpp

cpp 复制代码
class Response
{
    public:
     Response() : _result(0), _code(0)
    {
    }
    Response(int result, int code) : _result(result), _code(code)
    {
    }
    ~Response()
    {
    }
    bool Serialize(std::string& out_string)
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::StreamWriterBuilder wg;
        std::unique_ptr<Json::StreamWriter>  w(wg.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 parsesucess = reader.parse(in_string,root);
        if(!parsesucess)
        {
            std::cout<<"parseucess false"<<std::endl;
            return false;
        }

        _result = root["result"].asInt();
        _code = root["code"].asInt();
        return true;
       

    }
    int Result() const { return _result; }
    int Code() const { return _code; }
    void SetResult(int res) { _result = res;}
    void SetCode(int c) {_code = c;}

    private:
    int _result;
    int _code;
};

2.客户端

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

using namespace LogModule;




int main(int argc , char* argv[])
{
    if(argc != 3 )
    {
        std::cout<<"Clinet need two arguments"<<std::endl;
        return 1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        LOG(LogLevel::ERROR) << "create scokfd false";
    }
    struct sockaddr_in peer;
    memset(&peer,0,sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = ::htons(port);
    peer.sin_addr.s_addr = ::inet_addr(ip.c_str());


    //客户端不用bind 绑定 ,tcp是面向连接的,connect 会自动绑定
    int n = ::connect(sockfd,CONV(&peer),sizeof(peer));
    if(n<0)
    {
        LOG(LogLevel::ERROR)<<"connect false";
        return  1;
    }
    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 req(x,y,oper);

        //序列化
        std::string message;
        req.Serialize(message);

        Encode(message);
        char inbuffer[1024];
        // n = ::write(sockfd, message.c_str(), message.size());
        n = ::send(sockfd,message.c_str(),message.size(),0);
        if(n > 0)
        {
           
            //int m = ::read(sockfd, inbuffer, sizeof(inbuffer));
            int m =::recv(sockfd,inbuffer,sizeof(inbuffer),0);
            if(m > 0)
            {
                inbuffer[m] = 0;
                std::string tmp = inbuffer;
                std::string content;
                Decode(tmp,&content);
                Response resp;
                resp.deserialize(content);
                std::cout<<resp.Result()<<resp.Code()<<std::endl;
            }
            else
                break;
        }
        else 
            break;
    }
    ::close(sockfd);

    return 0;
}

3.服务端

TCPServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include "Common.hpp"
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <functional>
#include "Log.hpp"
#include "Internet.hpp"
#include "ThreadPool.hpp"


#define BackWait 8

using namespace LogModule;
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
using handler_t = std::function<std::string(std::string& tmp)>;

uint16_t defaultport = 8080;
std::string defaultip = "127.0.0.1";

class TcpServer
{
    struct Thread 
    {
        int sockfd;
        TcpServer* self;
    };
public:
    TcpServer(handler_t hander, uint16_t port = defaultport, std::string ip = defaultip)
        : _port(port), _ip(ip), _isrunning(false), _listensockfd(-1)
        ,_hander(hander)
    {
    }
    ~TcpServer()
    {
    }
    void InitServer()
    {
        _listensockfd = ::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create sockfd false";
            Die(1);
        }
        LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;

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

        

        // //面向连接的,还要等待随时被连接
        // //所以要设置为监听模式
        
       

        // 2. bind
        int n = ::bind(_listensockfd,CONV(&local),sizeof(local));

        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd is : " << _listensockfd;
        // 3. cs,tcp是面向连接的,就要求tcp随时随地等待被连接
        // tcp 需要将socket设置成为监听状态

        int m = ::listen(_listensockfd,8); 
        if(m < 0 )
        {
            LOG(LogLevel::FATAL) <<"监听失败";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::FATAL) <<"监听成功";
        
    }
    void HandlerRequest(int sockfd)
    {
        char buffer[1024];
        std::string package;
        while(true)
        {
            //接受消息
            ssize_t n = ::read(sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n] = 0;
                LOG(LogLevel::DEBUG)<<"接受到消息了#:"<<buffer;
                package += buffer;

                std::string cmd_result = _hander(package);
               
                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0); // 写入也是不完善
            }
            else if(n == 0)
            {
                LOG(LogLevel::INFO) << "client quit: " << sockfd;
                break;
            }
            else
                break;

        }
    }
    static void* ThreadHandler(void* args)
    {
        //用线程也要等待回收(join) 必须等待回收的话就会阻塞,所以让线程自己结束释放资源
        pthread_detach(pthread_self());
        Thread* tmp = (Thread*)args;
        tmp->self->HandlerRequest(tmp->sockfd);
        return nullptr;

    }
    void Start()
    {
        _isrunning = true;
        
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(_listensockfd,CONV(&peer),&len);
            //建立连接之后,这个对应的文件描述符才负责传信(接待)
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error: " << strerror(errno);
                continue;
            }
            LOG(LogLevel::INFO) << "accept success, sockfd is : " << sockfd;
            InetAddr inetaddr(peer);
            LOG(LogLevel::INFO) << "client info: " << inetaddr.Addr();


            //version 0
            //HandlerRequest(sockfd);

            // version -1 多线程版本
            // pid_t pid = fork();
            //::signal(SIGCHLD,SIG_IGN);
            // if(pid == 0)
            // {
            //     //子进程再创建孙子进程,子进程直接退掉,由系统进程1 来回收管理孙子进程
            //     //子进程和父进程 各有一张文件描述符表 文件都是通过引用计数进行管理的
            //     //就像管道一样
            //     ::close(_listensockfd);
            //     if(fork() > 0)
            //     {
            //         exit(0);
            //     }
            //     HandlerRequest(sockfd);
            //     exit(0);
            // }
            // 给出建议父进程不要关闭文件描述符,这个设计叫权责分明
            // 现在语法执行没错,如果修改内容容易有错
            // ::close(sockfd);
            
            // pid_t waitid = ::waitpid(pid,nullptr,0);
            // if(waitid<0)
            // {
            //     LOG(LogLevel::ERROR)<<"回收父进程失败";
            // }
        //version 2 用多线程   
        // pthread_t pid;
        // Thread* data = new Thread;
        // data->self = this;
        // data->sockfd = sockfd;
        // pthread_create(&pid,nullptr,ThreadHandler,data);
        
        //version 3 线程池
           task_t f = std::bind(&TcpServer::HandlerRequest,this,sockfd);
            ThreadPool<task_t>::getInstance()->Equeue([&sockfd,this](){
                this->HandlerRequest(sockfd);
           });
       }

    }

private:
    int _listensockfd; //这个文件描述符 只负责监听(也就是送客人,不负责招待)
    uint16_t _port;
    std::string _ip;
    bool _isrunning;
    handler_t _hander;
};

TCPServer.cc

cpp 复制代码
#include"TcpServer.hpp"
#include<memory>
#include"Log.hpp"
#include"Calculator.hpp"
#include"Protocol.hpp"
#include<functional>
#include"Daemon.hpp"

using namespace LogModule;

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

// using cal_fun = std::function<Response(const Request &req)>;

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

class Parse
{
    public:
    Parse(Cal_t cal)
    :_cal(cal)
    {

    }
    std::string Entry(std::string& package)
    {
        std::string message;
        std::string resptr;
        while(Decode(package,&message))
        {
            LOG(LogLevel::DEBUG) << "Content: \n" << message;
            if(message.empty())
            break;
            //反序列化
            Request req;
            if(!req.deserialize(message))
            break;

            //计算
            std::string tmp;
            Response resp = _cal(req);
            resp.Serialize(tmp);

            //添加长度字段
            Encode(tmp);

            //拼接应答,这样的话有多少个需求都会处理,最后统一返回
            resptr+=tmp;

        }
        return resptr;
    }
    private:
    Cal_t _cal;
};

int main()
{
    //ENABLE_FILE_LOG();

    //Daemon(false,false);
    Cal cal;
    Parse parse([&cal](const Request& req){
        return cal.entry(req);
    });
    
    std::shared_ptr<TcpServer> tserver = std::make_shared<TcpServer>([&parse](std::string &package){
       return parse.Entry(package);
    });
    tserver->InitServer();
    tserver->Start();


    return 0;
}

4.计算端

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

class Cal
{
    public:
    Cal()
    {

    }
    Response entry(const Request& req)
    {
        Response resp;
        switch (req.Oper())
        {
        case '+':
            resp.SetResult(req.X() + req.Y());
            break;
        case '-':
            resp.SetResult(req.X() - req.Y());
            break;
        case '*':
            resp.SetResult(req.X() * req.Y());
            break;
        case '/':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(1); // 1 就是除0
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // 2 就是mod 0
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
        resp.SetCode(3);
        break;
        }
        return resp;
    }
    private:
};

5.Log.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
// + 日志的可变部分(<< "hello world"

namespace LogModule
{
       using namespace Lock;
    std::string CurrentTime()
    {
        time_t time_stamp=::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr);
        char buffer[1024];

        snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
        curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);
    return buffer;

    }
    enum class LogLevel
    {
        DEBUG=1,
        INFO,
        WARNING,
        ERROR,
        FATAL

    };
    
    std::string Level2String(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(const std::string & message) = 0 ;
    };

    class ControlStrategy : public LogStrategy
    {
        public:
        ControlStrategy()
        {
            
        }
        ~ControlStrategy()
        {

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

        private:
        Mutex _mut;
        
    };
   
    /*std::filesystem::exists(_logpath)*/
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";
    class FileStrategy : public LogStrategy
    {
        public:
        FileStrategy(const std::string& logpath=defaultlogpath,const std::string& logname = defaultlogname)
        :_logpath(logpath)
        ,_logname(logname)
        {
            LockGuard lockguard(_mut);
            if(std::filesystem::exists(_logpath))
            {
                return;    
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch(std::filesystem::filesystem_error& t)
            {
                std::cerr<<t.what()<<std::endl;
            }
        }
        ~FileStrategy()
        {

        }
        void SyncLog(const std::string& message)
        {
           LockGuard lockgurad(_mut);
           std::string log = _logpath+_logname;
           std::ofstream out(log,std::ios::app);
           if(!out.is_open())
           {
                std::cout<<"打开失败"<<std::endl;
                return;
           }
           out<<message<<"\n";
           out.close();
        }
        
        private:
        Mutex _mut;

        std::string _logpath;
        std::string _logname;
    };
    class Logger
    {
        public:
        Logger()
        {
            _strategy=std::make_shared<ControlStrategy>(); 
        }
        void EnableControl()
        {
             _strategy=std::make_shared<ControlStrategy>(); 
        }
        void EnableFile()
        {
            _strategy=std::make_shared<FileStrategy>();
        }
        ~Logger()
        {

        }
        //一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16]
        class LogMessage
        {
            public:
            LogMessage(LogLevel level,const std::string &filename, int line,Logger& logger)
            :_time(CurrentTime())
            ,_level(level)
            ,_pid(::getpid())
            ,_name(filename)
            ,_line(line)
            ,_logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _time << "] "
                         << "[" <<Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _name << "] "
                         << "[" << _line << "] - ";
              _loginfo = ssbuffer.str();
              //std::cout<<_loginfo<<std::endl;         
            }
            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 _time;
            LogLevel _level;;
            pid_t _pid;
            std::string _name;
            int _line;
            std::string _loginfo;
            Logger &_logger;
        };
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line,*this);
        }
        private:
        std::shared_ptr<LogStrategy> _strategy;
    };

    Logger logger;
#define LOG(level) logger(level,__FILE__,__LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableControl()
#define ENABLE_FILE_LOG() logger.EnableFile()

}

四 重谈七层协议

五层协议: 物理层 数据链路层 网络层 应用层 传输层

七层协议:物理层 数据链路层 网络层 应用层 传输层 会话层 表示层

五层模型是将 OSI 七层模型中的会话层、表示层和应用层这三层合并成了一个单一的应用层。

为什么这样合并?

实际应用:在实际的 TCP/IP 协议栈中,会话管理(如建立、管理和终止会话)和数据表示(如数据加密、压缩、格式转换)的功能通常由应用程序本身或应用层协(如 HTTP、TLS/SSL)来实现,而不是由一个独立的、通用的协议层来处理。也就是说应用层实现大部分,所以为了不冲突,就由用户层自我决定。