【网络】序列化和反序列化

目录

为什么要进行序列化

进一步理解TCP通信

封装socket套接字

序列化和反序列化

解决粘报问题


在之前的博客中,我们已经是成功的利用UDP和TCP进行网络通信,但是不知道你发现没有,我们在用TCP时,发送数据和接收数据用的是write/read、send/recv这类接口,这类接口处理的数据都是字符串,那如果我们要传输一些"结构化的数据"怎么办呢?我们一般想到的就是把结构化的数据变成一个字符串,这样可以方便网络的发送
这其实就是序列化和反序列化:发送数据时将这个结构体按照一个规则转换成字符串,收到数据时再按相同的规则转化回结构体

为什么要进行序列化

上面说把结构化的数据变成一个字符串,这样可以方便网络的发送并且发送结构化的数据有哪些弊端呢?我们可以谈一下

首先字符串是计算机网络中最基本的数据类型之一,几乎所有的协议都支持字符串的传输;

并且不同平台内存对齐的规则是不同的,大小端也是不同的,如果传结构化的数据会存在很严重的跨平台问题,况且你没有办法保证你读到的数据是完整的,但是如果你转化成字符串,在字符串中加上特殊标记,你就可以完整的读到一个数据,我们接下来定协议的时候会有这个工作;

如果结构体中新增成员呢?我们要在解决上面问题后再解决新的问题,这无疑是很困难的,但是我们要转化成字符串就可以将序列化的过程变得更加通用一些。

并且直接发送结构化的数据我们在读取的时候无法保证读全 ,这就需要我们后面自己定协议来保证我们处理的是一个完整的报文,也就是解决tcp的粘报问题(tcp面向字节流,我们读取时可能读到半个,也可能读到一个半)

进一步理解TCP通信

下面我们再来更深入的理解一下TCP通信的本质,我们知道TCP通信前要建立连接,并且一个连接要有两个缓冲区(发送和接收),而我们调用的write/send函数本质就是一个拷贝函数,它只是把用户缓存空间中的内容拷贝到TCP维护的缓冲区中(这个就跟写入磁盘文件是一样的:我们做的不过是将数据拷贝到内核维护的文件缓冲区中,具体文件缓冲区中的内容何时刷新到磁盘中由OS说了算),跟文件操作一样,在TCP发送缓冲区中的内容什么时候发给接收方,如何发,出错了怎么办,这完全是由TCP协议决定的,要不说TCP全称是Transmission Control Protocol(传输控制协议),它就是用来控制传输的

所以网络通信本质上是双方操作系统在进行真正的网络通信

这时候就容易解释为什么调read的进程有时候会阻塞了,因为接收缓冲区中根本就没有数据

所以说TCP发送数据的本质就是将自己的发送缓冲区中的内容拷贝到对方接收缓冲区中

通信的本质就是拷贝

TCP支持全双工也是因为client和server各自有一对(发送和接收)缓冲区

所以站在用户的角度只是去拷贝,不发送,站在TCP角度,它需要处理的只是一串字节流

封装socket套接字

下面为了方便未来使用socket套接字通信,我们来封装一下套接字,用到的是模板方法模式,就是说我们使用socket时都是有固定模板的(UDP就是socket,bind;TCP就是socket,bind,listen,accept),每个模板中会有具体方法的实现(UDP作为子类要实现父类的纯虚函数),大致的理解就是这样

我们可以先创建一个Socket基类,定义出需要的各种纯虚函数,然后让TcpSocket派生类去继承Socket基类并完成对于纯虚函数的重写;并且创建套接字也是有模板的,这个可以在基类实现:


下面我们就可以直接写服务器端了,我们只需要使用前面封装好的socket即可

我们针对这种长服务可以采用多线程的方式,线程要执行什么工作我们可以放到main.cc中,因为线程不止需要收发数据,还需要对于结构化的数据进行序列化和解决上面提到的粘报问题

序列化和反序列化

这个过程其实就是将结构化的数据转化成字符串,当然我们可以手动的去转化,我们也可以使用jsoncpp这个开源的库,具体的过程可以参考下面的博客:

jsoncpp的简单使用

解决粘报问题

我们采取的策略是自定义协议,我们将结构化的数据转化为字符串之后还需要再添加报头后发送,这才能保证服务端可以拆出完整报文

我们可以假设添加完报头后数据是这样的:

复制代码
"len"\r\n"{json ptr}"\r\n

len是{json ptr}的长度,那么此时不管在服务端读到的是什么样的数据,我们都可以去先找到\r\n,找到之后,就可以把len提取出来,然后就知道完整的jsonptr的位置和长度了,这样就可以提取到完整的报文了,然后服务端再对完整报文进行反序列化,就可以得到结构化的数据了,之后我们就可以进行一系列的运算,然后把运算结果序列化,添加报头再发送给客户端就可以了
我们之前说OSI制定了七层模型,但是实际只分为五层(物理,数据链路,网络,传输,应用),那是因为应用层是用户自己写的,其实应用层还可以分为三层(会话,表示,应用)

会话层负责建立和断开通信连接,其实不就是我们封装的socket套接字吗

表示层负责设备固有数据格式和网络标准数据格式的转换,不就是我们的序列化反序列化过程吗

应用层是针对特定应用的协议,不就是我们写的计算器服务吗

以上的代码:

code for cal_server

相关推荐
柯一梦3 分钟前
STL2---深入探索vector的实现
c++
消失的旧时光-19433 分钟前
Linux 编辑器入门:nano 与 vim 的区别与选择指南
linux·运维·服务器
MSTcheng.11 分钟前
【C++】C++11新特性(二)
java·开发语言·c++·c++11
晓131313 分钟前
第七章 【C语言篇:文件】 文件全面解析
linux·c语言·开发语言
愚者游世13 分钟前
Delegating Constructor(委托构造函数)各版本异同
开发语言·c++·程序人生·面试·改行学it
小镇敲码人15 分钟前
探索华为CANN框架中的ACL仓库
c++·python·华为·acl·cann
莽撞的大地瓜21 分钟前
洞察,始于一目了然——让舆情数据自己“说话”
大数据·网络·数据分析
唐装鼠22 分钟前
Linux 下 malloc 内存分配机制详解
linux·malloc
予枫的编程笔记22 分钟前
【Linux入门篇】Linux运维必学:Vim核心操作详解,告别编辑器依赖
linux·人工智能·linux运维·vim操作教程·程序员工具·编辑器技巧·新手学vim
17(无规则自律)35 分钟前
深入浅出 Linux 内核模块,写一个内核版的 Hello World
linux·arm开发·嵌入式硬件