很开心,上半年发布的spring boot 2中,默认的web 容器是netty ,这说明“反应式” 容器已是大势所趋,不管是go 语言的协从线程,仍是java 基于reactor 线程模型,都是基于事件编程实现高并发的实例。这周开始我会讲关于NiO的一切,底层原理是什么,应用架构有哪些,如何利用其优点构建高性能服务器,欢迎关注。java
在介绍NIO以前有必要了解下TCP协议,由于目前多数应用都是给予应用层进行操做,致使隐藏了大量的网路细节,知道这些细节以及原理对咱们的问题排查颇有益处。react
TCP 是一种面向链接的协议,它给用户进程提供可靠的全双工的字节流。确保数据包的可靠,有序,以及支持流量控制。关于TCP 为什么要作这些,咱们从如下几个方面入手:web
IP网络层为什么不保证数据包的可靠性算法
TCP协议如何保证包可达、有序spring
TCP协议如何支持流量控制编程
TCP几种状态以及应用缓存
咱们先看下OSI的网络分层,在如下分层中,TCP 位于传输层,它保证的是协议的可靠性和连续性。具体的收发报是有底层的链路层以及物理层所决定的,因此TCP 所作的工做,也是基于底层的优化和改进。服务器
客户端与服务器之间的通讯使用应用协议,传输层的通讯采用TCP协议,而TCP 协议又采用了更低的一层IP协议,IP则使用某种形式的数据链路层通讯。网络
咱们知道网络的中的数据,最终经过多个路由器链接传送的。最底层的以太网协议规定了电子信号如何组成数据包,解决了局域网的点对点通讯问题,但没法解决多个局域网的的互通问题。架构
而网络层采用的IP协议,就是定义了一套本身的地址规则,主要解决寻址和路由的功能,根据对方的IP地址,寻找最佳路径传输信息。局域网经过路由器链接,路由器基于IP协议,指导数据包向某个路由借口转发。但IP协议不保证包必定到达以及完整性,特别是网络拥堵的时候,会丢弃一些数据包,保证数据的传送效率。
而保证数据包的完整、有序以及可靠,这就是TCP 协议要来作的事情了。
不少网络有一个最大传送单元,它是链路层中的网络对数据帧的一个限制,以以太网为例,MTU为1500个字节。一个IP数据报在以太网中 传输,若是它的长度大于该MTU值,就要进行分片传输,使得每片数据报的长度小于MTU。
另一个数据包还包含头信息,除了本身的Tcp包头,还有IP 头信息和以太网头信息。IP 数据包在以太网数据包的负载里面,最少须要20字节,因此 IP 数据包的负载最多为1480字节。
那么tcp的一个包大小是多少呐?
咱们须要机遇MSS这个值来肯定,MSS是TCP里的一个概念(首部的选项字段中)。MSS是TCP数据包每次可以传输的最大数据分段,TCP报文段的长度大于MSS时,要进行分段传输。 若是不设置,则MSS的默认值就为536个字节 。也就是说一个tcp包的在500字节左右。
上述也说了,底层的路由转发包,并不保证包的可靠性以及有序性。
首先为了保证包的完整性,TCP 会基于MSS 为大于 MSS的包进行分包处理,默认MSS大小为563byte,其大小小于MUT,以防止在网络层被分片处理。
其次增长SEQ和ACK,同时采用超时重发的机制来保证包的可靠性。
为了保证有序性,TCP 为每一个包编配一个Sequence number ,简称 SEQ 。以便接收的一方按照顺序还原。万一发生丢包,也能够知道丢失的是哪个包。通常第一个包的编号是一个随机数,也能够从1开始。
那么有编号了,如何确保包必定到达?
基于ACK 进行确认。对于接收方来讲,每次接受一个包必须返回ack信息,发送端从而确认这个包已经传送到。另外,接收方要对每一条报文作校验。若是校验发现出错,则不发送确认报文,从而触发发送方超时重传。
ACK 包含如下信息:
期待要收到下一个数据包的编号 next SEQ
接收方的接收窗口的剩余容量
咱们采用wiershark抓包一个oschina的包看下三次握手的数据。
个人本机ip:192.168.1.103 oschinaIp:116.211.174.177 三次握手过程: 1.me->osChina:syn=1 seq=x ack=0 2.osChina->me:syn=1 seq=y ack=x+1 3.me->osChina:seq=x+1 ack=y+1
一、me->osChina:syn=1 seq=0 ack=0
二、osChina->me:syn=1 seq=0 ack=0+1
三、me->osChina:seq=0+1 ack=0+1
对比一下三次握手的过程。
咱们知道网络极其不稳定,数据包即使增长了SEQ和ACK,可以保证其有序性,但依然保证丢包或者超时的问题。若是发送端发送数据,或者接收端回复ACK的消息在网络中丢失或者超时怎么处理?
RTO ,超时重传时间。要知道包是否出现超时,须要有一个评估方式,而RTT是对一个给定链接的往返时间的测量。因为网络流量的变化,这个时间会相应地发生改变,TCP须要跟踪这些变化并动态调整超时时间RTO。
发送方若是必定时间内没收到报文的ACK,就认为该报文丢失在网络中了,自动重发该报文。这种机制称之为超时重传。
在这期间,若是接收端的消息,因为丢失,接收端没有收到ack 消息,发送端会向接收端重发这个包。若是由于超时缘由,发送端在超时定时器以后收到了这个包的ack 信息,并且发送端已经重复发送了这个消息,此时发送端不会处理,直接丢弃该ack 。而接收端接收到了以后会再次回复ack 信息。
上述中咱们知道了TCP协议能够保证数据的可靠性,可是也得兼顾效率。兼顾效率的话须要考虑如下三个方面:
支持批量发包
可以基于网络的情况,支持拥堵控制
可以了解接收端的情况,防止接收端处理不过来
基于以上三个需求,作了如下处理。
若是TCP 中的包,都须要发送一个确认一个的话,效率过低了,单次发送和确认一个包,虽然保证了可靠性,但没法保证其效率。此时须要一个批量发送和确认的方式,这就是滑动窗口所作的事情。
发送滑动窗口:
发送窗口从左向右移动在这个发送窗口以前的数据必然是已经发送并且获得接收方确认的数据落在发送窗口以内的数据是发送方能够发送的数据在发送窗口以后的数据是不能发送的数据。
若是发生超时或者丢失现象。那么有两种解决方案:
一、回退N,丢失的包号以后全部包都重发二、选择重传ARQ,只发丢失的,避免重复的(效率高,防止发送重复的)
滑动窗口还有一个做用是让发送端知道接收端的处理情况。假设TCP接收方的缓存已经满了,没法处理更多的,而发送方并不知道,每次会给对方告知当前滑动窗口的大小值 ,此时发送端就不会再发送数据了。
接收方接收到数据一样立刻发送确认,可是同时对发送方宣布窗口大小为0。这样发送方就暂时不会发送数据。
报文到达时不立刻发送确认,直到缓存有足够的空间。这样就能够避免发送方滑动窗口。可是这也存在一个问题,接收方延迟发送确认的时间不该该超过超时时间,若是过长会致使发送方误觉得数据丢失从新发送数据。
咱们知道网络情况有好友坏,好的时候,能够多发些包,坏的时候,若是发包速率不变的话,除了会加剧网路负担之外,还会形成包的过多丢失,除非更多的超时重发,这无疑识下降了通讯效率。
基于此,TCP通讯双方维护一个叫作拥塞窗口(cwnd,congesion window)的值,这个值取决于网络中的拥塞率,发送方的发送窗口的值就等于拥塞窗口的大小。只要网络中没有出现拥塞,拥塞窗口的值就能够增大一些,这样发送方能够发送到网络中的数据就多一些。反之,拥塞窗口的值就减少,从而避免加重网络的拥塞率。
TCP目前拥塞控制主要有如下4种算法:
慢启动
拥塞避免
快速重传
快恢复
具体的算法实现方式就再也不介绍了,大概实现的功能就是,基于当前的网络情况,找到一个合适的发送速率,防止给网络形成过大的负担。好比说慢启动,就是开始的时候,发送得较慢,而后根据丢包的状况,调整速率:若是不丢包,就加快发送速度;若是丢包,就下降发送速度。
了解TCP的都知道,TCP 创建链接的时候,有三次握手,断开连接的时候又四次握手交互。那么其中的状态是有哪些?
上面的图看着是否是太乱记不住,咱们看看下面这张梳理一下,看看具体应用状态。
从上面能够看到,链接创建成功的时候,其状态是ESTABLISHED 的。当接受端的状态为SYN—RECV的时候,表示接受端,已经回复第二次握手信息了,等待发送端再次确认。若是网络中遭受到大量的SYN 攻击,会存在大量的SYN_RECV 状态。此时能够定位这些问题IP ,经过防火墙过滤就能解决大量的假链接问题。
在网络中,某一端主动关闭而没有经过四次握手关闭,此时tcp已经创建的通道是否还在,多久会关闭?此时的TCP 状态为TIME_WAIT ,能够想象,现实中常常出现这种情况,多数的关闭链接都是主动关闭而非经过协商通讯关闭。那么此时关闭,若果再重连还能重连上以前的tcp 通道么,仍是须要重现建立。
任何TCP实现必须为MSL选择一个值,默认是2分钟或者30秒,TIME_WAIT默认是2倍的MSL,持续时间在1-4分钟之间。MSL是IP数据包能在网络中存活的最长时间。
TIME_WAIT 存在的两个理由: 一、可靠的实现TCP全双工链接的终止 二、容许老的重复分节在网络中消失
TCP必须防止某个链接的老的重复分组在该链接已经终止后再现,从而被误解成属于同一链接的化身,有time_wait 足够长,是2倍的MSL的,那么足够让某个方向上的分组最多存活MSL秒就被丢弃。
从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是 2*MSL(RFC793定义了MSL为2分钟,Linux设置成了30s),如此超过了这个时间,当前的tcp通道就会被定义为关闭。
更多架构知识,欢迎关注个人公众号,大码候(cool_wier)