资料cv的原文链接:blog.csdn.net/u014494148/…
Netty的介绍
下面是从Netty官网趴下来的对Netty的定义
Netty是由JBOSS提供的一个java开源框架,Netty 是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty 是一个 NIO 客户端服务器框架,它可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和精简了 TCP 和 UDP 套接字服务器等网络编程。
简单理解:Netty是基于NIO(Nonblocking I/O,非阻塞IO)实现的网络通信框架,相比传统IO他的并发性能得到了很大提高,它在NIO基础上封装了NIO的使用细节,让网络编程变得更加高效简洁,大大简化了NIO的开发过程。
Netty拥有高性能、高吞吐量、低延迟、消耗资源少;零拷贝的特点,同时支持SSL/TLS 和 StartTLS ,社区活跃、版本迭代周期短。
Netty是互联网最流行的NIO框架,广泛应用于互联网领域,游戏领域,大数据领域,比较知名的ElasticSearch,Dubbo等技术都用到了Netty。
1. Reactor线程模型
Reactor(也叫Dispacher)线程模型不再为每个请求创建线程 ,而是基于 I/O 复用模型,多个请求连接同一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当请求中有数据,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理,一个线程可以处理多个连接的业务,如图:
- Reactor:即图中的ServiceHandler,Reactor负责监听和事件分发,分发给适当的处理程序来对 IO 事件做出反应, 在单独的线程中执行。就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
- Handlers:Reactor监听到IO事件,需要调度应用程序来处理即:Handler,Handler是真正处理 I/O 事件的程序,类似于接电话的实际的人员。
简单理解就是Reactor线程模型有两个角色 ,一个负责接待请求(Reactor), 一个负责处理请求(Handlers)
另外Reactor有三种模式 :
- Reactor 单线程模型
- 单Reactor 多线程模型
- Reactor主从多线程模型
1️⃣Reactor单线程
Netty框架是基于主从Reactor多线程模型进行改进(多个主Reactor)。
工作流程
Reactor单线程模型工作流程如下
-
Reactor 通过 Select 监控客户端请求,收到事件后通过 Dispatch 进行请求分发
-
如果是请求事件是建立连接,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler。
-
如果不是连接事件,则 Reactor 会分发调用连接对应的 Handler 来处理请求
-
Handler 会完成数据read,进行业务处理最后通过 Send将响应结果返回给Client
问题
Reactor单线程模型优点是模型简单,缺点也是比较明显
- 线程单一,如果并发高,业务处理慢,很容易导致性能瓶颈,
- 如果线程故障或终止,整个系统不可用
所以这种模型不太适合高并发场景,和业务处理比较耗时的场景。
2️⃣单Reactor多线程模型
工作流程
Reactor多线程模式工作流程如下
- Reactor 通过 Select 监控客户端请求,收到事件后通过 Dispatch 进行请求分发
- **如果是请求事件是建立连接,**则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 。
- **如果不是连接事件,**则 Reactor 会分发调用连接对应的 Handler 来处理请求
- Handler 只负责响应事件,不做具体业务处理,通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理
- Worker 线程池会分配独立的线程完成真正的业务处理,将响应结果发给 Handler 进行处理。
- Handler 收到响应结果后通过 Send 将响应结果返回给 Client。
问题
- 这种模型引入了线程池,Reactor线程负责接收连接和响应事件,具体IO事件交给线程池分配的线程处理。
- 优点是可以充分利用CPU,提供整体性能
- 但是Reactor承担所有的事件监听和分发,容易成为性能瓶颈,多线程的数据共享也是一个问题。
3️⃣主从Reactor多线程
工作流程
- Reactor 主线程 MainReactor 对象通过 Select 监听连接事件,收到事件后通过 Acceptor 接收,处理建立连接事件。
- **Acceptor 处理建立连接事件后,**MainReactor 将连接分配 Reactor 子线程给 SubReactor 进行处理。
- SubReactor 将连接加入连接队列进行监听,并创建一个 Handler 用于处理各种连接事件,
- 当有新的事件发生时,SubReactor 会调用连接对应的 Handler 处理
- Handler 通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理
- Worker 线程池会分配独立的线程完成真正的业务处理,将响应结果发给 Handler 进行处理
- Handler 收到响应结果后通过 Send 将响应结果返回给 Client
好处
这种模式的优点比较明显,Reactor主线程只需要接收新连接,子线程完成后续的业务处理,Reactor的压力得到了分担。
这种模型在许多项目中广泛使用,包括 Nginx 主从 Reactor 多进程模型,Memcached 主从多线程,Netty 主从多线程模型的支持。
2. Netty的线程模型
Netty是基于主从Reactor多线程模型实现,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果
Netty线程模型中有两个Group:
Boss Group:用于Accetpt连接建立事件并分发请求
Worker Group:用于处理I/O读写事件和业务逻辑。
2.1 关键组件
- Channel
Netty网络通信的组件,能够用于执行网络IO操作。
- Selector
Netty基于Selector对象实现I/O多路复用,即:一个线程可以监听多个连接的Channel事件, 当channel向Selector注册后,Selector 就可以自动不断地查询(select) 注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等)。
- NioEventLoop
NioEventLoop表示一个不断循环的执行处理任务的线程, 每个NioEventLoop 都有一个 selector。用于监听注册的Channel的IO事件。
NioEventLoop中维护一个线程和任务队列,支持异步提交任务,线程启动时会调用NioEventLoop的run方法,执行IO和非IO任务。 其中IO任务由prossSelectedKeys触发,非IO任务由runAlltasks触发。
- NioEventLoopGroup
NioEventLoopGroup用来管理EventLoop,可以理解为线程组,内部维护了一组线程
2.2 工作流程
Boss Group里面的NioEventLoop会轮询accept事件,遇到有新的连接,就生成NioSocketChannel,并把这个Channel注册到Worker Group的Selector上WorkerGroup轮询read和write事件,在可读或者可写条件满足时,就进行处理
Worker Group和Boss Group都是通过里面的NioEventLoop来操作的。NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法
每个Boss NioEventLoop循环执行的步骤有3步:
-
轮询检查accept事件
-
处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个 Worker NIOEventLoop上的 selector,这样Channel的IO事件就交给WorkGroup去监听了。
-
最后执行一个runAllTasks方法,用于处理任务队列中的任务
每个 Worker NIOEventLoop 循环执行的步骤:
- 轮询read, write 事件
- 处理 I/O 事件, 即 read, write 事件, 再对应NioSocketChannel处理
- 最后执行一个runAllTasks方法,用于处理任务队列中的任务
入门案例
我们来写一个简单的服务端和客户端通信的案例
我们需要有GoosGroup来循环监听请求事件,需要有WorkGroup来处理事件,而这两个角色都通过来就NioEventLoopGroup来进行事件监听,我们还需要创建事件处理器ChannelHandler,通过 Channel的ChannelPipeline把ChannelHandler进行关联。
引入依赖
首先创建Maven项目,导入Maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
服务端代码
对于服务端我们要做如下事情
- 创建两个NioEventLoopGroup ,一个作为BossGroup ,一个作为 WorkGroup
- 创建 ServerBootstrap 加入两个NioEventLoopGroup
- 通过 ServerBootstrap 添加Handler ,需要自己创建Handler
- 编写Handler ,通过继承ChannelInboundHandlerAdapter 来对不同的事件做不同的处理
Server
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建Boss Group :负责处理链接
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//创建 workGroup :负责处理读写事件
NioEventLoopGroup workGroup = new NioEventLoopGroup();
//创建服务端启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
try{
//把 bossGroup 和 workGroup 加入启动对象
bootstrap
.group(bossGroup,workGroup)
//保存活动链接状态,启用该功能时,TCP会主动探测空闲连接的有效性
.childOption(ChannelOption.SO_KEEPALIVE,true)
//多个客户端过来,处理不过来的请求在队列排队,指定存放请求的队列的大小
//ChannelOption.SO_BACKLOG:服务端处理客户端连接请求是顺序处理的,所以同一事件只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
//ChannelOption.SO_KEEPALIVE:当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。
.option(ChannelOption.SO_BACKLOG,64)
//指定服务器使用什么样的通道
.channel(NioServerSocketChannel.class)
//添加事件处理器Handler
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//把处理器添加进去 , 每个channel都对应一个pieline
//pieline通道和Handler进行绑定,通过Pieline可以获取到Handler
ch.pipeline().addLast(new MyServerHandler());
}
});
//绑定端口,开始接收客户端请求
//bind方法中会创建 Channel,然后使用 EventLoop注册Channel
ChannelFuture future = bootstrap.bind(new InetSocketAddress("127.0.0.1", 6000)).sync();
//对关闭通道进行监听
//当此通道关闭时将收到通知
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
创建handler
这个Handler是用来处理客户端channel的IO事件
//这个Handler是用来处理客户端channel的IO事件
public class MyServerHandler extends ChannelInboundHandlerAdapter {
//处理读事件 ChannelHandlerContext :上下文,含有通道,pipline ,地址 ; msg就是数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = (ByteBuf) msg;
System.out.println("MyServerHandler,接收到:"+ctx.channel().remoteAddress()+"发来的消息:"+buffer.toString(CharsetUtil.UTF_8));
}
//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//给客户端恢复消息
ctx.writeAndFlush(Unpooled.copiedBuffer("你好,我收到了消息",CharsetUtil.UTF_8));
}
}
客户端代码
客户端要做如下事情
- 创建 NioEventLoopGroup 用于监听服务端的事件
- 创建 Bootstrap启动对象 ,加入 NioEventLoopGroup
- 一样要通过 Bootstrap 添加Handler ,需要自己创建Handler
- 编写Handler ,通过继承ChannelInboundHandlerAdapter 来对不同的事件做不同的处理
Client
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端线程循环组
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
//客户端启动对象
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(eventExecutors)
//指定客户端使用的通道类型
.channel(NioSocketChannel.class)
//添加客户端处理器
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//添加客户端处理器
ch.pipeline().addLast(new MyClientHandler());
}
});
//链接服务端
ChannelFuture channelFuture = bootstrap
.connect(new InetSocketAddress("127.0.0.1", 6000)).sync();
//监听关闭事件
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventExecutors.shutdownGracefully();
}
}
}
创建handler
这个Handler是用来处理客户端channel的IO事件
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
//这个Handler是用来处理客户端channel的IO事件
public class MyClientHandler extends ChannelInboundHandlerAdapter {
// ChannelInboundHandlerAdapter :这个是为Channel通道绑定处理事件的Handler,用来接收请求。
//链接激活
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 给服务端发送消息
// Unpooled.copiedBuffer :工具,把数据拷贝到ByteBuf
ctx.writeAndFlush(Unpooled.copiedBuffer("你好服务端", CharsetUtil.UTF_8));
}
//处理读事件 ChannelHandlerContext :上下文,含有通道,pipline ,地址 ; msg就是数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf :Netty提供的byte缓冲区
ByteBuf buffer = (ByteBuf) msg;
System.out.println("MyClientHandler,接收到:"+ctx.channel().remoteAddress()+"发来的消息:"+buffer.toString(CharsetUtil.UTF_8));
}
//处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
结果
Netty核心类的认识
① NioEventLoopGroup
NioEventLoopGroup 继承于 EventLoopGroup ,代表Nio事件循环组,用来循环监听注册的Channel的IO事件
如上图,我们需要两个NioEventLoopGroup,一个为BossGroup做事件监听 ,一个为 WorkGroup做事件监听
② ServerBootstrap
ServerBootstrap是Netty服务端启动引导对象 ,Bootstrap是客户端的启动引导对象,作用都差不多,主要是用来配置整个netty程序,整合各个组件。
-
ServerBootstrap 常用方法如下:
-
group(EventLoopGroup parentGroup, EventLoopGroup childGroup): 该方法用于服务端,主要是为 BossGroup(Acceptor) 设置 EventLoopGroup 来监听连接事件,以及为 WorkGroup 设置EventLoopGroup来监听读写事件。 -
channel(Class<? extends C> channelClass): 指定用来创建channel通道实例的class -
option(ChannelOption option, T value): 方法的作用是用来给ServerChannel添加配置项 -
childOption(ChannelOption childOption, T value): 为请求的channel添加配置项 -
handler(ChannelHandler handler): 给BossGrop添加Handler -
childHandler(ChannelHandler childHandler): 给WorkGroup添加Handler -
bind(InetAddress inetHost, int inetPort): 为服务端绑定监听的地址和端口
-
-
Bootstrap 常用方法如下
-
connect(InetAddress inetHost, int inetPort):用来连接服务端 -
group(EventLoopGroup group): 为客户端添加EventLoopGroup事件循环组。
-
③ Future,ChannelFuture
由于Netty的IO是异步的,不能立即得到消息是否被处理完成,所以需要通过Future
ChannelFuture注册事件监听,当操作完成后会触发相应的事件,从而对事件结果做出相应的处理。
④ Channel
通道,用来关联IO操作和处理的Handler,不同的协议有不同的Channel
- NioServerSocketChannel : 服务端的TCP Socket 连接
- NioSocketChannel :客户端的TCP Socket连接
⑤ ChannelHandler
ChannelHandler是用来处理IO事件的Handler,Handler实际上分为两种,Inbound 入站和Outbound出站
ChannelHandler比较常用的有如下两个子类:
-
ChannelInboundHandlerAdapter
- 该适配器 实现了ChannelInboundHandler 接口,ChannelInboundHandler 又实现了ChannelHandler接口,主要是用来处理IO入站事件
-
ChannelOutboundHandlerAdapter
- 适配器 实现了ChannelOutboundHandler,ChannelOutboundHandler又实现了 ChannelHandler接口,它主要是用处理IO出站事件
二者的结合体体——ChannelDuplexHandler
ChannelDuplexHandler :双通道处理器,它同时实现了ChannelInboundHandler 和 ChannelOutboundHandler ,既可以处理IO入站事件,也可以处理IO出站事件。
继承图:
⑥ ChannelPipeline
对于每个新的通道Channel,都会创建一个新的ChannelPipeline,并将器pipeline附加到channel中。
可以把ChannelPipeline看成是一个ChandlerHandler的链表,当有事件发生的时候,Pipeline负责依次调用每一个Handler进行处理或截获通道的接收和发送数据。
ChannelPipeline 通过 ChannelHandlerContext来管理ChannelHandler。
下面是端点后的 ChannelPipeline的调用链截图
通过Channel可以获取到ChannelPipeline,通过ChannelPipeline也可以获取到Channel
ChannelPipeline是ChandlerHandler形成的双向链表 ,ChandlerHandler中包含了真正的Handler , 上图的Head是头节点,tail是尾节点 。 如下图:
所以整个IO事件的处理是通过 Channel ,ChannelPipeline 和 Handler 三者一起来完成的。这里要注意的是,如果事件运动方向是从客户端到服务端,数据会通过pipeline中的一些列的OutboundHandler进行处理,事件被称之为出站,反之就是入站。两种类型的Handller相互不影响
⑦ Unpooled
Unpooled是Netty提供的buffer缓冲工具类,可以根据给定的数据和编码创建一个ByteBuf,如
ByteBuf buffer = Unpooled.buffer(1024);。
ByteBuf类似于NIO中的ByteBuffer 。区别在于ByteBuffer需要调用flip()读写转换,而ByteBuf无需转换。
和ByteBuffer一样,ByteBuf中维护了一个byte[]来存储数据,同时还维护了一个writerIndex和readerIndex,当执行 buffer.writeByte 写操作writerIndex就会向后移动,指向下一个存储位置。当执行 buffer.readByte()操作的时候readerIndex就会向后移动指向下一个读的位置。当readerIndex等于writerIndex,ByteBuf中的数据读取完成。
基于Netty的Http服务器
资料来源: