1、BootStrap,ServerBootStrap
- 一个Netty应用通常由一个BootStrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中的BootStrap类是客户端程序的启动引导类,ServerBootStrap是服务端启动引导类
- 常见方法:
public ServerBootstrap group (EventLoopGroup parentGroup,
EventLoopGroup childGroup)
作用于服务器端,用来设置两个EventLoop
public B group(EventLoopGroup group)
作用于客户端,用来设置一个EventLoopGroup
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)
childHandler主要针对于服务器端为客户端划分;
public B handler(ChannelHandler handler)
Handler则在服务器端本身发挥作用
public ChannelFuture bind(int inetPort)
用于服务器,设置占用的端口号
public ChannelFuture connect (String inetHost,int inetPort)
该方法用于客户端,用来连接服务器
2、Future,ChannelFuture
- Netty中所有操作都是异步的,不能立即得知消息是否被正确处理,但可以过一会等它执行完成或直接注册一个监听器,具体实现通过Future和ChannelFuture,它们可以注册一个监听,当操作执行成功或失败时,监听会自动触发注册的监听事件
- 常用方法:
Channel channel()
返回当前正在进行IO操作的通道
ChannelFuture sync()
等待异步操作执行完毕
3、Channel
- Channel是Netty网络通信组件,能够用于执行网络IO操作
- 通过Channel可获得当前网络连接的通道状态
- 通过Channel可获得网络连接配置参数
- Channel提供异步的网络IO操作(建立连接,读写,绑定端口),异步调用意味着任何IO调用都将立即返回,但不保证在调用结束时所请求的IO操作已完成
- 调用立即返回一个ChannelFuture实例,通过注册监听器,可以在IO操作成功、失败或取消时回调通知调用方
- 支持关联IO操作与对应的处理程序
- 不同协议、不同的阻塞类型的连接是不同的,Channel类型与之对应,常用的Channel类型有:
NioSocketChannel
异步的客户端TCP socket连接
NioServerSocketChannel
异步的服务器TCP socket连接
NioDatagramChannel
异步的UDP连接
NioSctpChannel
异步的客户端Sctp连接
NioSctpServerChannel
异步的服务端Sctp连接
4、Selector
- Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件
- 当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询(select)这些Channel中是否有就绪的IO事件(可读、可写、完成网络连接等),这样程序就可以简单地使用一个线程高效地管理多个Channel
5、ChannelHandler及其实现类
- ChannelHandler是一个接口,处理IO事件 / 拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序
- ChannelHandler本身并没有提供很多方法,因为这个接口有许多方法需要实现,方便使用的时候,可以继承它的子类
- ChannelHandler及其实现类:
接 口
ChannelInboundHandler 用于处理Channel(通道)入站IO事件; ChannelOutboundHandler 用于处理Channel(通道)出站IO事件; 适 配 器 ChannelOutboundHandlerAdapter 用于处理出站IO操作; ChanneInboundHandlerAdapter 用于处理入站IO操作; ChannelDuplexHandler 用于处理入站和出站事件。
【注】以客户端应用程序为例:如果事件运动方向是客户端服务器,我们称之为“出站”,即客户端发送的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之称为“入站”。
4、自定义handler类继承ChannelInboundHandlerAdapter,通过重写相应方法实现业务逻辑:
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelRegistered(); } public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelUnregistered(); } //通道就绪事件 public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelActive(); } public void channelInactive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelInactive(); } //通道读取数据事件 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.fireChannelRead(msg); } //读取数据完毕 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelReadComplete(); } public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { ctx.fireUserEventTriggered(evt); } public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelWritabilityChanged(); } //异常捕获事件 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); } } |
6、Pipeline和ChannelPipeline
- ChannelPipeline是一个handler的集合,负责处理和拦截inbound / outbound的事件或操作。ChannelPipeline是ChannelHandler实例对象的链表
- ChannelPipeline实现了一种高级形式的拦截过滤器模式,用户可以完全控制事件的处理方式,以及Channel中各个ChannelHandler的交互模式
- 一个Channel包含了一个ChannelPipeline,而一个ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,且每个ChannelHandlerContext中由关联了一个ChannelHandler;
- 入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到第一个出站的handler,两者互不干扰。
...
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("MyHttpServerCC",new HttpServerCodec());
//增加一个自定义的处理器
pipeline.addLast("MyHttpServerHandler",new TestHttpServerHandler());
断点 System.out.println("ok");
}...
这段代码中显然有两个handler对象HttpServerCodec()和TestHttpServerHandler(),通过debug来观察ChannelPipeline构成的双向链表:
显然,pipeline 对象中包含了一个head和tail,从head中进入,可以看到链表第一个元素为我们编写的初始化文件TestServerInitializer,它本质上也是一个自定义的handler;
而next(下一个)即为HttpServerCodec;
继续next,发现下一个handler为TestHttpServerHandler;
继续next,得到链表尾元素,即默认的DefultChannelPipeline$TailContext,其中不再包含handler;
反向从tail进入,可以发现末尾元素没有handler,且next属性为null,prev指向它的前一个handler,即TestHttpServerHandler;
TestHttpServerHandler的prev指向前一个handler,即HttpServerCodec;
HttpServerCodec的前一个handler则是链表中第一个元素,即TestServerInitializer。
【注】在TestHttpServerHandler中传入的ctx,本质上是DefaultChannelHandlerContext类型的数据
-
常用方法:
ChannelPipeline addFirst(String name, ChannelHandler handler);
把一个业务处理类(handler)添加到链表中第一个位置
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
把一个业务处理类(handler)添加到链表中最后一个位置
7、ChannelHandlerContext
- 保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象;
- ChannelHandlerContext中包含一个具体的ChannelHandler,同时ChannelHandlerContext中绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。
TestHttpServerHandler
......
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
System.out.println(ctx.getClass());
if (msg instanceof HttpRequest){
断点 System.out.println("ctx 类型为:"+ctx.getClass());
System.out.println("pipeline hashcode = "+ctx.pipeline()
.hashCode() + "TestHttpServerHandler hash = "
+this.hashCode());
System.out.println("msg 类型为:"+msg.getClass());
System.out.println("客户端地址为:"+ctx.channel()
.remoteAddress());
HttpRequest httpRequest = (HttpRequest) msg;
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("请求了 favicon.ico,不做响应");
return;
}
......
}
......
对以上代码进行debug,分析ctx的数据类型和包含内容
- 首先可以得出ctx的数据类型为DefaultChannelHandlerContext,ctx中包含的handler为TestHttpServerHandler
- 由于已经证明TestHttpServerHandler为最后一个执行的handler,故其位于双向链表表尾,next值为null。Prev指向前一个DefaultChannelHandlerContext,其包含的handler为HttpServerCodeC
- Ctx还关联了pipeline信息,通过pipeline还可以找到对应的channel
- 常用方法:
ChannelFuture close()
关闭通道
ChannelOutboundInvoker flush()
刷新
ChannelFuture writeAndFlush(Object msg)
将数据写入ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
8、ChannelOption
- Netty在创建Channel实例后,一般需要通过ChannelOption参数来配置channel的相关属性
- ChannelOption参数如下:
ChannelOption.SO_BACKLOG
对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
ChannelOption.SO_KEEPALIVE
一直保持连接活动状态
9、EventLoopGroup和其实现类NioEventLoopGroup
- EventLoopGroup本质上是一个接口(interface),继承了EventExecutorGroup,通过继承关系分析,发现EventLoopGroup的实现子类是MultithreadEventLoopGroup下的NioEventLoopGroup
- EventLoopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护了一个selector实例
- EventLoopGroup提供next接口,可以从组里按照一定规则获取其中一个EventLoop来处理任务。在Netty服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如BossEventLoopGroup和WorkerEventLoopGroup。
- 通常一个服务端口(ServerSocketChannel)对应一个Selector和一个EventLoop线程。BossEventLoopGroup负责接收客户端连接并将SocketChannel交给WorkerEventLoopGroup进行IO处理
- BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护了一个注册了ServerSocketChannel的Selector实例。BossEventLoopGroup不断轮询Selector将连接事件分离出来。
- 通常是OP_ACCEPT事件,然后将接收的SocketChannel交给WorkerEventLoopGroup。
- WorkerEventLoopGroup会由next选择其中一个EventLoop将这个SocketChannel注册到其维护的Selector并对其后续的IO事件进行处理
......
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
......
对上述代码段进行debug,容易得到bossGroup 的children中只包含了一个EventLoop,而 workerGroup 的children中包含了8个EventLoop,这是由于我们设置了bossGroup 为单线程,而workerGroup 默认为CPU核数*2的多线程。
运行服务器端代码后,多开客户端,并输出当前线程及channel
发现:前8个客户端连接的线程为[nioEventLoopGroup-3-1~3-8],其对应的channel也各不相同,第9个客户端连接的线程虽然为nioEventLoopGroup-3-1,但其对应的channel和第1个客户端也不相同。
这表明了WorkerEventLoopGroup会通过next方法选择其中的一个EventLoop,而每个EventLoop对应一个Selector不断轮询其队列中的IO请求并分配channel进行处理
- 常用方法:
Public NioEventLoopGroup(); 构造方法
Public Future<?>shutdownGracefully(); 断开连接,关闭线程
10、Unpolled类
- Unpolled类是Netty提供的专门用于操作缓冲区(即Netty的数据容器)的工具类
- 常用方法:通过给定的数据和字符编码返回一个ByteBuf对象:Public static ByteBuf copierBuffer(CharSequence string, Charset charset)
- Unpolled获取Netty数据容器ByteBuf的基本使用:
创建一个对象,该对象包含一个数组 array,是一个byte[10]
在Netty的buffer中,读取buffer中的数据不需要通过flip()方法进行状态切换,其底层维护了readerIndex 和 writerIndex
通过readerIndex、writerIndex 和 capacity,将buffer分割为三个区域
0——>readerIndex: | 已经读取过的区域
readerIndex——>writerIndex: | 可读但还未被读取的区域
writerIndex——>capacity: | 可写的区域
- 每调用一次byteBuf.readByte()读取数据,byteBuf的readerIndex便减少1;调用byteBuf.getByte()则不会引起readerIndex的变化
- public abstract CharSequence getCharSequence(int index, int length, Charset charset) 的作用是按照某一个范围进行数据的读取,index表示起始位置,length表示读取长度,charset表示字符编码格式