网络通信中字节流存在的问题,tcp协议特点,自定义协议(引入+介绍,序列化反序列化介绍,实现思路)

目录

字节流存在的问题

介绍

tcp协议特点介绍 

tcp的收发自主权

tcp的全双工通信

自定义协议

引入

计算器

字符串形式

结构体形式

思考

序列化和反序列化

引入

介绍

计算器代码思路 


字节流存在的问题

介绍

我们前面写的代码都是以字符串形式收发的,虽然暂且没有出现什么问题,但不代表他没问题

  • 我们目前无法保证在字节流中,每次都可以读取出一份完整的数据
  • 之前可以做到只是因为数据量小+网络良好
  • 当数据量很大/网络有波动时,tcp协议会出现数据分片、拥塞、丢包等情况,导致接收方收到的消息可能只有一小部分/多份混在一块
  • 就像在普通的二进制文件中,我们无法知道一句的结尾在哪里,我们也无法区分网络数据

tcp协议特点介绍 

tcp的收发自主权

  • 当然,上面指的问题并不是出在传输层,因为传输层是写在os里的
  • 就像我们不用关心在普通文件的io里,数据何时被os刷新到磁盘上一样,它一定会在合适的时间帮我们处理的
  • 换句话说,就像os一样处理文件数据一样,tcp协议具有网络数据的收发自主权,我们不需要关心它何时收发数据
  • 那么,写的时候就没有任何顾虑了,因为有tcp协议帮我们兜底
  • 但是,读取内容的时候,需要额外处理

tcp的全双工通信

之前在进程通信中介绍过的管道文件,它属于单向通信,只能一方收,一方发,且不能同时做

  • 因为a和b共用一个缓冲区,a使用读方式操作这个缓冲区,b使用写方式操作它
  • 在一方进行文件操作时,另一方只能等着

而tcp协议不一样,它是全双工的

  • 因为tcp协议会定义两个缓冲区,分别用于发送和接收
  • 这样双方都可以同时进行发送和接收
  • 因为此时有四个缓冲区,即使同时rw也都是操作的不同的缓冲区,所以不会有影响:
  • 注意,我们之前用于tcp协议的收发数据函数是read和write
  • 但他们其实并不涉及网络,而只是一种拷贝函数 -- 将用户数据拷贝到内核缓冲区里,将内核数据拷贝到用户缓冲区(也就是我们定义的数组)里
  • 就像上面介绍的一样,tcp协议层数据的收发由tcp协议自己决定 -- 决定什么时候发,发什么,出错了怎么办
  • 所以tcp协议属于传输控制协议
  • 而read和write只需要等待内容的到来

自定义协议

引入

  • 就像上面介绍的那样,read不可以直接读取tcp协议传输过来的数据,而是需要处理,因为我们很可能无法正确拿到一份完整的数据
  • 并且,很显然tcp不具备这个功能,它只是用来保证数据的可靠性的,而不是正确性,如何解析数据是应用层协议该考虑的
  • 所以,我们就需要自定义/使用现有的应用层协议:

其实,前面我们写的都属于自定义协议,只不过定制的只是其中一小部分

  • 比如:我们规定好双方都发字符串,规定服务端是echo服务端
  • 或者规定好客户端发的是命令,服务端将执行命令
  • 或是客户端发的是单词,服务端翻译这个单词

我们以计算器为例,来看看我们该如何自定义协议

计算器

我们需要在cs两端分别传递运算式 和 运算结果+运算是否出错

字符串形式

  • 如果使用纯字符串传递,我们需要手动将数据剥离出来
  • 比如: 运算式是"1+1" ,需要被剥离成1,1,+
  • 计算结果如果是"2,0" (2表示运算结果,0表示运算没有出错)
  • 但这样还是会出现上面介绍的问题,我们无法确定一次性读上来的就是一份完整的数据,需要进行字符串分析

结构体形式

  • 如果使用结构体,确实可以很轻松地将数据拿到手
  • 但是在不同的编译器下,结构体的大小是不一定的
  • 还记得结构体的内存对齐吗?
  • 不同的系统(比如linux和windows) / 不同的编译器有不同的对齐方式,那么一旦结构体中字段很多, 很可能导致结构体大小不同,同一个字段在结构体内的偏移量不同
  • 那么字段就配不上了,客户端在提取时,很可能读出来的字段是空白的/只有一部分/多读了
  • 而且,一旦同时有多个请求,服务端会一次拿到多个结构体,如何确定一个完整的结构体的结尾处在哪呢?(因为结构体也是以二进制方式传递的)

思考

所以我们需要考虑其他方法来发送

  • 定义结构体的方式很好,只要双方建立起对结构体中字段的约定,使用结构体就可以大大降低我们的通信成本
  • 只是,不能直接以结构体对象的方式发送
  • 我们可以将两种方式结合在一起使用

结构体是一定要存在的,因为一般我们进行网络通信,都是要传输非常多的结构的

  • 比如这里的计算器要传输运算数据,运算符,运算结果,运算状态等等
  • 比如聊天时的聊天信息,用户名称,头像,发送时间等等
  • 最好的方式就是将他们打包起来

但是我们又不能直接以结构体对象的形式发送给对方,因为双方对于结构体的字段理解虽然相同,但它们不一定处于相同的位置上

  • 所以,可以将结构体转换为字符串的形式发送给对方(虽然要进行解析,但总比解决结构体的内存对齐问题要容易的多)
  • 然后对方将数据赋值给自己的结构体,结构体会自动将数据填充到对应顺序的字段上

就像下图:

序列化和反序列化

引入

其实

  • 定义结构体,也就是定制了协议
  • 上面的将结构体->字符串的形式,就是序列化
  • 将字符串->结构体的形式,就是反序列化

为什么要进行序列化,反序列化呢?

  • 因为以字符串的形式更适合网络通信
  • 但我们又不想手动剥离数据

所以,实际上,这样也就形成了分层(两层)

  • 第一层只需要定制协议(也就是结构体)
  • 第二层只需要保证这个字符串(或者说二进制流)可以被完整发送和接收即可

介绍

序列化

  • 将数据结构或对象转换为字节流的过程

  • 在序列化过程中,对象的状态被保存为一系列字节,可以是二进制格式,也可以是文本格式,例如 JSON 或 XML

  • 序列化后的数据可以在网络上传输或保存到文件中

反序列化

  • 将字节流重新转换为原始数据结构或对象的过程

  • 在反序列化过程中,之前序列化的字节流被解析,并根据其中的数据创建原始的数据结构或对象

计算器代码思路 

我们尝试在网络计算器中运用序列化和反序列化

  • 定义出请求和响应两个结构体(定制协议)
  • 当客户端产生运算请求后,将[赋值好的结构体]经过序列化->字符串,传递给服务端
  • 服务端收到报文后,先解析报文(反序列化->请求结构体),然后拿到[操作数和运算符]进行计算,然后将数据赋值给[响应结构体]
  • 然后将该结构体序列化->字符串,传回给客户端
  • 客户端收到响应报文后,经过解析(反序列化->响应结构体),然后拿到[运算结果和错误码],向用户进行数据显示

而其中的细节 -- 如何区分完整的报文,扩展协议,如何用代码实现,将在下篇博客中介绍

 

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值