序列化和自定义协议

序言

 在上一篇文章中,我们介绍了Socket 编程,已经可以简单地使用该方法来进行服务端和客户端的数据了。在这篇文章中我们将在此基础上学习序列化和反序列化,以及在应用层上自定义协议


序列化和反序列化

1. 为什么需要序列化和反序列化?

 在 Socket 编程 的学习中,客户端给服务端传输的数据全是字符串,但是如果我们有传输对象或者是结构体的需求怎么办呢?
 首先我们需要知道在网络通信中,直接传输对象或复杂数据结构通常是不可行的,因为网络协议通常只支持字节流或字符流的传输。所以想要传输结构体我们就首先需要将该结构体或对象序列化!序列化可以将这些对象或数据结构转换为字节序列(如二进制格式)或字符序列(如JSON、XML等),从而在网络中传输。

2. 安装 Json 库

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字 符串以及从字符串反序列化为 C++ 数据结构的功能Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。

 我们在这里将简单的带大家上手这个库,首先我们需要安装这个库,在 Linux 输入指令:
ubuntu:sudo apt-get install libjsoncpp-dev

3. 数据序列化

 我们构造一个简单的结构体只包含内置类型的数据:

struct MyData
{
    MyData(int left, int right, char oper)
        : _left(left)
        , _right(right)
        , _oper(oper)
    {}

    int _left;
    int _right;
    char _oper;
};

 现在我们初始化一个结构体对象,Json::Value对象,并使用结构体的数据填充该Json

// 初始化一个对象
MyData d1(1, 2, '-');

// 创建一个 Json::Value 对象,它将用于构建 JSON  
Json::Value root;
// 填充 Json 对象
root["Left"] = d1._left;
root["Right"] = d1._right;
root["Oper"] = d1._oper;

JSON 已经构建好了,现在我们只需要格式化输出字符串就行了:

// FastWriter 的效率比较好,因为不需要额外的输出空格,换行符
Json::FastWriter write;
std::string result = write.write(root);

我们输出一下序列化的结果:

{"Left":1,"Oper":45,"Right":2}

在这里我们也尝试一下其他的格式化输出字符串,比如:

std::string result = root.toStyledString();

输出带有一定风格的字符串结果:

{
   "Left" : 1,
   "Oper" : 45,
   "Right" : 2
}

 如果我使用的是 class 就不能直接访问类的私有成员变量,所以为了更好地实现类的封装,我觉得在类中实现一个成员函数来帮我们完成是更好的方案:

Json::Value toJson() const
{
    // 创建一个 Json::Value 对象,它将用于构建 JSON
    Json::Value root;
    // 填充 Json 对象
    root["Left"] = _left;
    root["Right"] = _right;
    root["Oper"] = _oper;

    return root;
}

现在我们已经将我们的结构体输出为字符串了,并假设已经发送给服务端了,那服务端如何反序列化呢得到正确的数据呢?

4. 数据反序列化

 将数据序列化传送到服务端不是目的,服务端完整的获取原格式的数据才是最终目的!现在我们就需要将数据反序列化,首先我们初始化一个 Json::Value 来存储解析后的 Json 数据,其次我们还要一个 Json::Reader 来解析数据:

Json::Value root;
Json::Reader read;
// 解析数据到 root 中
read.parse(result, root);

我们将获取的数据再构造一个对象也就实现了 脱胎换骨:

MyData d2(root["Left"].asInt(), root["Right"].asInt(), root["Oper"].asInt());

在这里需要注意,我们 提取数据时一定要指定数据的类型


自定义协议

1. 再识 TCP

TCP 是一个全双工通信,他在接收消息的同时也可以发送信息!则会是因为 TCP 在底层实现中 包含两个主要的缓冲区:一个用于发送数据(发送缓冲区),另一个用于接收数据(接收缓冲区):
在这里插入图片描述

如果存在一个场景,发送端发送信息的次数极其高,但是接收端的处理数据的速度也很有限,这就会造成接受端的接收缓冲区非常的拥挤!当我们的发送端发送一个完整的报文过去时,有没有可能我们的接收端没有充足的空间来接受呢?这是完全有可能的吧,这就会造成报文的部分信息丢失!

 为了解决这个问题,我们在应用层可以自定义一个协议,该协议用于判断数据是否完整!
注意:UDP也是一个全双工通信,但是在真正意义上他并不具有发送缓冲区!因为UDP是无连接的、不可靠的协议,它不会将数据保存在缓冲区中等待确认发送!他会直接将数据加上报头后发送!

2. 自定义协议 — 发送端

 我们在这里需要编写一个程序,该程序需要发送一个人的信息给服务端,服务端接收后如果信息完整,返回 OK。该结构体的内容包括如下:

class Person
{
public:
    Person(std::string name, int age, int weight)
        : _name(name)
        , _age(age)
        , _height(weight)    
    {}

    // 便于直接使用 Json 对象初始化
    Person(Json::Value& root)
        : _name(root["Name"].asString())
        , _age(root["Age"].asInt())
        , _height(root["Height"].asInt())    
    {}

    // 序列化
    Json::Value toJson()
    {
        Json::Value root;
        root["Name"] = _name;
        root["Age"] = _age;
        root["Height"] = _height;

        return root;
    }

private:
    std::string _name;
    int _age;
    int _height;
};

增添的 toJson 功能以及使用 Json 对象 初始化等会会用到,一定程度方便我们的编写。

 现在我们将我们的数据序列化了,那如何让添加自定义协议呢?在这里我们的报头就简单一些,我们只是 添加序列化后字符串的长度

len\r\nResult\r\n

在这里我们的 \r\n 代表分隔符,隔开长度和字符串,也隔开不同的组,现在我们就来实现该函数吧:

const std::string SEP = "\r\n";

// 添加报头
void Encode(std::string& jsonstr)
{
    int len = jsonstr.size();
    jsonstr = std::to_string(len) + SEP + jsonstr + SEP;
}

现在我们看一下加上报头后,我们的序列化后的数据是啥样:

38
{"Age":12,"Height":175,"Name":"jack"}


符合我们的预期!

3. 自定义协议 — 接收端

 加入我们现在已经接收到了发送端的数据,那该如何还原呢?现在不能之间反序列化了,因为我们在序列化的基础上加上了报头,那我们先去报头查看信息是否完整:

// 去除报头
std::string Decode(std::string& packagestream)
{
    auto pos = packagestream.find(SEP);
    if(pos == std::string::npos) return std::string();

    // 获取长度
    std::string lenstr = packagestream.substr(0, pos);
    int len = std::stoi(lenstr);

    // 判断信息是否完整   len\r\nJsonStr\r\n
    int totalsize = lenstr.size() + SEP.size() + len + SEP.size();
    if(packagestream.size() < totalsize) return std::string();

    std::string jsonstr = packagestream.substr(pos + SEP.size(), len);
    return jsonstr;
}

在这里主要就设计字符串的分割操作,理解了也是很简单的,现在我们只需要将该字符串解析为 Json 对象就好啦:

jsonstr = Decode(jsonstr);
Json::Reader().parse(jsonstr, root);

Person p1(root);

我们在前面完成的 Person 构造函数也发挥了作用!


总结

 在这篇文章中我们自己实现了对数据的序列化和反序列化,并且简单构造了在应用层的协议,希望大家有所收获!

WebFlux和WebClient是Spring框架中的非阻塞Web客户端库,它们通常用于构建高性能、异步的网络应用程序。当涉及到SOAP(简单对象访问协议)这种基于XML的通信时,由于其复杂性和对结构化的请求和响应要求,自定义序列化变得至关重要。 在WebClient中,你可以通过以下几个步骤来处理SOAP协议自定义序列化: 1. **添加依赖**:首先需要添加支持SOAP的第三方库,如`spring-ws`或`cxf-reactive`。 ```groovy dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' implementation 'org.springframework.ws:spring-ws-starter' } ``` 2. **创建SoapMessageConverter**:你需要创建一个自定义的`MessageConverter`,它将SOAP消息转换为你所需的对象类型。例如,可以使用`XStream`或`JAXB`(Java Architecture for XML Binding)进行XML到Java对象的映射。 ```java @Bean public MessageConverter soapMessageConverter() { XStream xstream = new XStream(); // 配置XStream以解析SOAP消息 return new MappingJackson2XmlHttpMessageConverter(xstream); } ``` 3. **配置WebClient**:在WebClient的设置中,应用自定义的`MessageConverter`。 ```java @Bean public WebClient webClient(BeanFactory beanFactory) { List<HttpMessageConverter<?>> converters = new ArrayList<>(); converters.add(soapMessageConverter()); converters.addAll(beanFactory.getBean("httpMessageConverters", List.class)); return WebClient.builder() .messageConverters(converters) .build(); } ``` 4. **发送SOAP请求**:现在你可以使用WebClient发送SOAP请求了,比如: ```java Mono<ServerResponse> response = WebClient.create("https://your-soap-api.com") .retrieve() .bodyToMono(SoapResponse.class); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值