博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Socket IO与NIO(五)
阅读量:6972 次
发布时间:2019-06-27

本文共 5281 字,大约阅读时间需要 17 分钟。

数据传输稳定性优化

之前所有的操作都是基于字符串,我们发送字符串的时候带有结束符,接受的时候也是读取结束符作为他的分割,之前的操作并没有严格的去校验 每一个字节并且得到他的结束符,然后进行分割,而是直接读取到结束符为止。在这样的情况下,我们会出现一系列其他意外的一些问题。虽然 说读取效率更高了,因为咋们一次性把所有的东西都读取出来了,但是他又带来了数据稳定性上的问题。什么问题呢?假如说客户端发送一条数据 过来,那么服务器端收到这条数据做出真确的行为。但是如果客户端发送了两条数据,那有可能在服务器端把他当做一条数据进行接受了。 如果我们按照之前的规则,读取每一个字节并且判断每一个字符的一个结束符是否为换行符的话,那么这样的消耗是非常高的,因为这需要一个一个 字符进行校验,当我们发送一个大文件的时候或者大批量数据的时候,这消耗是服务器无法承担的。

消息粘包

  • TCP本质上并不会发生数据层面的粘包。TCP底层是分包机制,一个包一个包的发送,这样的情况下数据并不会发生粘包。我们说的粘包不是TCP层 面的粘包,而是业务层面的粘包。
  • TCP的发送方与接收方一定会确保数据是以一种有序的方式到达客户端。
  • TCP是会确保数据包的完整性。
  • UDP是不保证消息完整性的,所以UDP往往发生丢包等情况。
  • TCP数据传输具有:顺序性、完整性。
  • 在常规所说的Socket“粘包”,并非数据传输层面的粘包。
  • “粘包”是数据处理的逻辑层面上发生的粘包。
  • 这里所说的“粘包”:包含TCP、UDP甚至其他任意的数据流交互方案。
  • Mina、Netty等框架从根本来说也是为了解决粘包而设计的高并发库,还有调度上的优化。

消息粘包图

我们在逻辑层面发送了M1 M2 M3 3条数据,理想的数据接受情况是M1 M2 M3。但实际接受情况有可能会是M1M2同时到达,并且别同时接受,之后 才接受M3,这是M1M2就是所谓的消息粘包。

消息不完整

  • 从数据的传输层面来讲TCP也不会发生数据丢失不全等情况。从传输层面来讲一定可以确保数据发送过去并被接受,这是传输层面的保证。TCP一旦 出现传输层面的丢包或是粘包,那这个连接一定出现了异常,一定无法再进行后面的数据传输,这个时候Socket一定是处于断开的状态。
  • 一旦出现数据丢失不求等情况一定是TCP停止运行终止之时。
  • “数据不完整”依然针对的是数据的逻辑接受层面。
  • 在物理传输层面来讲数据一定是能安全的完整的送达另一端。
  • 但另一端可能缓冲区不够或者数据处理上不够完整导致数据只能读取一部分数据。
  • 这种情况称为“数据不完整”“数据丢包”等。意思是当你发送一串比较长的消息的时候,那么这个消息在你发送的过程当中可能会被TCP分成 一个一个小包,小包发送到服务器的时候,服务器收到这个小包发现前面的一部分已经足够组装一个大包的时候,他会完成一个组装,并把 这个包push到业务上层,这个时候业务上层就说有消息来了,可以通过channel读取数据到Buffer当中,这个时候就开始读取了,但是读取 过程当中,并不知道这一串大的消息究竟有多长,我仅仅只是说读到他返回为0(就是读不到数据)的时候,我们就进行后面的流程了,有可能 会出现我们仅仅只接受到了大消息的一半的情况,也即是把一个大的消息当成了2个子消息来处理了,这样的情况就是消息不完整,当然这是 接受层面上的问题。 还有一种情况是客户端发送一个大的数据过去,这个大数据在服务器的网卡层面已经完全接受到了,并且在系统底层的缓冲区里面缓冲下来了 这个时候我们需要把它读取到我们的Buffer当中,但是Buffer仅仅只有100个字节,而这个大数据有200个字节的情况下,也只能读取前100个 字节,后面还有100个字节没有读,这个时候我认为说已经满了,他就拿去用了,这时有可能也会出现用上面的问题,这也是消息不完整的情 况。

*** 如何有序的混传数据 我们消息数据可以无限的发送,不用去管它底层的传输,但是我们接受的时候要能够保证,咋们消息是怎么发送过去的,就要怎么样接受回来,这 才能保证消息传输是有意义的。

  • 数据传输加上开始结束标记。开始和结束可以同时加,也可以只加结束,或者说只加开始,那我们所谓的换行符也就是结束标记,如果说我们认为 所有的数据都是具备一个换行符的,那我们发送这条数据时候,当遇到换行符的时候,我们就认为说这条消息已经结束,再开始接受后面的 消息。一旦后面的消息也具备换行符,那么后面的消息也代表一条独立的消息,可以就是所谓的可以直接发送一行,然后读取一行。
  • 数据传输使用固定头部的方案。意思是可以在要发送消息的前面加上一个固定的特殊字符,比如说换行符或AAA或在头部加上描述信息 描述后面 数据的具体内容的信息,确保对内容进行一个分割。
  • 混合方案:固定头部、数据加密、数据描述。

无论是假设头部还是尾部都会影响性能。因为我在接受消息的时候根本不知道消息是否结束了,意味着我要对每一个字节进行校验。这个校验有 可能是我从网卡上面读取一个字节我就进行校验。也可以说我从网卡上面一次性把消息都读取到Buffer当中,然后我再到buffer当中去进行 一个校验。

提倡的是固定头部的方案。

起止符方案

固定头部描述方案

在服务器端我会首先读取前面4个字节,我们可以将它转换成int类型,int值可以存储后面消息体具体的长度。加入int 存储的是100个字节,那么这个时候,我读取的时候直接从channel当中直接读取100个字节到咋们的一个buffer当中,那么我就认为这就是一段 完整的消息了,我就把这100个字节直接转换成String,然后做后续的处理。相对起止符方案他更加优秀,优秀在传输上面更加高效。消息不完整 和粘包都会被避免。

起止标记技术实现

固定头部技术实现

借鉴学习HTTP精髓

  • HTTP如何识别一个请求。在HTTP1及以前,每一次请求他都是一个单独的Socket连接,然后发送数据 传输数据 返回数据,然后再端口Socket。 从HTTP2开始,我们可以去实现它的复用逻辑,也就是可以建立一个Socket,进行很多次的发送和返回。
  • HTTP如何读取请求头,请求头协议是怎样的。
  • HTTP如何接受数据包体。
  • 当数据为文件时,HTTP如何判断文件已接受到底了。

HTTP 1.X

重点在于描述,描述每一个区间的数量,拿到Global Header可以得到一个总的长度,拿到总的长度之后,解析出Packet Header,拿到Packet Header之后可以得到Timeval、Capture length、Packet length,拿到这些长度信息之后,我们就可以读取Packet data。这个地方也就是所谓 的在header里面封装了body部分具体有多长

每个请求头信息使用换行符一行一行的换行,这是一个整体,都是请求头,这个请求头在HTTP里面他们通过16个字节判断请求头究竟有 多长,那当我知道请求头有多长的时候,我会一致性把请求头的信息全部读取出来,形成一个大的字符串,然后根据换行符拆分成各自小的字符串 ,拆分成小的之后,我就能知道你具体的请求信息了,拿到请求信息之后,同时我可以根据前16个字节就能之后后面数据究竟有多长,这些都是具体 的约束和规范。

HTTP 2.X

他有可能会经过一系列的握手说要经常咋们的安全校验,然后进入到咋们的程序层,这个地方HTTP1和HTTP2他们之间的区别 是HTTP2具备一个 一帧一帧分开的概念,这个后面我们会把一个大的数据包拆分成小的数据包进行发送,这种情况就是咋们分包的概念来实现。 每个分包也是很简单的。HTTP1当中一个消息一个发送,他就是一个有头部和有数据的大消息体。而HTTP2当中,他可能会分成,把头部部分分成 HEADER部分,然后再分成DATA部分,两个部分单独的进行发送,并且单独的进行传输,而这样的过程有助于咋们服务器端接受不同的部分或者拒绝 某一个部分。

这是长连接的一种方式。首先我们请求一条消息,我们发送一个请求头到服务器。首先是Request Message,Request Message里面有一个 HEADER frame,这是请求头的信息。服务器接受到了之后说,你这个信息我可以进行响应,这个时候他就回送了一条消息,回送的消息里面也是 包括了头部,然后还有一个具体的body部分,他不是单一body,他是一个复合型的body,也就是DATA frame stream1,stream1我丢过来给你, 后面还有一系列的stream2 stream3....,这就是咋们的一整套流程。这个东西也就实现了咋们的一个长连接,然后你往服务器端说,我现在想要 拿到当前的一个未读消息,那么服务器端这个时候有未读消息,就返回给你。如果说没有 他会等待一下,直到他有未读消息,他会把这个推送给你。 这是可以用来做推送的。你可以对一个连接进行多次请求,多次请求头,第一个请求头想要的是一个主页,第二个请求头想要的是about页面, 那么它也会通过不同的数据返回给你。这一整套流程都是建立在咋们的一个有序的一个有规矩的一个消息的封包上面,也就是咋们具体要去实现的 部分。

你可以通过HTTP2建立一个连接,就可以实现说你可以拿非常多的信息。

HTTP2.X Header 9-byte,在HTTP2上面有一个头部,头部上面有个一个标准的9个字节,前面3个字节也就是24个bit,这24个bit用来 表示咋们的一个长度,也就是最大长度等于2的24次方。之后一个字节用来做type的校验。之后32个bit,前面是一个flags flags是个特殊的标志 位,flags之后的数据用来标识咋们HTTP2的一个特殊的唯一标识。在之后是R R是个boolean值 这也是标志位,R后面是流的基本定义,流的唯一 标识,还是每一帧的数据承载。这就是HTTP2.X的框架。他前面有个东西就是我们说的长度描述,type描述,flags标志位,这3个是我们比较看 中的地方,也是我们需要借鉴的地方,假如我给我的消息,前面3个字节用来标识长度,我们也使用type用来标识是否对传输的数据进行加密,如果 说是加密的或者没加密的,我们再根据状态是否读取他后面的一个或两个字节用来判断咋们具体的加密类型。我们要学习的地方也就是HTTP2.X的 框架概念。

混传数据总结与梳理

构建有序消息体:

  • 数据包分析与特征提取。
  • 数据头部构建。
  • 数据头、数据体接收。

类之间的关系:

  • connector
  • Sender & Receiver
  • 新增的3个类

基于发送的流程来看

SendDispatcher(发送调度着)

Send(发送真实的人)

SendDispatcher(发送调度着)当中有个一个queue队列,然后我们把Packet Put到队列当中,Put进去之后 就会take拿一个Packet出来,拿出来 之后,我们会把Packet当中的数据写入到IoArgs,当把数据写入到IoArgs之后,我们会把这一份IoArgs进行一个注册,那么注册到咋们的Sender, 这个时候会调用咋们sender.sendAsync(args, listener)异步发送的方法,并把IoArgs传递进去,还会传递一个listener的回调,当sender 经过了selector事件机制的回调之后,会判断说这个时候sender可以进行发送数据了,并且这个时候会把咋们IoArgs里面的数据真实的拿去发送。 当他把数据发送好了之后呢,他会进行一个回调,回调回来自然也就回调咋们的listener,回调listener的什么方法呢,就是onCompleted(IoArgs args) 完成的回调,就是说当前的这个IoArgs已经发送成功了。发送成功了 回调回来,自然这个listener是由谁来持有的呢,是由咋们的发送调度着所 持有的(SendDispatcher)。如果说此时,当前的这个Packet还没有完全的发送完成,那么它还会把Packet当中的数据再次的写入到IoArgs里面去 然后进行一遍上面的流程,直到我们Packet被真实的完成了。当然这个地方还涉及到咋们的一个包头和包体的概念。那包头和包体的概念也就是 首先会提取咋们Packet当中的一个数据长度和数据的类型,然后我们会把数据长度和数据类型在第一个包的最前面写入到IoArgs里面去,先把长度 和类型通过Sender发送出去,然后才发送咋们Packet当中的真实的内容,也是一样把真实的内容写入到IoArgs当中,然后在通过Sender发送。当 包头和包体都发送完成之后,他会干一件事情,他会从queue队列当中再拿下一个Packet。如果有再重复上面的流程。

转载于:https://juejin.im/post/5c7c8ea7e51d455835740aa3

你可能感兴趣的文章
PHPGrid 1.4.8 发布,PHP 的 CRUD 框架
查看>>
HNOI 2002 营业额统计(Splay入门)
查看>>
Python面向对象关系
查看>>
OpenCV学习(2)--基本数据结构
查看>>
PCIE错误分析
查看>>
linux服务器开发并发模型
查看>>
YYHS-Floor it(递推+矩阵乘法+快速幂)
查看>>
redis安装
查看>>
da面板修改SSH端口号
查看>>
python基础语法学习
查看>>
nginx+ssl 服务器 双向认证
查看>>
【2018】ios app真机调试到上架App Store完整教程
查看>>
ajax文件上传
查看>>
ztree树形菜单
查看>>
(一)Model的产生及处理
查看>>
A value is trying to be set on a copy of a slice from a DataFrame.
查看>>
leetcode12_C++整数转罗马数字
查看>>
网页自动登录,自动填充表单代码
查看>>
【转】web测试方法总结
查看>>
所有者,群组,其他人
查看>>