Socket IO与NIO(五)

数据传输稳定性优化

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

消息粘包

  • 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。若是有再重复上面的流程。高并发

相关文章
相关标签/搜索