目录
1.应用层协议
应用层协议是什么?
当我们使用通信软件发送信息时,我们不仅仅只是把我们输入的信息发送给对方了,发送的信息中还包括发送的时间,发送消息人的昵称等等...... 也就是说,我们发送的消息是由不同类型的数据构成的 ,能够装不同类型的数据,在C/C++中只能是使用结构体或者类来表示。
当发送方发送了该结构体类型的数据之后,接收方接收到该数据之后,还得能够正确地解析出来,这个时候,接收方也要拥有相同类型的结构体,这就是应用层的协议。比如上面的message结构体就是我们定义的应用层的协议。
所以:定义应用层协议就是定制通信双方都能认识的,符合通信和业务需求的结构化数据;在代码实现方面其实就是struct or class。
2.序列化和反序列化
平时,开发人员开发的一个个基于CS模式(客户端服务器模式)的网络程序都是在应用层;客户端和服务器端应用层程序之间要进行通信是通过调用 socket 编程接口来实现的,但是socket编程接口在读写数据时都是通过字符串的方式 来进行收发的,但是通信双方要发送的数据可不仅仅局限于字符串类型的数据,可能有整形数据、浮点型数据、字符...... 在实际应用中,通信双方传输的往往是结构化的数据,这个时候该怎么办呢?
这个时候发送方就需要将结构化的数据转化成字符串类型的数据 ,这个过程就叫做序列化 ;序列化之后的字符串类型的数据被接收方接收到之后,并不能直接使用,还需要将字符串类型的数据转化为结构化的数据 ,这个过程就叫做反序列化。
什么是序列化?序列化就是把消息由多变一,方便进行网络传输。
什么是反序列化?反序列化就是把消息由一变多,方便上层处理。
其实进行序列化和反序列化还有一个重要的原因:
应用层的数据是要交给传输层进行传输的,如果传输层使用的是tcp协议 的话,tcp是面向字节流的协议,只关心一个个的字节,发送数据时发送的也是一个个的字节。所以接收方接收到的数据也是一个个的字节,但是各个平台对于结构化数据的处理是不一致的(内存对齐的规则存在差异),这个时候,即便通信双方定义了相同的结构体类型,但是由于平台的差异可能会导致数据不一致。但是序列化之后的数据不是结构体类型的,而是字符串类型的,也就避免了对结构化数据处理方式不一致而导致的数据不一致问题。
3.流式数据的处理
存在的问题
铺垫 :tcp协议是输入传输层的协议,传输层是在操作系统内核实现的,操作系统会为传输层的协议维护两个缓冲区,一个是接收缓冲区,一个是发送缓冲区。当我们调用write、send接口的时候,其实就是向指定的数据拷贝到tcp的发送缓冲区,由操作系统自己判断什么时候将发送缓冲区中的内容发送给对方。当我们调用read、recv接口时,其实就是将tcp接收缓冲区中的内容拷贝到指定的空间中。在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,这就是tcp支持全双工的原因。
当数据在发送方的应用层 进行序列化之后,需要调用系统调用接口将序列化的数据拷贝到tcp协议的发送缓冲区,如果传输层使用的是面向字节流的tcp协议,收发数据时,只关心一个个的字节。既然发送的是字节流数据,那么对方收到的也就是字节流数据,那么问题来了,接收方如何将字节流数据完整的从接收缓冲区中读取出来呢?
传输层的协议只保证数据正确的传输,也就是发送的数据会正确的发送给对方,但并不保证应用层如何正确地读取数据,这是应用层要做的事情,应用层是用户空间,所以这个问题需要用户自己来解决(这里的用户是指开发人员),要想解决这个问题,我们可以为自定义的结构化数据添加协议报头,来保证读取数据时的准确性。
如何制定协议报头呢?
如果我们知道序列化之后数据的长度,我们不就可以判断从接收缓冲区中读取的数据是否完整了吗?每次读取的时候,读取指定长度的数据,该指定长度的数据就是一个个完整的报文。所以我们可以将协议定义成这个样子。
其中\r\n是分隔符,作为报头和下一个完整报文之间的分隔符。
有了报文的格式之后,进行数据的收发时,在应用层又需要多做两步工作。
**添加报头:**发送数据时,将有效载荷添加协议报头,打包成指定格式的报文。
**提取有效载荷:**接收数据时,根据报头的内容,将报文中的有效载荷正确的解析出来。
添加报头:
我们先计算出有效载荷的长度(真正要发送的数据的长度),然后按照我们定义的报文格式组装出要发送的报文,然后再将组装好的报文进行发送。添加报头代码如下:
提取有效载荷:
先提取出有效载荷的长度,然后计算出报文的总长度,最后提取出有效载荷。分割符的长度是已知的,所以这一点并不难实现,需要注意的是,如果提取到的不是完整的报文,我们将返回空串,这个时候会继续从接收缓冲区中读取内容拼接到 inbuffer 的后面,所以,我们能保证每次读取上来的都是一个个完整的有效载荷。
4.Jsoncpp的介绍和使用
Jsoncpp的介绍
序列化和反序列化操作在网络程序开发中是经常要使用的,如果每次编写网络程序都要自定义协议,然后进行序列化和反序列化操作,这是一件比较繁杂的事情,于是,有大佬写了一个专门用于序列化和反序列化的库,这个库就是Jsoncpp库。
什么是Jsoncpp?
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串 以及从字符串反序列化为 C++ 数据结构的功能。
Jsoncpp的使用
序列化操作:
假如我们有一个学生结构体,具有name、age、weight属性,我们要将结构化的数据转化为字符串类型的数据,需要进行以下几步操作:
1、定义一个Json::Value类型的对象。
2、根据结构体中的字段依次填充Json::Valeu对象的字段。
3、定义Json::FastWriter类型的对象或Json::StyledWriter类型的对象。
4、调用该对象的write方法。
这样一来,就可以将Json::Value类型的对象中被填充的字段转化为字符串了。
注意:Json::FastWriter类型的对象和Json::StyledWriter类型的对象的区别?
两种类型的对象都可以进行序列化,只不过序列化之后的格式有所不同。Json::FastWriter类型的对象转化出的字符串是没有换行的,就是简单的一串字符串。Json::StyledWriter类型的对象转化出的字符串是有换行的。
反序列化操作:
json::string是一个Json格式的字符串,我们要将该字符串中的内容进行发序列化成结构化的数据,我们可以通过以下几步进行:
1、定义一个Json::Value类型的对象和一个Json::Reader类型的对象。
2、调用Json::Reader对象的parse方法,将json格式的字符串转化到Json::Value类型的对象中。
3、从Json::Value类型的对象中提取出结构体的字段,然后赋值到结构体类型对象所对应的字段中。
这样一来就完成了反序列化工作。