1.为什么使用netty
简单,再也不用编写复杂的代码逻辑去实现通信,再也不用考虑性能问题,不需要考虑编解码问题,半包读写问题。
netty运用于Hadoop的RPC框架Avro,JMS框架RocketMQ,主流的分布式通信框架Dubbox等。
在业务场景中比如导航,建立长连接实时返回数据结果,以及滴滴打车,实时返回车程等。
2.netty特性
Design:各种传输类型,阻塞和非阻塞套接字统一的API使用灵活简单但功能强大的线程模型,无连接的DatagramSocked支持链逻辑,易于重用。
Easy Of Use:提供大量的文档和例子,除了依赖jdk1.6,某些功能依赖jdk1.7,其他特性可能有相关依赖,都是可选的。
Performance:比Java API更好的吞吐量和更低的延迟,因为线程池和重用所有消耗较少的资源尽量减少不必要的内存拷贝。
Robustness:鲁棒性,可以理解成健壮性链接,快或慢或超载不会导致更多的OutOfMemoryError,在高速的网络程序中不会有不公平的read/write。
Security:完整的SSL/TSL和start TLS支持可以在如Applet或OSGI这些受限制的环境中使用。
Community:版本发布频繁,社区活跃
3.NIO通信步骤
(1)创建ServletSocketChannel,为它配置非阻塞模式。
(2)绑定监听,配置TCP参数,录入backlog大小。
(3)创建一个独立的IO线程,用于轮询多路复用器Selector
(4)创建Selector,将之前创建的ServletSocketChannel注册到Selector,并设置监听标识位SelectionKey.ACCEPT
(5)启动IO线程,在循环体中轮询Selector.select()方法,轮询就绪的通道。
(6)当轮询到了处于就绪的通道时,需要进行判断操作位,如果是ACCEPT状态,说明是新的客户端接入,则调用accept方法接受新的客户端。
(7)设置新接入客户端的一些参数,如非阻塞、并将其通道继续注册到Selector之中,设置监听标识位等。
(8)如果轮询通道位的操作是READ,则进行读取,构造Buffer 对象等。
(9)更细节的还有数据没发送完继续发送等问题..............
一个简单的NIO服务器端程序,就是如此的复杂,接下来看Netty需要的几个步骤。
4.Netty通信步骤
(1)创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的请求),另一个则进行网络通信读写。
(2)创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。
(3)创建一个实际处理数据的类ChannelIntializer,进行初始化的准备工作,比如设置接受传出数据的字符集,格式,已经实际处理数据的接口。
(4)绑定端口,执行同步阻塞方法,等待服务器端启动即可。
5.服务端和客户端
(1) serverBootstap 的SO_BACKLOG配置
服务器端TCP内核模块维护需要两个队列,我们称之为A队列和B队列。
客户端向服务端发送connect请求的时候,会发送带有SYN标志的包(第一次握手)
服务器端收到客户端的SYN包后会向客户端发送SYN ACK确认(第二次握手)
默认内核模块将客户端加到A队列进行等待,然后当服务器端收到客户端发来的ACK
时,TCP内核模块会将客户端连接从A转移到B中,连接完成,返回accept。也就是
说ACCEPT从B连接中取出完成三次握手的连接。
A和B队列的长度和就是backlog,当A和B连接的长度和大于backlog时,新连接会
被TCP内核拒绝,当backlog过小时,可能会出现accept速度跟不上。
需要注意的是:backlog对程序支持的连接数并无影响,backlog影响的只是还没有
被accept取出的连接。
(2) 服务器端会配置两个EventLoopGroup,客户端只需要配置一个。
(3) 服务端处理类实现ChannelHandlerAdapter和SimpleChannelInboundHandler的区别
ChannelHandlerAdapter在处理完消息后需要负责释放资源。在这里将调用ByteBuf.release()来释放资源。
SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是通过Netty处理所有消息的ChannelHandler实现了
ReferenceCounted接口达到的。所以当服务器要返回相同的消息给客户端,用ChannelHandlerAdapter。
(4) netty将接受消息和输出消息封装成了ByteBuf对象
传统Nio模式接受和传输消息需要进行flip操作(复位指针),ByteBuf不用,因为ByteBuf里面有读写两个指针。
(5) ByteBuf资源手动去释放资源
ByteBuf是一个计数对象,处理器的职责是释放所有传递到处理器的计数对象。如果在业务代码中手动执行writeAndflush操作也不用手动释放,因为它会自动去释放。
(6) netty中所有操作都是异步,所以很多操作都会有一个Future类做为操作通知返回对象,可以避免在消息还没写完或者读完
通道已经关闭了。
6.消息的粘包黏包问题
问题描述:TCP协议是流协议,服务器端发送数据 ABC DEF,客户端解析成AB DE F或者其他格式,如果不做处理的话这种
情况很容易出现。
详细原因:
(1) 应用程序write写入的字节大小大于套接字发送缓冲区的大小。
(2) 进行MSS大小的TCP分段
(3) 以太网帧的payload大于MTU进行IP分片
解决方案:
(1) 发送消息协议好固定长度,如果不够长度的话自己空位补空格。接受数据长度不够固定长度时保留在tcp流中,够固定长度
的时候再进行解析。
(2) 在包尾部增加特殊字符进行分割,例如加回车换行符。
Netty封装了DelimiterBasedFramerDecoder
(3) 推荐的主流,将消息分成消息头和消息体,在消息头中包含消息长度的字段,然后进行业务逻辑的处理。
7.netty的长连接和短连接
默认netty连接都是长连接,如何切换成短连接呢?
(1)在server端每次发完消息后关闭和客户端的连接,即在操作结尾加上addListener 关闭对客户端的通道,但是客户端发送过
来的消息还是会接收到。
(注:writeAndFlush操作会把数据写到tcp流里面 并发送到通道里面,单纯的write 并不会被服务端读取数据)
8.netty的数据通信
通信方式大致分为三种:
(1)长连接不间断的进行连接,也就是服务端和客户端的通道一直处于开启状态,推荐场景:服务器性能比较好,客户端连接数量比较少。
(2)一次性批量提交数据,采用短连接方式,也就是我们将数据保存在本地临时缓冲区或者临时表中,界当到达临时一次性批量提交,或者根据定时任务轮询提交,推荐场景:对实时性要求不高。
(3)使用一种特殊的长连接,在指定的某一个时间,服务器没有和客户端通信时断开连接,当客户端向服务端发送请求时再次建立连接。这种场景需要考虑到
(a)如何在超时后关闭通道,关闭通道后如何再创立连接?
注:通信是双向的,所以不是说一边关闭连接通道,另一边就不可以朝这边传递数据了。
关闭通道:服务端和客户端在通道处理器上加上new ReadTimeoutHandler(5) 5分钟无通信关闭。
创立连接:每次在通信之前判断连接是否为空或者未激活状态,如果不是就重新获取连接。
(b)客户端在宕机时无需考虑,在机器重启后与服务器建立连接,服务器宕机时客户端怎么和服务端进行连接呢?
一般客户端会定时向服务端发送监听包监听服务端在线情况,时长一般是服务端休眠时间的两倍。
9.netty的心跳检测
使用场景:维护服务器集群,主机对下面各服务器进行实时监控,采用netty模拟心跳机制。
用sigar 获取 机器内存,cpu,网络,存储登情况。需要将下载的sigar的sigar-amd64-winnt.dll(windows64位)文件放到
JAVE_HOME的bin目录下。
示例代码逻辑:发送心跳之前 客户端和服务端进行2次握手,进行身份验证,身份验证成功后,服务端起一个定时线程,发送
请求包(心跳包),服务端接收请求包(心跳包)后记录请求包内容,包括服务端的CPU、内存、网络、存储等状态(这些内容
通过sigar的api获取)
心跳包模拟代码见示例;
10.netty做文件上传下载
netty做文件操作的基础是基于Http协议,所以先了解一下http协议
Http协议是建立在Tcp协议传输协议上的应用层协议,目前针对于WEB开发,在netty中使用Http协议也是异步非阻塞的。
Http协议特点:
(1)简单:客户端请求服务器时只需要指定URL和携带必要的参数就可以了。
(2)灵活:Http协议允许传输任何类型的数据对象,传输内容由Http协议头部的Content-Type加以标记。
(3)无状态:Http协议是无状态的,无状态指的是协议对事物处理没有记忆能力,这意味着如果后续处理需要之前的信息,则它必须要重新获取,体现出Http的设计是为了使网络传输更加的轻量级,敏捷,负载较轻。
Http协议组成部分:
请求行
请求头
请求正文(实体部分)
Http协议请求方式:
GET(获取Request-URI所标识的资源)、POST(在Request-URI所标识的资源附加新的请求信息)、HEAD(请求获取
Request-URI所标识的资源响应头)、PUT(请求服务器存储一个资源,并用Request-URI作位标识)、DELETE(请求服务器删
除Request-URI所标识的资源)、TRACE(请求服务器回送收到的请求信息,主要用来测试和诊断)、CONNECT(保留将来使
用)、OPTIONS(请求查询服务器的性能和相关资源的选项和需求)
Http协议的响应信息:
1xx:提示信息,表示请求已经接收,继续处理。
2xx:成功,表示请求已经接收成功。
3xx:重定向,要完成的操作必须进行更进一步的操作。
4xx:客户端错误。可能是请求语法错误或者请求无法实现。
5xx:服务器端错误,服务器未能处理请求(可能内部异常)。
netty做文件处理示例代码逻辑:
服务端下载:
在文件处理之前对请求进行http解码码、http消息聚合(将多个消息转换成单一的FullHttpRequest)、http编码、有需要的话加
入chunked处理器(支持大文件传输,底层是分段传输)。
上续操作直接把请求封装成FullHttpRequest,然后基本的几重判断,如请求方式、请求编码结果是否存在、请求资源是否存在
等。
如果请求遍历目录,手动构造html见面,遍历请求内容,然后封装一个DefaultFullHttpResponse做返回对象,设置头部信息为
text/html,再将httpResponse写入处理器上下文。
如果请求下载资源,用RandomAccessFile去获取资源,构建一个ChunkedFile,将RandomAccessFile写入,声明传输的内容
区间和每次传输的大小,添加传输监听以获取传输进度。如果使用chunked的话最后要再发一个空消息体,表示所有消息体已成
功发送。
附录:菜鸟一枚,可能写的不是很好,啊哈哈哈,代码我没有上传githup,所以需要代码的码友留个qq吧。