文章目录
原博文,点击这里
调用顺序
代码:NettyPro中protocoltcp模块:Git HUb地址,点击这里
这里的L:表示Local
R:表示为Remote
消息的传递
编解码
对于编解码器使用的是out
,进行消息的传递
Handler
对于Handler使用的是:
1.ChannelHandlerContext#fireChannelActive()
传递到下一个处理器
2.ctx.writeAndFlush与ctx.channel().writeAndFlush
传递到出站处理器。
1.Channel
定义:一个Channel表示一个通道,跟io中的stream的角色一样,但是有几点不同。 1. stream是单向的,只支持读或者写,channel是双向的,既支持读也支持写。2. stream是阻塞的,channel是并行的。
其中Channel的生命周期状态如下:
状态说明 | 说明 |
---|---|
channelUnregistered | channel创建之后,还未注册到EventLoop |
channelRegistered | channel注册到了对应的EventLoop |
channelActive | channel处于活跃状态,活跃状态表示已经连接到了远程服务器,现在可以接收和发送数据 |
channelInactive | channel未连接到远程服务器 |
一个Channel正常的生命周期如下:
channelRegistered -> channelActice -> channelInactive -> channelUnregistered
在另外一种特殊情况下,会发生多次channelRegistered和channelUnregistered,这是因为用户可以从EventLoop上取消注册Channel来阻止事件的执行并在之后重新注册。状态变化如下:
2.ChannelHandler
ChannelHandler有2种类型:
1.Inbound Handler: 处理inbound数据(接收到的数据)以及所有类型的channel状态修改事件
2.Outbound Handler: 处理outbound数据(发送出去的数据)并且可以拦截各种操作,比如bind、connect、disconnect、close、write等操作
Inbound和Outbound Handler都属于ChannelHandler,它们都可以被添加到ChannelPipeline中,它们内部也提供了handlerAdded、handlerRemoved这两种方法分别在ChannelHandler添加到ChannelPipeline和ChannelHandler从ChannelPipeline中被删除的时候触发。
2.1ChannelInboundHandler
ChannelInboundHandler方法在两种情况下触发:channel状态的改变和channel接收到数据。
一些方法说明:
方法名 | 描述 |
---|---|
channelRegistered(…) | Channel注册到EventLoop,并且可以处理IO请求 |
channelUnregistered(…) | Channel从EventLoop中被取消注册,并且不能处理任何IO请求 |
channelActive(…) | Channel已经连接到远程服务器,并准备好了接收数据 |
channelInactive(…) | Channel还没有连接到远程服务器 |
channelReadComplete(…) | Channel的读取操作已经完成 |
channelRead(…) | 有数据可以读取的时候触发 |
userEventTriggered(…) | 当用户调用Channel.fireUserEventTriggered方法的时候触发,用户可以传递一个自定义的对象当这个方法里 |
ChannelInboundHandler有一个实现ChannelInboundHandlerAdapter
,它实现了所有的方法,我们只需要继承这个类然后复写需要的方法即可。
ChannelInboundHandler中的channelRead方法中有读取的ByteBuf。由于Netty在ByteBuf的使用上使用了池的概念,当不需要这个ByteBuf的时候需要进行资源的释放以减少内存的消耗。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// do something
ReferenceCountUtil.release(msg);
}
Netty内部提供了一个SimpleChannelInboundHandler
类,这个类读取数据会自动释放资源。它继承ChannelInboundHandlerAdapter并复写了channelRead方法,在channelRead方法里面finally代码里会自动release资源,并提供了channelRead0方法:
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
// do something, do not need release
}
2.2选择和使用
所以一般使用ChannelInboundHandler的话,有3种方法。
直接实现ChannelInBoundHandler接口
继承ChannelInboundHandlerAdapter
继承SimpleChannelInboundHandler
第1种基本不用,第3种用来处理接收消息,第2种用来处理事件状态的改变
3.ChannelOutboundHandler
ChannelOutboundHandler方法在两种情况下触发:发送数据和拦截操作。
ChannelOutboundHandler有一个强大的功能,可以按需推迟一个操作,这使得处理请求可以用到更为复杂的策略。比如,如果写数据到远端被暂停,你可以推迟flush操作,稍后再试。
一些方法说明:
方法名 | 描述 |
---|---|
bind(…) | 请求绑定channel到本地地址 |
connect(…) | channel连接到远程地址 |
disconnect(…) | channel从远程服务器上断开 |
close(…) | 关闭channel |
deregister(…) | 取消channel在eventloop上的注册 |
read(…) | 在channel中读数据 |
flush(…) | flush数据到远程服务器 |
write(…) | 写数据到远程服务器 |
ChannelOutboundHandler有一个实现ChannelOutboundHandlerAdapter,它实现了所有的方法,我们只需要继承这个类然后复写需要的方法即可。
在outboundhandler中有时候也需要释放资源,当消息被消费并且不再需要传递给下一个outbound handler的时候,调用ReferenceCountUtil.release(message)释放消息。
当消息被写回去或者channel关闭的时候,这个消息的资源会被自动释放,所以没有一个类似SimpleChannelInboundHandler的概念。
ChannelHandlerContext
当ChannelHandler被添加到ChannelPipeline中的时候,ChannelHandlerContext会被创建。
所以说ChannelHandlerContext属于ChannelHandler。
可以通过ChannelHandlerContext的channel方法得到Channel和pipeline方法得到ChannelPipeline。
ChannelHandlerContext可以被保留下来并且在其他地方进行调用,比如在其他线程,或者在handler外部进行调用。
可以使用以下方法保留ChannelHandlerContext:
class WriterHandler extends ChannelHandlerAdapter {
private ChannelHandlerContext ctx;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
public void send(String msg) {
ctx.write(msg);
}
}
Netty中提供了一个@Sharable注解用来将一个实例的ChannelHandler添加到多个ChannelPipeline中,如果不加上这个注解,被多个ChannelPipeline使用的话会抛出异常。
ChannelPipeline
多个ChannelHandler可以组成一个ChannelPipeline,里面的每个ChannelHandler可以转发给下一个ChannelHandler。
ChannelPipeline内部的所有ChannelHandler的处理流程图:
ChannelPipeline提供了多种方法用于添加或删除或代替ChannelHandler,比如addLast, addFirst, remove, replace等方法。