Netty理论四:TCP vs UDP
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty理论四:TCP vs UDP相关的知识,希望对你有一定的参考价值。
参考技术A从数据结构上可以看出来,TCP比UDP要复杂的多。
我们上面说tcp是面向连接的,这是啥意思呢,简单的说,tcp要发送数据,首先得先建立连接,而udp不需要,直接发送数据就行了。
TCP是全双工的,即客户端在给服务器端发送信息的同时,服务器端也可以给客户端发送信息。
而半双工的意思是A可以给B发,B也可以给A发,但是A在给B发的时候,B不能给A发,即不同时,为半双工。
为什么采用三次握手?而不是二次?
是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误;比如有以下场景,当客户端发出第一个
连接请求1并没有丢失,而是在某些网络节点长时间滞留了,这时客户端会认为超时,会再次发送连接请求2,服务端在接收到连接请求2以后建立了正常的连接,这时候失效请求1又到达了服务端,服务端会误以为是客户端又发出的一个连接请求,于是会再次建立连接,假定不采用三次握手,那服务端发出确认,新的连接就建立了。但是由于客户端并没有发出新的建立连接的请求,因此客户端不会再新的连接上发送数据,而服务端却以为新的连接已经建立了,在一直等待客户端的数据,由此会导致服务端许多资源的浪费。采用了三次握手后,可以防止这个现象发生,在客户端收到服务端对于自己的无效连接的应答后,并不会向服务端发出确认,服务端由于收不到确认,就可以认为此次连接是无效的。
第二次挥手完成后,客户端到服务端的连接已经释放,B不会再接收数据,A也不会再发送数据。这个时候只是客户端不再发送数据,但是 B 可能还有未发送完的数据,所以需要等待服务端也主动关闭。
为什么是四次?
关闭连接时,当Server收到FIN报文时,很可能并不会立即关闭socket,因为Server端可能还有消息未发出,所有其只能先回复一个ACK报文,告诉Client端,你的关闭请求我收到了;当Server把所有的报文都发送完以后,Server才能给Client端发送FIN报文关闭连接,Client收到后应答ASK。所以需要四次握手
在四次握手后,Server端先进入TIME_WAIT状态,然后过2MS(最大报文生存时间)才能进入CLOSE状态。为啥?
因为网络是不可靠的,客户端在第四次握手的ACK可能会丢失,所以TIME_WAIT状态就是用来重发可能丢失的ACK报文
TCP是顺序性,是通过协议中的序号来保证,每个包都有一个序号ID,
在建立连接的时候会商定起始 序号ID 是什么,然后按照 序号ID 一个个发送。
tcp是通过应答/确认/ACK 以及 重传机制来保证消息可靠传输的。
即在消息包发送后,要进行确认,当然,这个确认不是一个一个来的,而是会确认某个之前的 ID,表示都收到了,这种模式成为累计应答或累计确认。
确认是通过报文头里面的确认序号来保证的。
为了记录所有发送的包和接收的包,TCP 需要在发送端和接收端分别来缓存这些记录,发送端的缓存里是按照包的 ID 一个个排列,根据处理的情况分成四个部分
流量控制指的是发送端不能无限的往接收端发送数据(UDP就可以),为啥呢?
因为在 TCP 里,接收端在发送 ACK 的时候会带上接收端缓冲区的窗口大小,叫 Advertised window,超过这个窗口,接收端就接收不过来了,发送端就不能发送数据了。这个窗口大概等于上面的第二部分加上第三部分,即 发送未确认 + 未发送可发送。
流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。
tcp接收端缓冲区的大小是可以调试的,见 Netty高级功能(五):IoT百万长连接性能调优
TCP通过一个定时器(timer)采样了RTT并计算RTO,但是,**如果网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,然而重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这就导致了恶性循环,最终形成“网络风暴” —— TCP的拥塞控制机制就是用于应对这种情况。 **
拥塞控制的问题,也是通过窗口的大小来控制的,即为了在发送端调节所要发送的数据量,定义了一个“拥塞窗口”(Congestion Window),在发送数据时,将拥塞窗口的大小与接收端ack的窗口大小做比较,取较小者作为发送数据量的上限。
所以拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。
TCP 拥塞控制主要来避免两种现象,包丢失和超时重传,一旦出现了这些现象说明发送的太快了,要慢一点。
具体的方法就是发送端慢启动,比如倒水,刚开始倒的很慢,渐渐变快。然后设置一个阈值,当超过这个值的时候就要慢下来
通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。
第一层:物理层
第二层:数据链路层 802.2、802.3ATM、HDLC、FRAME RELAY
第三层:网络层 IP 、IPX、ARP、APPLETALK、ICMP
第四层:传输层 TCP、UDP 、SPX
第五层:会话层 RPC、SQL 、NFS 、X WINDOWS、ASP
第六层:表示层 ASCLL、PICT、TIFF、JPEG、 MIDI、MPEG
第七层:应用层 HTTP,FTP ,SNMP等
netty知识总结
目录
MTU(Maximun Transmission Unit)
一、tcp & udp
tcp : 面向连接 ;基于流 ;可靠的;仅支持一对一;首部开销20b;全双工可靠信道
udp:无连接;基于报文;不可靠;支持一对多、多对一、多对多通信;首部开销小8b;不可靠信道
二、tcp如何保证更可靠
1、确认应答和序列号
TCP传输时,将数据流进行编号,这就是序列号 ;可以用来应答、数据排序去重
每次接收方收到数据后,对川蜀坊进行应答,也就是发送ack报文
2、超时重传
TCP发出一段后,启动一个定时器,等待目标端ack报文,如不能及时收到确认,则重新发送报文
3、流量控制
tcp链接双方均有一个固定大小的缓冲空间,tcp接收端只允许发送端发送缓冲区能够接纳大小的数据。
当接收方来不及处理发送方数据时,能够提示发送方降低速率,防止包丢失
tcp流量控制协议是可变大小的滑动窗口协议
4、拥塞控制
当网络阻塞时,发送方继续重发,会加重网络阻塞,所以当出现阻塞时,应该控制发送方的速率。拥塞控制是为了控制网络的阻塞程度。
TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
三、滑动窗口
滑动窗口是tcp流量控制的协议。假设我们每次发送一个数据包,然后就等待ack应答 ,这样会极大的影响传输效率,所以,我们把多个数据包达成一个package,一起发送出去,然后一起等待 。我们能够一起发送的数据量大小 , 就是窗口。
我们每次发送固定大小的窗口的数据 ,然后等待ack ,可行吗?答案是否定的。
窗口设置小了 : 需要频繁的接收数据的ack应答
窗口设置大了: 接收端处理不过来,会阻塞我们的网络。
所以我们需要一个动态的窗口:接收方来告诉我,可以处理多少数据 , 这就是提出窗口;发送方根据提出窗口和 已发送但是未确认的量 , 来计算可用窗口的大小。
我们可以把数据看做一条链 , 有一个可以向后滑动的窗口,框柱了 已发送待确认和允许发送的数据段
TCP的包可以分为四种状态
-
已发送并且已经确认的包。
-
已发送但是没有确认的包。
-
未发送但是可以发送的包。
-
不允许被发送的包。
零窗口 :当接收方提出窗口= 0 ,发送方只能停止发送。但是会建立一个零窗口定时探测器,向接收方询问窗口大小,当不在是0时 , 就可以继续发送了。
四、状态流转
LISTEN:等待从任何远端TCP 和端口的连接请求。 SYN_SENT:发送完一个连接请求后等待一个匹配的连接请求。 SYN_RECEIVED:发送连接请求并且接收到匹配的连接请求以后等待连接请求确认。 ESTABLISHED:表示一个打开的连接,接收到的数据可以被投递给用户。连接的数据传输阶段的正常状态。 FIN_WAIT_1:等待远端TCP 的连接终止请求,或者等待之前发送的连接终止请求的确认。 FIN_WAIT_2:等待远端TCP 的连接终止请求。 CLOSE_WAIT:等待本地用户的连接终止请求。 CLOSING:等待远端TCP 的连接终止请求确认。 LAST_ACK:等待先前发送给远端TCP 的连接终止请求的确认(包括它字节的连接终止请求的确认) TIME_WAIT:等待足够的时间过去以确保远端TCP 接收到它的连接终止请求的确认。 TIME_WAIT 两个存在的理由: 1.可靠的实现tcp全双工连接的终止; 2.允许老的重复分节在网络中消逝。 CLOSED:不在连接状态(这是为方便描述假想的状态,实际不存在)
五、粘包、拆包
tcp是流协议,本身是无界限的。tcp会根据实际情况划分发送,一个完整的数据包可能拆分成多个,也可以整合成一个进行发送,这就是拆包、粘包
MTU(Maximun Transmission Unit)
最大传输单元,在数据链路层中,规定MTU大小,以太网 设置为1500字节
MSS(Maximun Segment Size)
最大保温段 , MSS = MTU - IP首部(20)- TCP首部(20) = 1460
1、引起拆包、粘包的原因
1、应用程序写入的数据 超过了套接字缓存大小 , 发生拆包
2、应用程序写入的数据小于套接字缓存,缓存会整合多个数据,统一发送到网络上,发生粘包
3、接收方法不能即时读取套接字缓存区数据,发生粘包
2、tcp解决方案
1、消息定长
2、分隔符
3、消息头,规定数据长度
3、netty解决方案
1、FixedLengthFrameDecoder 消息定长
2、LineBasedFrameDecoder 换行符 和 DelimiterBasedFrameDecoder (用户自定义风格福)
3、LengthFieldBasedFrameDecoder 数据包中添加一个长度的字段
4、自定义编码、解码器
六、同步、异步、阻塞、非阻塞
-
同步,就是我调用一个功能,该功能没有结束前,我死等结果。
-
异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
-
阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
-
非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
七、网络IO模型
八、BIO、NIO、AIO
1、BIO , 同步且阻塞 , 一个请求一个线程,对服务器要求高,适用于连接数比较小且固定的架构,开发难度小
2、NIO,同步非阻塞 ,客服端的链接请求注册到多路复用器上,多路复用器轮询到有链接IO时,转给一个线程处理,自己继续阻塞等待,jdk1.4开始支持
3、AIO ,异步非阻塞,客户端的IO请求由内内核理完成,然后通知业务线程处理,适用于连接数多 且 链接较长的架构。jdk7 开始支持
九、select 、poll 、 epoll
1、链接数
select : 1024个 , 单个进程能打开的最大连接数 有FD_ZETSIZE 决定
poll : 没有上限,采用链表的方式
epoll : 链接数有上限,但是很大,1G内存支持10万的链接
2、轮询方式
select : 普通轮询 , 当连接数过大时,性能较差
poll : 同上
epoll : 采用回调的方式 , 链接的socket注册到epoll上 , 当有数据读写时,调用callBack
3、数据copy
select: 内核空间拷贝至用户空间 ,非常耗时
poll : 同上
epoll : 内核空间和用户空间 共享一块内存空间,相当于是零拷贝
十、netty 优点
NIO:
1、nio类库、api学习成本高,复杂
2、失败重试、数据丢失、拆包粘包等问题
3、selectk空轮训,内存飚增
netty:
1、架构设计优雅简单,耦合度低,底层模型可以使用不同网络协议
2、提供多种标准协议、安全、编码解码的支持
3、降低了NIO使用难度
4、很多开源框架都在使用,dubbo /rocketmq/spark
5、零拷贝、统一的api、标准可扩展的时间模型
十一、netty的组件
1、channel
2、buffer
3、EventLoop
4、ChannelHandler 和 ChannelPipeline
5、Bootstrap 和 ServerBootstrap
十二、什么是netty
1、netty是基于java nio(IO多路复用)的网络通信框架 ,极大的简化了TCP、UDP套接字服务的网络编程, 并且性能与安全性、使用性上更好。
2、特点:采用的reactor线程模型,是异步非阻塞、基于事件驱动、高性能、高可靠性和高定制型
3、支持多种协议、多种粘包拆包及二次编码机制。
4、使用范围广泛,比如dubbo/rocketMQ , Elasticsearch/gRPC等等
十三、为什么使用netty
1、简单易用,为我们屏蔽jdk自带的java nio的复杂度。
2、优秀的线程模型 reactor , 性能高 ,且零维护
3、功能强大,预置了多种编解码器,支持多种主流协议
4、高定制性,通过ChannelHandler 对通信框架进行灵活扩展
5、安全性高,有完整的SSL/TLS等支持
6、社区活动、网上教程多
7、使用广泛,在互联网、大数据、网络游戏等领取广泛应用,且满足商业标准
十四、应用场景
理论上NIO可以做的事情,使用netty都可以做。
1、作为RPC框架的网络通信工具:DUBBO、GRPC
2、用作HTTP服务器,目前我们常见的web服务器都是基于toncat或者jetty的 , 是基于serlet的 , NIO支持更底层的TCP协议,所以也可以用作支撑HTTP服务器,目前netty 就是http的协议支持。
3、即时的通讯系统,比如聊天工具 或者 聊天室,这块开源项目比较多的
4、消息推送系统。比如rocketMQ
十五、重要组件
1、bootstrap / serverbootstrap
bootstrap是netty的引导程序,采用了构造者模式,包含了线程组、通道channel、handler等配置信息。
引导netty的启动:
1、new NioEventLoop的构造方法里打开了一个多路复用器
2、serverBootstrap.bind()
2.1、创建一个ServerSocketChannel (initAndRegister())
2.2、绑定一个端口,先注册0事件 ,因为这时 项目还未完全启动
AbstractChannel对象的
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
2.3、真正的绑定 DefaultChannelPipeline.HeadContext -> AbstractChannel.Head
javaChannel().bind(localAddress, config.getBacklog());
ServerBootstrapAcceptor请求接收器,将boss线程接收到的链接请求封装转发给worker线程
public void channelRead(ChannelHandlerContext ctx, Object msg) { try { childGroup.register(child).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { forceClose(child, future.cause()); } } }); } catch (Throwable t) { forceClose(child, t); } }
2、nioEventLoopGroup/nioEventLoop
netty的线程池和执行线程。
nioEventLoopGroup : 提供了创建、获取nioEventLoop、注册channel的方法。
nioEventLoop : 我们可以看做是线程的扩展,该对象主要负责监听网络事件 并调用事件处理器进行相关的操作。
3、channel
channel接口是netty 对网络操作的抽象 , 包含了基本的IO操作,如bind()/connet()/read()/write()等 , 其主要实现如下:
NioServerSocketChannel , 对应 java nio的 ServerSocketChannel
NioSocketChannel , 对应 java nio的 SocketChannel
4、channelFuture
netty是异步非阻塞 , 我们在发起调用后 , 有可能不能立刻得到我们期待的结果, 所以我们通过channelFuture.addListener ,添加一个回调。
另外我们还可以通过sync , 是的异步非阻塞变为同步阻塞式调用
5、channelPipeline
channelPipeline是channelHandler的双向链表,提供了一个容器定义并处理流转与处理器链上的channel。
一个channel只能绑定于一个channelPipeline , 也就只能在此容器内流转 ,局部范围内是串行的,没有线程安全问题
channelPipeline 定义了handler的head和tail , 可以沿着责任链依次处理。
6、channelHandler
是netty消息的具体处理器,负责处理连接、读、写等操作。
十三、高性能
基于I/O多路复用模型
零拷贝
基于NIO的Buffer
基于内存池的缓冲区重用机制
无锁化的串行设计理念
I/O操作的异步处理
提供对protobuf等高性能序列化协议支持
可以对TCP进行更加灵活地配置
以上是关于Netty理论四:TCP vs UDP的主要内容,如果未能解决你的问题,请参考以下文章