【网络】协议的定制与Json序列化和反序列化

这篇具有很好参考价值的文章主要介绍了【网络】协议的定制与Json序列化和反序列化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

应用层

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

初识TCP协议通讯流程

  • 建立链接和断开链接

基于TCP协议,我们需要知道写代码时对应的接口大概在TCP通讯的过程中属于什么样的时间点角色,在TCP协议时详谈。三次握手,四次挥手

listen状态:准备好了,可以进行链接,accept:获取链接,不是创造链接,链接已经在底层创建好了,在应用层调用accept把链接拿上来

connect:1.发起链接请求2.绑定套接字;建立链接,在底层向服务端建立链接请求,在TCP中,采用链接的方案是三次握手的方案,connect会发起三次握手,发起链接请求和真正的建立链接是两码事,建立链接由双方OS自动完成的,为什么自动完成?网络分层中,下三层是OS内部的,用户感知不到。通过客户端调用connect让OS来帮我们把三次握手的工作做完。

而accept是获取链接,链接是已经建立好了的,所以accept并不参与三次握手的任何细节,accept一定是在获取链接前别人把链接做完,既链接建立完。三次握手是OS自己完成的,connect只是发起,accept只是收尾。即使上层不调用accept,三次握手也是能够建立好的。

TCP保证可靠性不是write和read有关系的,由双方OS完成的,后面详谈。

建立链接后面就要断开链接,所以UDP由于不需要建立链接,自然不需要谈论断开链接

而四次挥手的工作都是由双方的OS完成,而我们决定什么时候分手一旦调用系统调用close,用户层就不用管了。

  • 理解链接

谈男女朋友时,都会表达自己的爱意,一定有一方主动发起链接,无论如何表达,双方看对眼的概率是极低的。而主动发起链接,是怎么发起的呢?首先,男方先表白,然后女方在做表态,什么时候在一起?男方回答就现在。这就是双方三次握手成功。(虽然现实生活中被拒绝是常态)

建立链接究竟在干什么:记下一些东西

  • 什么是建立链接

所谓的建立链接,三次握手根本就是手段,不是目的,为了达到让双方都能记住这一套,一个服务端链接客户端,很多客户端来链接了,意味着很多的客户端来了,OS应该区分清楚,需要把链接管理起来,先描述在组织,需要创建对应的链接数据结构,把所有的链接描述起来,在对其进行管理。所谓的链接就是OS内部创建的链接结构体,包含了在建立链接时对应的属性信息。当有新的链接进来时,每到来一个链接,服务端会构建一个链接对象 ,将所有的链接对象在内部中用特定的数据结构管理起来。这就是链接的建模过程。维护链接是需要成本的。占用内存资源,要用对象进行管理。

断开链接需要四次挥手,断开链接的最终目的毫无疑问就是把建立好的链接信息释放。四次挥手理解:

男女朋友处的非常好,走到了婚姻的殿堂,但是被现实打败了,过不下去啦。然后一方提出离婚,但是你自己说了不算,另一方说好啊,过了一会,对象又说离就离,那我也要离,那么你一看,我也OK。所以断开链接是双方的事情,必须得征求双方的意见。双方在协商,TCP要保证可靠性,你说的话要保证你也听到了,我也知道了,反之也一样。这就是传说中的四次挥手

TCP与UDP对比

可靠传输VS不可靠传输

有连接VS无连接

字节流VS数据报

定制协议

应用层协议的定制

再谈协议

协议是一种约定,socket api的接口,在读写数据时,都是按照字符串的方式来接收的,如果要传输一些”结构化的数据“怎么办呢?

结构化的数据:群里说话的时候除了消息本身,还有头像,昵称时间等等信息 。但是不是一个一个独立的个体,你需要做的把这些消息形成一个报文——打包成一个字符串。

由多变一这个过程就是序列化。经过网络传输后,收到的是一个报文,收到一个报文要的是什么?把一个字符串变成多个字符串,这个过程是反序列化

业务数据发送到网络的时候,先序列化发送,收到的是序列字节流,要先进行反序列化, 然后才能使用

业务协议就是结构体,这样说还是不够的,所以我们要手写一个协议。

应用场景:形成字符串对方收到,收到之后上层来不及接收,对方又发一个,有可能一次全读的,上层如何保证收到的是一个报文?

tcp这里怎么保证收到一个完整的报文

理解业务协议,理解序列化和反序列化。

网络版本计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加减乘除数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端

TCP是面向字节流的,所以明确报文和报文的边界:

【网络】协议的定制与Json序列化和反序列化

TCP是全双工的,如果接收方来不及读,那接收缓冲区就会存在很多数据,读的时候怎么怎么保证读到一个完整的报文:

1.定长2.特殊符号3.自描述方式

序列化、反序列化与定制协议是两码事,是不同阶段的事情,定制协议:报头+有效载荷

Protocal.hpp

自定义协议:

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)

请求和响应:Request,Response

Request:x,y,op(“x op y”)x和y是数据,op是操作符,比如1+2

Response:设置了退出码exitcode和结果result()

对请求和响应添加报头,这里设置的报头是长度,enLength(即添加大小,转化成字符串),也就是封装了enLength函数:

//"x op y"->"content_len"\r\n"x op y"\r\n
//"exitcode result"->"cotent_len"\r\n"exitcode result"\r\n
const std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;
    return send_string;
}

对请求和响应提取报文,只要报文,不要报头,也就是封装了deLength函数:

//"cotent_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos);
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

对请求和响应进行序列化和反序列化:对于序列化和反序列化我们可以用Json来进行实现

序列化过程:结构化数据->“x op y”

反序列化过程:“x op y”->结构化数据

Protocal.hpp还提供了recvPackage函数

#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERROR
};

//"x op y" --->"content_len"\r\n"x op y"\r\n,添加报头
std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;
    return send_string;
}

//"content_len"\r\n"exitcode result"\r\n
// 去掉报头,得到"exitcode result"
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos); // content_len:如“14”
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

class Request
{
public:
    Request() : x(0), y(0), op(0)
    {
    }

    Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
    {
    }

    // 序列化:
    // 结构化-> "x op y"
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        std::string x_string = std::to_string(x);
        std::string y_string = std::to_string(y);

        *out = x_string;
        *out += SEP;
        *out += op;
        *out += SEP;
        *out += y_string;
#else
        Json::Value root;
        root["first"] = x;
        root["second"] = y;
        root["oper"] = op;

    Json::FastWriter writer;
    *out = writer.write(root);
#endif
        return true;
    }

    // 反序列化化:
    //"x op y"->结构化
    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);
        if (left == std::string::npos || right == std::string::npos)
            return false;
        if (left == right)
            return false;
        if (right - (left + SEP_LEN) != 1)
            return false;
        std::string x_string = in.substr(0, left);
        std::string y_string = in.substr(right + SEP_LEN);

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;
        x = std::stoi(x_string);
        y = std::stoi(y_string);
        op = in[left + SEP_LEN];
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        x = root["first"].asInt();
        y = root["second"].asInt();
        op = root["oper"].asInt();
#endif
        return true;
    }

public:
    int x;
    int y;
    char op;
};

class Response
{
public:
    Response() : exitcode(0), result(0)
    {
    }

    Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
    {
    }

    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        std::string ec_string = std::to_string(exitcode);
        std::string res_string = std::to_string(result);

        *out = ec_string;
        *out += SEP;
        *out += res_string;
#else
        Json::Value root;
        root["exitcode"] = exitcode;
        root["result"] = result;

        Json::FastWriter writer;
        *out = writer.write(root);
#endif
        return true;
    }

    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        std::string ec_string = in.substr(0, mid);
        std::string res_string = in.substr(mid + SEP_LEN);

        if (ec_string.empty() || res_string.empty())
            return false;
        exitcode = std::stoi(ec_string);
        result = std::stoi(res_string);
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        exitcode = root["exitcode"].asInt();
        result = root["result"].asInt();
#endif
        return true;
    }

public:
    int exitcode;
    int result;
};

// 读取报文,保证读取的是一个完整的报文 ,inbuffer由外部传入
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
    char buffer[1024];
    while(true)
    {
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if(n>0)
        {
            buffer[n] = 0;
            inbuffer+=buffer;
            
            auto pos = inbuffer.find(LINE_SEP);
            if(pos == std::string::npos) continue;
            std::string text_len_string = inbuffer.substr(0,pos);
            int text_len  =std::stoi(text_len_string);
            int total_len = text_len_string.size()+2*LINE_SEP_LEN+text_len;

            std::cout<<"处理前#inbuffer:\n"<<inbuffer<<std::endl;
            if(inbuffer.size()< total_len)
            {
                std::cout<<"你输入的消息,没有遵守所定制的协议,正在等待后续的内容,continue"<<std::endl;
                continue;
            }

            //至少是一个完整的报文
            *text = inbuffer.substr(0,total_len);
            inbuffer.erase(0,total_len);

            std::cout<<"处理后#inbuffer:\n"<<inbuffer<<std::endl;
            break;
        }
        else
            return false;
    }
    return true;
}

对于recvPackage函数我们要保证读到的至少是一个完整的报文

CalServer

服务端代码

//CalServer.hpp
namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;
    typedef std::function<bool(const Request &req, Response &resp)> func_t;


    void handlerEntery(int sock,func_t func)
    {
        std::string inbuffer;
        while(true)
        {
            //1.读取:"content_len"\r\n"x op y"\r\n
            //保证读到的消息是一个完整的请求
            std::string req_text,req_str;
            if(!recvPackage(sock,inbuffer,&req_text)) return;
            std::cout<<"带报头的请求:\n"<<req_text<<std::endl;
            //去掉报头
            if(!deLength(req_text,&req_str)) return;
            std::cout<<"去掉报头的正文:\n"<<req_str<<std::endl;

            //2.对请求Request,反序列化
            //2.1得到一个结构化的请求对象
            Request req;
            if(!req.deserialize(req_str)) return;

            //3.计算机处理————业务逻辑
            Response resp;
            func(req,resp);

            //4.对响应Response,进行序列化
            //4.1得到一个"字符串"
            std::string resp_str;
            resp.serialize(&resp_str);

            std::cout<<"计算完成,序列化响应:"<<resp_str<<std::endl;

            //5.发送响应
            std::string send_string = enLength(resp_str);
            std::cout<<"构建完成完整的响应\n"<<send_string<<std::endl;
            send(sock,send_string.c_str(),send_string.size(),0);


        }
    }
    class CalServer
    {
    public:
        CalServer(const uint16_t&port = gport):_listensock(-1),_port(port)
        {}

        void initServer()
        {
            _listensock = socket(AF_INET,SOCK_STREAM,0);
            if(_listensock<0)
            {
                logMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }

            logMessage(NORMAL,"create socket success:%d",_listensock);

            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(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                logMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL,"bind socket success");

            if(listen(_listensock,gbacklog)<0)
            {
                logMessage(FATAL,"listen socker error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL,"listen socket success");
        }

        void start(func_t func)
        {
            for(;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
                if(sock<0)
                {
                    logMessage(ERROR,"accept error,next");
                    continue;
                }
                logMessage(NORMAL,"accept a new link success,get new sock:%d",sock);

                pid_t id = fork();
                if(id == 0)
                {
                    close(_listensock);
                    handlerEntery(sock,func);
                    close(sock);
                    exit(0);
                }
                close(sock);

                pid_t ret = waitpid(id,nullptr,0);
                if(ret>0)
                {
                    logMessage(NORMAL,"wait child success");

                }
            }
        }
         ~CalServer() {}
    public:
        int _listensock;
        uint16_t _port;
    };
}

//CalServer.cc
#include "calServer.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// req是处理好的完整的请求对象
// resp:根据req进行业务处理,填充resp,不需要管理任何IO,序列化和反序列化
bool cal(const Request &req, Response &resp)
{
    // req已经有结构化的数据
    resp.exitcode = OK;
    resp.result = 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.exitcode = DIV_ZERO;
        else resp.result = req.x/req.y;
    }
    break;
    case '%':
    {
        if(req.y == 0)
            resp.exitcode = MOD_ZERO;
        else resp.result = req.x%req.y;
    }
    break;
    default:
        resp.exitcode = OP_ERROR;
        break;
    }
    return true;
}

// tcp服务器,启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);
    unique_ptr<CalServer> tsvr(new CalServer(port));
    tsvr->initServer();
    tsvr->start(cal);
    return 0;
}

CalClient

ParseLine:解析,构建一个请求:“1+1”文章来源地址https://www.toymoban.com/news/detail-493174.html

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Protocol.hpp"
#define NUM 1024

class CalClient
{
public:
    CalClient(const std::string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {
    }
    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(2);
        }
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string line;
            std::string inbuffer;
            while (true)
            {
                std::cout << "mycal>>> ";
                std::getline(std::cin, line);  // 1+1
                Request req = ParseLine(line); // "1+1"
                std::string content;
                req.serialize(&content);
                std::string send_string = enLength(content);
                send(_sock, send_string.c_str(), send_string.size(), 0); // bug?? 不管

                std::string package, text;
                //  "content_len"\r\n"exitcode result"\r\n
                if (!recvPackage(_sock, inbuffer, &package))
                    continue;
                if (!deLength(package, &text))
                    continue;
                // "exitcode result"
                Response resp;
                resp.deserialize(text);
                std::cout << "exitCode: " << resp.exitcode << std::endl;
                std::cout << "result: " << resp.result << std::endl;
            }
        }
    }
    Request ParseLine(const std::string &line)
    {
        // 建议版本的状态机!
        //"1+1" "123*456" "12/0"
        int status = 0; // 0:操作符之前,1:碰到了操作符 2:操作符之后
        int i = 0;
        int cnt = line.size();
        std::string left, right;
        char op;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
            {
                if(!isdigit(line[i]))
                {
                    op = line[i];
                    status = 1;
                }
                else left.push_back(line[i++]);
            }
            break;
            case 1:
                i++;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
        return Request(std::stoi(left), std::stoi(right), op);
    }
    ~CalClient()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

#include "calClient.hpp"
#include <memory>

using namespace std;
static void Usage(string proc)
{
    cout<<"\nUasge:\n\t"<<proc<<" serverip serverport\n\n";
}
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<CalClient> tcli(new CalClient(serverip,serverport));
    tcli->initClient();
    tcli->start();
    return 0;
}

Json的安装

sudo yum install -y jsoncpp-devel

到了这里,关于【网络】协议的定制与Json序列化和反序列化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TCP定制协议,序列化和反序列化

    目录 前言 1.理解协议 2.网络版本计算器 2.1设计思路 2.2接口设计 2.3代码实现: 2.4编译测试 总结         在之前的文章中,我们说TCP是面向字节流的,但是可能对于面向字节流这个概念,其实并不理解的,今天我们要介绍的是如何理解TCP是面向字节流的,通过编码的方式,自

    2024年02月12日
    浏览(34)
  • 【网络编程】协议定制+Json序列化与反序列化

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、序列化与反序列化的概念 二、自定义协议设计一个网络计算器 2.1TCP协议,如何保证接收方收到了完整的报文呢?

    2024年02月06日
    浏览(56)
  • 协议定制 + Json序列化反序列化

    1.1 结构化数据 协议是一种 “约定”,socket api的接口, 在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些\\\"结构化的数据\\\" 怎么办呢? 结构化数据: 比如我们在QQ聊天时,并不是单纯地只发送了消息本身,是把自己的头像、昵称、发送时间、消息本身

    2024年02月09日
    浏览(46)
  • 【网络】协议定制+序列化/反序列化

    如果光看定义很难理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么? 其实序列化最终的目的是为了对象可以 跨平台存储,和进行网络传输 。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是

    2024年02月08日
    浏览(43)
  • iOS处理json,序列化和反序列化

    Mantle 是一个开源的 Objective-C 框架,用于在 iOS 和 macOS 应用程序中实现模型层的序列化和反序列化。它提供了一种简单而强大的方式来将 JSON数据格式转换为自定义的数据模型对象,以及将数据模型对象转换为字典或 JSON 格式。 Mantle具有如下特点 自动映射 Mantle自动将 JSON 数据

    2024年02月11日
    浏览(63)
  • rust学习-json的序列化和反序列化

    由于 serde 库默认使用 JSON 格式进行序列化和反序列化 因此程序将使用 JSON 格式对数据进行序列化和反序列化 JSON:广泛使用的 JavaScript 对象符号,用于许多 HTTP API Postcard:no_std 和嵌入式系统友好的紧凑二进制格式。 CBOR:用于小消息大小且无需版本协商的简洁二进制对象表示

    2024年02月12日
    浏览(40)
  • Untiy Json和Xml的序列化和反序列化

    Ps: Unity C# Json 序列化换行 效果: 引用:

    2024年02月13日
    浏览(47)
  • ASP.NET中JSON的序列化和反序列化

      JSON是专门为浏览器中的网页上运行的JavaScript代码而设计的一种数据格式。在网站应用中使用JSON的场景越来越多,本文介绍ASP.NET中JSON的序列化和反序列化,主要对JSON的简单介绍,ASP.NET如何序列化和反序列化的处理,在序列化和反序列化对日期时间、集合、字典的处理。

    2024年02月15日
    浏览(35)
  • 【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )

    当谈到网络编程时,序列化和反序列化是非常重要的概念。在上一篇文章中,我们已经了解了在Linux环境下实现简单的TCP网络程序的基础知识。本文将继续探讨序列化和反序列化,这些概念对于在网络上传输数据以及跨网络通信至关重要。通过深入了解序列化和反序列化,我

    2024年04月08日
    浏览(88)
  • Spring Boot 中使用 ObjectMapper 进行 JSON 序列化和反序列化

    在 Java 中,可以使用各种 JSON 序列化和反序列化工具将 Java 对象转换为 JSON 字符串或者将 JSON 字符串转换为 Java 对象。其中,Jackson 库是比较常用的 JSON 序列化和反序列化工具之一,它提供了 ObjectMapper 类,用于将 Java 对象转换为 JSON 字符串,或者将 JSON 字符串转换为 Java 对象

    2024年02月14日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包