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

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
相关推荐
_.Switch2 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_850410832 小时前
文件系统和日志管理
linux·运维·服务器
qq_254674412 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.2 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
芯盾时代3 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
心灵彼岸-诗和远方4 小时前
DevOps业务价值流:架构设计最佳实践
运维·产品经理·devops
一只哒布刘4 小时前
NFS服务器
运维·服务器
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
小松学前端5 小时前
第六章 7.0 LinkList
java·开发语言·网络