Netty核心组件介绍
在了解新知识之前,先来对前面的知识回顾下:
- Netty抽象出两组线程池,BossGroup专门负责接收客户端连接,WorkerGroup专门负责网络读写
- NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道
- NioEventLoop内部采用串行化设计,从消息的读取->解码->编码->发送,始终由IO线程NioEventLoop负责
- NioEventLoopGroup下包含多个NioEventLoop
- 每个NioEventLoop中包含一个Selector,一个taskQueue
- 每个NioEventLoop的Selector上可以注册监听多个NioChannel
- 每个NioChannel只会绑定在唯一的NioEventLoop上
- 每个NioChannel都绑定有一个自己的ChannelPipeline
任务队列中Task的使用
关于示例代码以NettyServerHandler
举例
1. 用户程序自定义的普通任务
public class NettyServerCustomTaskHandler extends NettyServerHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//比如有个很耗时的业务->异步执行->提交到该channel对应的NioEventLoop下的taskQueue中
ctx.channel().eventLoop().execute(() -> {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,这是一个耗时操作", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
});
}
}
2.用户自定义定时任务
public class NettyServerCustomTaskHandler extends NettyServerHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//用户自定义定时任务 ->该任务是提交到 scheduledTaskQueue 中
ctx.channel().eventLoop().schedule(() -> {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,这是一个耗时操作", CharsetUtil.UTF_8));
System.out.println("channel code=" + ctx.channel().hashCode());
} catch (Exception ex) {
System.out.println("发生异常" + ex.getMessage());
}
}, 5, TimeUnit.SECONDS);
}
}
3.非当前Reactor线程调用channel的各种方法
例如在推送系统的业务线程里,根据用户标志找到对应的Channel引用,然后调用write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中后被异步消费
异步模型
基本介绍
- 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态通知和回调来通知调用者。
- Netty中的IO操作时异步的。包括Bind/Write/Connect等操作会简单的返回一个ChannelFuture。
- 调用者并不能立刻获得结果,而是通过Future-Listener机制,用户可以方便主动地获取或者通过通知机制获取IO操作结果
- Netty的异步模型是建立在future和callback(回调)之上的。Future的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不太合适。那可以在调用fun的时候立马返回一个future,后续可以通过Future去监控方法fun的处理过程(Future-Listener机制)
Future说明
- 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,比如检索计算等
- ChannelFuture是一个接口,我们可以添加监听器,当监听的事件发生时就会通过监听器
Future-Listener机制
- 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作
- 常见有如下操作
- 通过
isDone
方法来判断当前操作是否完成 - 通过
isSuccess
方法来判断已完成的当前操作是成功 - 通过
getCause
方法来获取已完成的当前操作失败的原因 - 通过
isCancelled
方法来判断已完成的当前操作是否被取消 - 通过
addListener
方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器,如果Future对象已完成,则通知指定的监听器
- 通过
示例
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.option(ChannelOption.SO_BACKLOG, 128)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerCustomTaskHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(6668).sync();
//给 channelFuture 注册监听器,监控我们关心的事件
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if(channelFuture.isDone()){
System.out.println("已完成");
}
if(channelFuture.isSuccess()){
System.out.println("已成功");
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
示例:基于Netty实现一个简单的HTTP服务
- Netty服务器在6668端口监听,浏览器发出请求"http://localhost:16668/"
- 服务器回复消息给浏览器或客户端消息,并对指定请求资源进行过滤
//服务器端
public class NettyBrowserServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("serverCodec", new HttpServerCodec());
ch.pipeline().addLast(new NettyBrowserHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(16668).sync();
channelFuture.addListener(future -> {
if (channelFuture.isSuccess()) {
System.out.println("运行成功");
}
});
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//handler
/**
* Created by myk
* 2020/1/21 下午2:28
* 1、SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子类
* 2、客户端和服务器端通讯的数据放在HttpObject中
*/
public class NettyBrowserHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判断是否为Http请求
if (msg instanceof HttpRequest) {
System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + ",NettyBrowserHandler.hash=" + this.hashCode());
System.out.println("消息类型" + msg.getClass());
System.out.println("客户端地址:" + ctx.channel().remoteAddress());
HttpRequest httpRequest = (HttpRequest) msg;
//浏览器请求时会请求图标favicon.ico 这里没有这个图标因此将这个给过滤掉
if (Objects.equals(httpRequest.uri(), "/favicon.ico")) {
return;
}
//回复信息给浏览器
ByteBuf byteBuf = Unpooled.copiedBuffer("hello,这是浏览器", CharsetUtil.UTF_16);
//给浏览器回复信息 要符合http协议规范'
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
//设置content-type=text-plain
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
//设置content-length
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
//将构建好 response 返回
ctx.writeAndFlush(response);
}
}
}
浏览器的输出结果:
Bootstrap、ServerBootstrap介绍
-
Bootstrap意思是引导,一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类,
-
常见的方法有
public ServerBootstrap group(EventLoopGroup parentGroup,EventLoopGroup childGroup)//该方法用于服务器端,用来设置两个EventLoop
public B group(EventLoopGroup group);//该方法用于客户端,用来设置一个EventLoop public B channel(Class<? extends C> channelClass);//该方法用来设置一个服务器端的通道实现 public <T> B option(ChannelOption<T> option,T value);//用来给ServerChannel添加配置 public <T> ServerBootstrap childOption(ChannelOption<T> childOption,T value);//用来给接收到的通道添加配置 public ServerBootstrap childHandler(ChannelHandler childHandler);//该方法用来设置业务处理类(自定义的handler) public ChannelFuture bind(int inetPort);//该方法用于服务器端,用来设置占用的端口号 public ChannelFuture connect(String inetHost,int inetPort);//该方法用于客户端,用来连接服务器端
Future、ChannelFuture介绍
Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会儿等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,它们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件
常见的方法有:
Channel channel();//返回当前正在执行IO操作的通道
ChannelFuture sync();//等待异步操作执行完毕
Channel介绍
- Netty网络通信的组件,能够用于执行网络IO操作
- 通过Channel可获得当前网络连接的通道的状态
- 通过Channel可获得网络连接的配置参数(例如接收缓冲区的大小)
- Channe提供异步的网络IO操作(如建立连接、读写、绑定端口),异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成
- 调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以IO操作成功、失败或取消时回调通知对方
- 支持关联IO操作与对应的处理程序
- 不同协议、不同阻塞类型的链接都有不同的Channel类型与之对应,常用的Channel类型:
- NioSocketchannel 异步客户端TCP Scoket连接
- NioServerSocketChannel,异步的服务器端TCP Socket连接
- NioSctpChannel,异步的UDP连接
- NioDatagramChannel,异步的UDP连接
- NioSctpServerChannel,异步的Sctp服务器端连接,这些通道涵盖了UDP和TCP网络IO以及文件IO
Selector
- Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件。
- 当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询(Select)这些注册的Channel是否有已就绪的IO事件(例如可读、可写、网络连接完成等),这些程序就可以很简单地使用一个线程高效地管理多个Channel
ChannelHandler及其实现类
- ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序
- ChannelHandler本身并没有提供很多方法,因为这个接口有很多的方法需要实现,使用期间可以继承它的子类
-
ChannelInboundHandler用于处理入站IO事件
-
ChannelOutboundHandler用于处理出站IO操作
//适配器
-
ChannelInboundHandlerAdapter用户处理入站IO事件
-
ChannelOutboundHandlerAdapter用于处理出站IO操作
-
ChannelDuplexHandler用于处理入站和出站事件
对常用方法的介绍(部分未列出):
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
//通道就绪事件
void channelActive(ChannelHandlerContext ctx) throws Exception;
//通道不活跃事件
void channelInactive(ChannelHandlerContext ctx) throws Exception;
//通道读取数据事件
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
//通道读取完毕事件
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
//异常捕获事件
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
执行顺序:handlerAdded->channelRegistered->channelActive->channelReadComplete->channelInactive->channelUnregistered->handlerRemoved
Pipeline和ChannelPipeline的介绍
-
ChannelPipeline是Handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链(ChannelPipeline是保存ChannelHandler的list,用于处理或拦截Channel的入站事件和出站操作)
-
ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及channel中各个ChannelHandler如何相互交互
-
在Netty中每个Channel都有且仅有一个ChannelPipeline与之对应,它们的组成关系如下:
说明:
一个channel包含了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个ChannelHandlerContext中又关联着一个ChannelHandler
入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。
常用方法
ChannelPipeline addFirst(ChannelHandler... handlers);//把一个业务处理类(handler)添加到链中的每一个位置
ChannelPipeline addLast(ChannelHandler...handlers);//把一个业务处理类(handler)添加到链中的最后一个位置
ChannelHandlerContext介绍
-
保存Channel相关的所有上下文信息,同时管理一个ChannelHandler对象
-
ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用
-
常用方法
ChannelFuture close();//关闭通道 ChannelOutboundInvoker flush();//刷新 ChannelFuture writeAndFlush(Object msg);//将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
ChannelOption介绍
Netty在创建Channel实例后,一般都需要设置ChannelOption参数:
ChannelOption.SO_BACKLOG
对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务器端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
ChannelOption.SO_KEEPALIVE
一直保持连接活动状态
EventLoopGroup和实现类NioEventLoopGroup的介绍
-
EventLoopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护者一个Selector实例
-
EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在服务端编程中,我们一般需要提供两个EventLoopGroup,BossEventLoopGroup和WorkerEventLoopGroup
-
通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理,
说明:
BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector实例,BossEventLoop不断轮询Selector将连接事件分离出来。然后将接收到的SocketChannel交给WorkerEventLoopGroup。WorkerEventLoopGroup会由next选择其中一个EventLoop来将SocketChannel注册到其维护的Selector,并对其后续的IO事件进行处理。
常用方法:
public NioEventLoopGroup();//构造方法
public Future<?> shutdownGracefully();//断开连接,关闭线程