【 计网 】序列化与反序列化

目录

自定义网络版计算器的实现

1.socket的封装:模版方法模式

2.自定义协议

[2.1 Jsoncpp](#2.1 Jsoncpp)

[2.2 协议类](#2.2 协议类)

[2.3 服务端代码](#2.3 服务端代码)

[2.4 客户端代码](#2.4 客户端代码)


在传输层和网络层是由操作系统决定的,让我们来研究一下应用层。假设现在有这样一个问题。我们定义一个结构体,结构体内容包括头像,昵称,消息内容。我们想把这个结构体从发送到另一台机器上。如果直接发送的话很可能会出现匹配错误。为了保证报文的完整性,方便网络传送。我们就需要对传送的内容进行序列化和反序列化操作

序列化把消息由多变一,方便网络传送。反序列化把消息由一变多,方便上层处理。消息由几部分构成,分别是什么含义,由上层软件来解释。

所谓协议定制,本质就是在定义双方都认识的,符合通信和业务需要的结构化数据,结构化数据就是所谓的struct和class

重新理解与一下write和read

其中由TCP自主决定什么时候发,发多少,出错了怎么办(传输控制协议)。主机间通信的本质,把发送方的发送缓冲区的数据拷贝到接收方的接收缓冲区。为什么TCP是全双工的:因为有两对缓冲区。

我们未来定制的协议:

  1. 结构化的字段,提供好序列化和反序列化方案

  2. 解决因为字节流导致的读取报文不完整的问题

自定义网络版计算器的实现

1.socket的封装:模版方法模式

写一个基类socket,基类的大多数方法,都设置为纯虚方法

bash 复制代码
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
        virtual void Close() = 0;

    public:
        void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
       
    };

再写一个子类TcpSocket继承Socket,实现这些方法

2.自定义协议

2.1 Jsoncpp

Jsoncpp是一个处理JSON数据的C++库,它提供了将JSON数据序列化为字符串以及从字符串反序列化为C++数据结构的功能。

使用方法:

1.序列化操作

bash 复制代码
    std::string Serialize()
    {
        // _x = 10 _y = 20, _oper = '+'
        // "10" "20" '+' : 用空格作为分隔符
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper; // ?

        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }

2.反序列化操作

bash 复制代码
 bool Deserialize(std::string &in)
    {
        // "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt(); //?
        }
        return ok;
    }

将字符串in的内容转换成Json::Value格式,储存在root中

2.2 协议类

协议(基于TCP的)需要解决两个问题:

  1. request和response必须得有序列化和反序列化功能

  2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)

举个例子

因为TCP是面向字节流的,当我们tcp中读取数据的时候,读取到的报文不完整,或者读多了,导致下一个报文不完整了,做"粘报"问题。read本身不负责读到报文的完整性,需要程序员自己解决

我们可以自己在报文前面添加报头

bash 复制代码
    std::string Encode(const std::string &jsonstr)
    {
        // 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
        std::string len = std::to_string(jsonstr.size());
        return len + sep + jsonstr + sep; // 应用层封装报头
    }

    // 1. 判断报文完整性
    // 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
    bool Decode(std::string &buffer, std::string *package)
    {
        ssize_t pos = buffer.find(sep);
        if (pos == std::string::npos)
            return false; // 让调用方继续从内核中读取数据
        std::string package_len_str = buffer.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        // buffer 一定有长度,但是一定有完整的报文吗?
        int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
        if (buffer.size() < target_len)
            return false;

        // buffer一定至少有一个完整的报文!
        *package = buffer.substr(pos + sep.size(), package_len_int);
        buffer.erase(0, target_len);
        return true;
    }
2.3 服务端代码
bash 复制代码
 void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 读取
        std::string buffer_queue;
        while (true)
        {
            int n = sock->Recv(&buffer_queue);
            if(n > 0)
            {
                std::string json_package;
                // 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
                bool ret = Decode(buffer_queue, &json_package);
                if(!ret)
                    continue;
                // 我敢100%保证,我一定拿到了一个完整的报文
                // {"x": 10, "y" : 20, "oper" : '+'} -> 你能处理吗?
                // 2. 请求json串,反序列化
                Request req;
                bool ok = req.Deserialize(json_package);
                if(!ok)
                    continue;
                // 3. 我一定得到了一个内部属性已经被设置了的req了.
                // 通过req->resp, 不就是要完成计算功能嘛!!业务
                Response resp = _func(req);

                // 4. 序列化
                std::string json_str = resp.Serialize();

                // 5. 添加自定义长度
                std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\r\n{result:XXX, code:XX}\r\n"

                // 6. 直接发送
                sock->Send(send_str);
            }
            else if(n == 0)
            {
                LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
                break;
            }
            else 
            {
                LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
                break;
            }
        }
    }

让我们来理清一下服务端所做的工作

2.4 客户端代码

客户端也需要创建套接字,建立连接,连接服务器成功后获取数据,构建请求,发送请求,获取应答,显示结果

bash 复制代码
// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);
    std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
    client->BuildTcpClientSocketMethod();

    if (client->Connect(server_ip, server_port) != 0)
    {
        // 失败
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
    std::string resp_buffer;
    // 连接服务器成功
    while (true)
    {
        // 1. 从标准输入当中获取数据
        int x, y;
        char oper;
        GetDataFromStdin(&x, &y, &oper);

        // 2. 构建一个请求-> 可以直接发送的字符串
        std::string req_str = protocol->BuildRequestString(x, y, oper);

        // std::cout << "-----------encode req string-------------" << std::endl;
        // std::cout << req_str << std::endl;
        // std::cout << "------------------------------------------" << std::endl;
        
        // 3. 发送请求
        client->Send(req_str);

        // 4. 获取应答
        Response resp;
        bool res = protocol->GetResponse(client, resp_buffer, &resp);
        if(res == false)
            break;

        // 5. 显示结果
        resp.ShowResult();
    }
    client->Close();
    return 0;
}

一个自定义网络版计算器就实现了


点个关注不迷路,持续更新好内容

相关推荐
Boop_wu1 小时前
[LangGraph] 案例 2 : 支持搜索的智能代理系统
服务器·windows·python·langchain
物联通信量讯说1 小时前
5G会取代4G吗?物联网设备出海正在走向多网络融合
网络·物联网·5g
祖力551 小时前
网络编程:IO多路复用(select、poll、epoll)
linux·服务器·网络
数字智核1 小时前
2026昆山工厂空压机突然停机怎么办?找谁抢修
服务器·网络·数据库
迷路爸爸1801 小时前
在 Ubuntu / Linux 上轻量安装 LaTeX:TinyTeX 使用记录
linux·运维·ubuntu
信奥卷王1 小时前
2025年09月GESPC++五级真题解析(含视频)
算法
白狐_7981 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法
pt10431 小时前
网络自动化Python课程:利用Cisco NSO编排网络服务
运维·网络·自动化
闻缺陷则喜何志丹1 小时前
【动态规划】P3609 [USACO17JAN] Hoof, Paper, Scissor G
c++·算法·动态规划·洛谷