网络基础二——序列化与反序列化

1、应用层

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

2、再谈"协议"

协议是一种 " 约定 "。 socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的 . 如果我们要传输一些 "结构化的数据 " 怎么办呢 ?
这个时候就要进行序列化和反序列化,什么意思呢?如图:

左边的客户端在给右边的服务器想发送这样一个msg的结构化的数据,那么服务端就必须也要有一个完全相同的结构可以接受数据,然后在客户端向网络发送数据时会先转换成字符串,然后服务器接收时再将字符串转换成结构化的数据,所以只要确保一端发送的数据,另一端能够识别,这种约定,就是应用层的协议。

3、网络版计算器

Socket.hpp

这是一个封装的套接字接口,可以创建套接字,进行绑定、监听、接收、连接以及关闭套接字。

cpp 复制代码
//Socket.hpp
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"

enum{
    SocketErr = 2,
    BindErr,
    ListenErr,
};

//TODO
const int backlog = 10;

class Sock
{
public:
    Sock()
    {}
    ~Sock()
    {}
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd_ < 0)
        {
            lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
            exit(SocketErr);
        }

    }
    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if(bind(sockfd_, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(BindErr);
        }
    }
    void Listen()
    {
        if(listen(sockfd_, backlog) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
        if(newfd < 0)
        {
            lg(Warning, "accept error, %s: %d", strerror(errno), errno);
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);
        return newfd;
    }
    bool Connect(const std::string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
        if (n == -1) 
        {
            std::cerr << "connect to" << ip << ":" << port << std::endl;
            return false;
        }
        return true;
    }
    void Close()
    {
        close(sockfd_);
    }
    int Fd()
    {
        return sockfd_;
    }
private:
    int sockfd_;
};

TcpServer.hpp

下面是一个创建tcp服务器的接口,实现了启动服务器的功能,在里面调用其他接口处理收到的数据然后写回给客户端。

cpp 复制代码
//TcpServer.hpp
#pragma once
#include <functional>
#include <string>
#include "Log.hpp"
#include "Socket.hpp"
#include <signal.h>

using func_t = std::function<std::string (std::string &package)>;

class TcpServer 
{
public:
TcpServer(uint16_t port, func_t callback):port_(port), callback_(callback)
{}
bool InitServer()
{
    listensock_.Socket();
    listensock_.Bind(port_);
    listensock_.Listen();
    lg(Info, "init server ... done");
    return true;
}
void Start()
{
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    while(true)
    {   
        std::string clientip;
        uint16_t clientport;
        int sockfd = listensock_.Accept(&clientip, &clientport);
        if(sockfd < 0) continue;
        lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
        //提供服务
        if(fork() == 0)
        {
            listensock_.Close();
            std::string inbuffer_stream;
            // 数据计算
            while(true)
            {
                char buffer[128];
                ssize_t n = read(sockfd, buffer, sizeof(buffer));
                if (n > 0)
                {
                    buffer[n] = 0;
                    inbuffer_stream += buffer;

                    lg(Debug, "debug: %s", inbuffer_stream.c_str());

                    std::string info = callback_(inbuffer_stream);
                    if(info.empty()) continue;

                    write(sockfd, info.c_str(), info.size());
                }
                else if(n == 0) break;
                else break;
            }
            exit(0);
        }
        close(sockfd);
    }
}
~TcpServer()
{}
private:
    uint16_t port_;
    Sock listensock_;
    func_t callback_;
};

ServerCal.hpp

这是计算器服务器的接口,里面定义了计算器的基本功能加减乘除取模,调用了Protocol接口中的序列化反序列化来将数据标准化。

cpp 复制代码
//ServerCal.hpp
#pragma once 
#include <iostream>
#include "Protocol.hpp"

enum{
    Div_Zero = 1,
    Mod_Zero, 
    Other_Oper
};

class ServerCal
{
public:
    ServerCal()
    {}
    Response CalculatorHelper(const Request &req)
    {
        Response resp(0, 0);
        switch(req.op)
        {
            case '+':
                resp.result = req.x + req.y;
                break;
            case '-':
                resp.result = req.x - req.y;
                break;
            case '*':
                resp.result = req.x * req.y;
                break;
            case '/':
                {
                    if (req.y == 0) resp.code = Div_Zero;
                    else resp.result = req.x / req.y;
                }
                break;
            case '%':
                {
                    if (req.y == 0) resp.code = Mod_Zero;
                    else resp.result = req.x % req.y;
                }
                break;
            default:
                resp.code = Other_Oper;
                break;
        }

        return resp;
    }

    // "len"\n"10 + 20"\n
    std::string Calculator(std::string &package)
    {
        std::string content;
        bool r = Decode(package,&content);  // "len"\n"10 + 20"\n
        if(!r) return "";
        // "10 + 20"
        Request req;
        r = req.Deserialize(content);// "10 + 20" -> x=10 op=+ y=20
        if(!r) return "";

        content = ""; //
        Response resp = CalculatorHelper(req); 

        resp.Serialize(&content);
        content = Encode(content);
        
        return content;
    }
    ~ServerCal()
    {}
};

ServerCal.cc

这里是服务器启动的程序,通过绑定端口号启动服务器。

cpp 复制代码
//ServerCal.cc
#include "TcpServer.hpp"
#include "ServerCal.hpp"

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

//./servercal 8080
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t port = std::stoi(argv[1]);
    ServerCal cal;
    TcpServer *tsvp = new TcpServer(8080, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
    tsvp->InitServer();
    tsvp->Start();
    return 0;
}

Protocol.hpp

这是对数据进行序列化反序列化的接口。

cpp 复制代码
//Protocol.hpp
#pragma once 

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

// #define MySelf 1

const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";

std::string Encode(std::string &content)
{
         // 封装报头
        std::string package = std::to_string(content.size());
        package += protocol_sep;
        package += content;
        package += protocol_sep;

        return package;
}

//"len"\n"x op y"
bool Decode(std::string &package, std::string *content)
{
    std::size_t pos = package.find(protocol_sep);
    if (pos == std::string::npos) return false;
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content + 2
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;

    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);

    return true;
}


// json, protobuf
class Request
{
public:
    Request(int data1, int data2, char oper):x(data1), y(data2), op(oper)
    {}
    Request()
    {}
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建报文的有效载荷
        // struct => string,x op y => "len"\n"x op y"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);
        *out = s;
        return true;
#else 
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        //Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        std::size_t left = in.find(blank_space_sep);
        if(left == std::string::npos) return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if(right == std::string::npos) return false;
        std::string part_y = in.substr(right+1);

        if(left + 2 != right) return false;
        op = in[left + 1];

        x = std::stoi(part_x);
        y = std::stoi(part_y);
        return true;
#else 
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op  = root["op"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成" << x << op << y << '=?' << std::endl;
  
    }
public:
    // x op y
    int x;
    int y;
    char op; // + - * / %
};






class Response
{
public:
    Response(int res, int c):result(res), code(c)
    {}
    Response()
    {}
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);
        *out = s;
        return true;
#else 
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        //Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "result cpde"
    {
#ifdef MySelf
        std::size_t pos = in.find(blank_space_sep);
        if(pos == std::string::npos) return false;
        std::string part_left = in.substr(0, pos);
        std::string part_right = in.substr(pos+1);

        result = std::stoi(part_left);
        code = std::stoi(part_right);
        return true;
#else 
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "结果响应完成: result:" << result << ", code:" << code << std::endl;
    }
public:
    int result;
    int code; // 0, 可信,否则!0具体是几,表明对应的错误原因
};

ClientCal.cc

这是客户端启动的程序,通过服务器ip和端口连接服务器。

cpp 复制代码
//ClientCal.cc
#include <iostream>
#include <string>
#include <ctime>
#include <assert.h>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"

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

// ./clientcal ip port
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]);

    Sock sockfd;
    sockfd.Socket();
    bool r = sockfd.Connect(serverip, serverport);
    if(!r) return 1;
    
    srand(time(nullptr) ^ getpid());
    int cnt = 1;
    const std::string opers = "+-*/%=&^";

    std::string inbuffer_stream;
    while(cnt++ < 10)
    {   
        std::cout << "第" << cnt << "次测试..." << std::endl;
        int x = rand() % 100 + 1;
        usleep(1234);
        int y = rand() % 100;
        usleep(4321);
        char oper = opers[rand() % opers.size()];
        Request req(x, y, oper);
        req.DebugPrint();

        std::string package;
        req.Serialize(&package);

        package = Encode(package);

        std::cout << "这是最新的发出去的请求: \n" << package;
        write(sockfd.Fd(), package.c_str(), package.size());

        char buffer[128];
        ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); //我们也无法保证能读到一个完整的报文
        if(n > 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer; //"len"\n"result code"\n
            std::string content;
            bool r = Decode(inbuffer_stream, &content); // "result code"
            assert(r);

            Response resp;
            r = resp.Deserialize(content);
            assert(r);

            resp.DebugPrint();
        }

        sleep(1);
    }

    sockfd.Close();
    return 0;
}

Log.hpp

这是日志接口。

cpp 复制代码
//Log.hpp
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    // void logmessage(int level, const char *format, ...)
    // {
    //     time_t t = time(nullptr);
    //     struct tm *ctime = localtime(&t);
    //     char leftbuffer[SIZE];
    //     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    //              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    //              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //     // va_list s;
    //     // va_start(s, format);
    //     char rightbuffer[SIZE];
    //     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //     // va_end(s);

    //     // 格式:默认部分+自定义部分
    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

Log lg;

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }

Makefile

cpp 复制代码
//Makefile
.PHONY:all
all:servercal clientcal

Flag=-DMySelf=1
Lib=-ljsoncpp

servercal:ServerCal.cc
	g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag)
clientcal:ClientCal.cc
	g++ -o $@ $^ -std=c++11 -g $(Lib) #$(Flag)

.PHONY:clean
clean:
	rm -f servercal clientcal
相关推荐
SelectDB15 小时前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
zzzzzz3102 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode2 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220703 天前
如何搭建本地yum源(上)
运维
大树886 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz6 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工6 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
网络研究院6 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展