一 netty概述
1.1 为什么有了NIO还有Netty
- NIO的缺点
- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的Selector 空轮询 bug 会导致 cpu 飙升 100%
- NIO的API繁杂,使用麻烦,必须熟练掌握Selector、Channel、Buffer等相关API。并且需要熟练Java多线程编程和网络编程,才能写出高质量的NIO代码。
- 开发工作量和难度都非常大。例如客户端的断连重连,网络闪退,半包读取,失败缓存等问题。
1.2 什么是netty
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients. (来自官网 https://netty.io/)
Netty 是一个异步的基于事件驱动的网络应用框架。为了快速开发高性能的服务端和客户端
- Netty是JBoss提供的一个Java开源框架。提供了异步、基于事件驱动的网络应用框架。用于快速开发高性能、高可靠性的网络IO程序
- Netty可以帮助你快速、简单的开发一个网络应用,相当于简化和流程化一个NIO的开发过程
- Netty是目前最流程的NIO框架。在互联网领域、大数据分布式计算领域、游戏行业 等都有广泛的应用。比如ES、Dobbo等框架内部都是采用了Netty
从下到上解释为:
- 绿色部分:是netty的核心包
- netty的零啊拷贝
- 交互的API库
- 可扩展的事件模型
- 黄色的部分 支持的相关协议
- webSock谢谢
- zlib / gzip 压缩
- protobuf 。谷歌的解码编码器
- 大文件的传输等
- 红色的 是支持的传输服务
1.4 Netty的优点
- 优雅的设计、适用于各种传输类型的统一API阻塞和非阻塞Socket。基于灵活可扩展的事件模型。
- 使用方便,有详细的JavaApi
- 高性能、高吞吐量,延迟低,减少资源的消耗。还有零拷贝
- 安全。完整的SSL(Secure Socket Layer,安全套接字层) /TSL (Transport LayerSecurity,传输层安全协议)和StartTLS支持的各种传输协议
- 社区比较活跃,不断更新。
- Netty5 已经废弃了,一般都是用netty4
二、Netty的核心组件
- Channel
- callback
- Future
- event 和ChannelHandler
2.1 Channel
channel 是Java NIO的一个基本架构。其基本的构造是 Socket,在jdk中channel是通讯载体,在netty中channel被赋予了更多的功能。
首先强调一点:NIO的Channel与Netty的Channel不是一个东西!
Netty重新设计了Channel接口,并且给予了很多不同的实现。Channel时Netty的网络抽象类,除了NIO中Channel所包含的网络I/O操作,主动建立/关闭连接,获取双方网络地址的功能外,还包含了Netty框架的功能,例如:获取Channel的EventLoop\Pipeline等。
- Channel接口是能与一个网络套接字(或组件)进行I/0操作(读取\写入\连接\绑定)的纽带.
- 通过Channel可以获取连接的状态(是否连接/是否打开),配置通道的参数(设置缓冲区大小等),进行I/O操作
简单的理解就是拿到了channel,就可以和客户端进行通信,具体参考了Netty Channel源码分析 和 《Netty实战》
常见的Channel类型有:
- NioSocketChannel:代表异步的客户端TCP scoket连接 (用的最多)
- NioServerSocketChannel:异步的服务器端 TCP Socket 连接 (用的最多)
- NioDatagramChannel:异步的 UDP 连接
- NioSctpChannel:异步的客户端 Sctp 连接
- NioSctpServerChannel:异步的 Sctp 服务器端连接
- OioSocketChannel:同步的客户端 TCP Socket 连接
- OioServerSocketChannel:同步的服务器端 TCP Socket 连接
- OioDatagramChannel:同步的 UDP 连接
- OioSctpChannel:同步的 Sctp 服务器端连接
- OioSctpServerChannel:同步的客户端 TCP Socket 连接
常见的API有:
接口名称 | 说明 |
---|---|
eventLoop() | Channel需要注册到EventLoop的多路复用器上,用于处理I/O事件,通过eventLoop()方法可以获取到Channel注册的EventLoop。EventLoop本质上就是处理网络读写事件的Reactor线程。在Netty中,它不仅仅用来处理网络事件,也可以用来执行定时任务和用户自定义NioTask等任务。 |
pipeline() | 返回channel分配的ChannelPipeline |
isActive() | 判断channel是否激活。 |
localAddress() | 返回本地的socket地址 |
remoteAddress() | 返回远程客户端的socket地址。 |
flush() | 将之前已写的数据冲刷到底层Channel上去。可以理解为发送数据 |
write(Object msg) | 请求将当前的msg通过ChannelPipeline写入到目标Channel中。需要注意write操作只是将消息存入到消息发送环形数组中,消息并没有发送,需要调用flush()方法才会发送 |
writeAndFlush(Object msg) | 等同于调用write()并接着调用flush() |
read() | 从当前的Channel中读取数据到第一个inbound缓冲区中,如果数据被成功读取,触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件 |
writeAndFlush(Object msg) | 等同于调用write()并接着调用flush() |
metadate() | 熟悉TCP协议的读者可能知道,当创建Socket的时候需要指定TCP参数,例如接收和发送的TCP缓冲区大小,TCP的超时时间。是否重用地址等。在Netty中,每个Channel对应一个物理链接,每个连接都有自己的TCP参数配置。所以,Channel会聚合一个ChannelMetadata用来对TCP参数提供元数据描述信息,通过metadata()方法就可以获取当前Channel的TCP参数配置。 |
writeAndFlush(Object msg) | 等同于调用write()并接着调用flush() |
close(ChannelPromise promise) | 主动关闭当前连接,通过ChannelPromise设置操作结果并进行结果通知,无论操作是否成功,都可以通过ChannelPromise获取操作结果,会触发ChannelPipeline中所有ChannelHandler的ChannelHandler.close事件 |
parent() | 对于服务端Channel而言,它的父Channel为空;对于客户端Channel,它的父Channel就是创建它的ServerSocketChannel。 |
id() | 返回ChannelId对象,ChannelId是Channel的唯一标识。 |
2.2 回调
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用。这使得后者可以在适当的时候调用前者。回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一
Netty 在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理。
ChannelHandler的主要API如下:
接口名称 | 说明 |
---|---|
handlerAdded | 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用 |
handlerRemoved | 当从 ChannelPipeline 中移除 ChannelHandler 时被调用 |
exceptionCaught | 当处理过程中在 ChannelPipeline 中有错误产生时被调用 |
channelRegistered | 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用 |
channelUnregistered | 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用 |
channelActive | 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪 |
channelInactive | 当 Channel 离开活动状态并且不再连接它的远程节点时被调用 |
channelReadComplete | 当Channel上的一个读操作完成时被调用 |
channelRead | 当从 Channel 读取数据时被调用 |
ChannelWritabilityChanged | 当 Channel 的可写状态发生改变时被调用 |
bind | 当请求将 Channel 绑定到本地地址时被调用 |
connect | 当请求将 Channel 连接到远程节点时被调用 |
disconnect | 当请求将 Channel 从远程节点断开时被调用 |
close | 当请求关闭 Channel 时被调用 |
read | 当请求从 Channel 读取更多的数据时被调用 |
flush | 当请求通过 Channel 将入队数据冲刷到远程节点时被调用 |
write | 当请求通过 Channel 将数据写到远程节点时被调用 |
2.1 Future
Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问
Future最早出现于JDK的java.util.concurrent.Future,它用于表示异步操作的结果.由于Netty的Future都是与异步I/O操作相关的,因此命名为ChannelFuture,代表它与Channel操作相关.
ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("链接成功");
} else {
Throwable cause = future.cause();
System.out.println("链接失败"+cause.getMessage());
}
}
});
- 异步连接到一个远程节点
- 注册ChannelFutureListener 到channelFuture ,以便之后到操作能够获取到通知
- 当bind 等操作执行完,会调用ChannelFutureListener的operationComplete方法,并执行向对应的逻辑
- 如果发生了错误,可以对错误进行处理。
2.1 Event和ChannelHandler
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
对于服务器事件处理模型,通常有下面几种方式
- 每收到一个请求,就创建一个线程。来处理该请求
- 每收到一个请求,就放入到一个事件列表,让主线程通过非阻塞到方式来处理请求。
以上各种方式各有各的好处:
第一种方式,由于设计到多线程到问题,可能会遇到线程见到通信和共享资源锁到问题。而且线程本身就是稀有资源,所以性能也有一定到限制
第二种方式呢。在写程序到时候来逻辑可能比较复杂等。
事件驱动模型
事件驱驱动架构由三个基本组件构成,事件(event)、事件循环(eventLoop)、事件处理器(ChannelHandler)。事件产生后发送给事件循环,事件循环将每个事件分派给个各个事件处理器。素有事件,是按照它们们与入站或出站数据流的相关性进行分类的。所以事件A由处理器A处理,事件B将被处理器B处理。
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。