来自Channel底层的IO事件,会转发给pipeline来处理,另一方面用户直接调用Channel的IO方法也是通过pipeline达到底层。因此pipepline是Netty内核与业务层之间的传送带,是一个双向的IO事件通道,其中从业务层往底层方向叫"outbound",从底层通往上层,叫“inbound"。
Pipeline本质上是由一个一个ChannelHandler节点组成的双向链表,其中既有Netty预置的节点,也有用户代码添加的节点。每个节点处理特定IO事件,或者将IO事件进行转换再传递给下一个节点。Pipline是典型的责任链模式,方便适应各种业务场景和数据格式。
Pipeline是Netty事件驱动模型的另-个核心抽象(第一个是EventLoop),在这个模型中,所有的IO相关操作,无论是数据读写,还是其他Socket管理操作,Netty都把它当做事件来传递。
这里并没有一个所谓Event类型(据说Netty3是有的,Netty4出于性能原因去掉了),事件传递就是指接口方法调用;在面向对象的世界里,方法调用和事件传递这两个概念本就可以混用。
我们了解一下围绕Pipeline的几个核心抽象概念。
ChannelInboundInvoker表示处理Inbound事件的调用入口,而ChannelOutboundInvoker则表示发起Outbound事件的调用入口。
public interface ChannelInboundInvoker {
ChannelInboundInvoker fireChannelRegistered();
ChannelInboundInvoker fireChannelUnregistered();
ChannelInboundInvoker fireChannelActive();
ChannelInboundInvoker fireChannelInactive();
ChannelInboundInvoker fireExceptionCaught(Throwable cause);
ChannelInboundInvoker fireUserEventTriggered(Object event);
ChannelInboundInvoker fireChannelRead(Object msg);
ChannelInboundInvoker fireChannelReadComplete();
ChannelInboundInvoker fireChannelWritabilityChanged();
}
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception;
}
这些接口方法的名字都非常好地表达了它的意思,InBound事件和OutBound事件的触发方法形式有明显的区别:
这两个接口都叫Invoker,表示它是主动触发者,但是不是处理者,handler才是处理器,名字和功能对应得非常到位。
现在可以看看ChannelPipeline的接口定义:
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable> {
//在头部添加一个ChannelHandler
ChannelPipeline addFirst(String name, ChannelHandler handler);
//在头部添加一个ChannelHandler,且指定该handler在group内执行
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
//此处省略了addLast,addBefore,addAfter
//移除handler
ChannelPipeline remove(ChannelHandler handler);
//按名字移除handler
ChannelHandler remove(String name);
//此处省略了removeFirst,removeLast,replace
//头部handler
ChannelHandler first();
//头部节点
ChannelHandlerContext firstContext();
//获取对应handler的节点
ChannelHandlerContext context(ChannelHandler handler);
//Pipline所属的Channel
Channel channel();
//fireXXX方法来自ChannelInboundInvoker,重新声明以修改返回值类型
@Override
ChannelPipeline fireChannelRegistered();
//其他fire方法声明
...
}
上面的的代码省略了一些功能类似的方法的声明,它主要包含以下几类方法:
增删改查
接口,可以给handler赋予一个有意义的name,便于管理;虽然我们一直认为Channel PipeLine应该在限定在单个EventGroup内的单个EventLoop内执行,但是Pipeline显然支持更灵活的形式。方法addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
说明了这一点。
ChannelHandlerContext是Pipeline的内部节点类型,它的接口定义如下:
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
//所属的Channel
Channel channel();
//所属的pipeline
ChannelPipeline pipeline();
//handler应该执行的EventExecutor
EventExecutor executor();
//所包装的ChannelHandler
ChannelHandler handler();
//该节点是否已经从pipeline移除
boolean isRemoved();
}
ChannelHandlerContext有三个作用:
我们注意到:ChannelHandlerContext和ChannelPipeline一样,都扩展自ChannelInboundInvoker和ChannelOutboundInvoker接口,这是很容易理解的,因为pipeline基本上将所有IO事件都转发给它的节点。 但二者表达的语义是不一样的,pipeline作为invoker调动整个流水线来处理事件;而ChannelHandlerContext作为invoker,调用本节点(及后续节点)来处理事件。
ChannelHandler是事件处理器接口,它的定义如下:
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
这一层ChannelHandler定义的方法不多,其功能可望文生义。
它定义了一个Sharable注解,标注那些可以被共享的handler,被共享意味着该handler可以被添加到多个pipeline,或在一个pipeline中添加多次。一般handler如果是无状态的,或做好了相关状态管理机制,才可被共享。
ChannelHandler有两个重要的子接口,第一个是ChannelInboundHandler,代表处理Inbound事件的handler,它的定义如下:
public interface ChannelInboundHandler extends ChannelHandler {
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 userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
ChannelInboundHandler与ChannelInboundInvoker相呼应。而另一个子接口ChannelOutboundHandler,代表处理Outbound事件的handler,与ChannelOutboundInvoker相呼应。
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception;
}
非常幸运,pipeline的继承层次没有这么复杂,具体类DefaultChannelPipeline直接实现了它,并且被大部分Channel直接使用,所以我们只需要掌握DefaultChannelPipeline就够了。
先看看该类的重要成员字段:
public class DefaultChannelPipeline implements ChannelPipeline {
//内部双向链表的Head节点,和tail节点
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
//所属channel
private final Channel channel;
//由于handler可以在多个EventExecutorGroup内执行,这里记住每个EventExecutorGroup所选择的EventExecutor
private Map childExecutors;
//估计数据对象尺寸的handler,由ChannelConfig提供
private volatile MessageSizeEstimator.Handle estimatorHandle;
}
再看构造方法:
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
//创建head和tail节点
tail = new TailContext(this);
head = new HeadContext(this);
//头尾相连,构成双向链表
head.next = tail;
tail.prev = head;
}
我们以addFirst为例,看一下pipeline handler链管理的相关方法,
@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//检查handler是否被多次添加,对于非sharable的handler,是不允许的
checkMultiplicity(handler);
//检查nam的重复,如果name=null,生成一个,
name = filterName(name, handler);
//实际就是创建DefaultChannelHandlerContext,一个链表节点
newCtx = newContext(group, name, handler);
//纯粹链表操作,看代码即可
addFirst0(newCtx);
//如果Channel此时还没register(到EventLoop),那么handler的HandlerAdded回调不能执行,callHandlerCallbackLater创建延迟的回调任务
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
//到这里,说明Channel已经完成register,执行HandlerAdded回调
//当前不在对应的EventLoop内,执行callHandlerAddedInEventLoop
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
//当前在对应的EventLoop内,直接执行HandlerAdded回调
callHandlerAdded0(newCtx);
return this;
}
private void addFirst0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext nextCtx = head.next;
newCtx.prev = head;
newCtx.next = nextCtx;
head.next = newCtx;
nextCtx.prev = newCtx;
}
callHandlerAdded0方法其实就是执行ChannelHandher.callHandlerAdded,以及一些异常处理逻辑;而callHandlerAddedInEventLoop,在指定的EventLoop执行callHandlerAdded0,它们的代码就不贴了。
触发inbound事件的方法来自ChannelInboundInvoker接口,以fireChannelActive为例:
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
出人意料地简单,直接传递给head的invokeChannelActive方法节点即可;其他fireXXX方法的实现也是类似地专递给节点的invokeXXX方法。
再看outbound事件,以connect为例:
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
return tail.connect(remoteAddress);
}
转发给tail节点即可;其他诸如bind,write,close,flush,read都是类似的。
pipeline处理inbound事件的实现是交给head节点,处理oubound事件是交给tail节点,这意味着inbound事件,从head–>tail方向传递outbound事件,从tail–>head方向传递。
DefaultChannelHandlerContext是ChanelPipeline使用的默认节点类型,它本身没什么逻辑,全部在基类AbstractChannelHandlerContext里。
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
//前驱和后驱节点指针
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
//节点状态,指handlerAdded回调尚未调用
private static final int ADD_PENDING = 1;
//节点状态,指handlerAdded回调已经调用
private static final int ADD_COMPLETE = 2;
//节点状态,指handlerRemove回调已经调用,节点已经从pipepline移除
private static final int REMOVE_COMPLETE = 3;
//节点状态,默认初始
private static final int INIT = 0;
//节点所属的pipeline
private final DefaultChannelPipeline pipeline;
//节点名字
private final String name;
//一个标记,执行节点的EventLoop按顺序执行事件,也即扩展自OrderedEventExecutor接口
private final boolean ordered;
//一个关于ChannelHandler处理事件的未掩码,通过这个掩码pipeLine能知道handler能处理那些事件,进而跳过不必要的节点,提高执行效率
private final int executionMask;
//执行handler的EventLoop,可为null,即使用Channel注册的executor
final EventExecutor executor;
private ChannelFuture succeededFuture;
//如果handler需要在其他executor执行,需要将执行逻辑封装为一个task
private Tasks invokeTasks;
//初始化state
private volatile int handlerState = INIT;
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
String name, Class extends ChannelHandler> handlerClass) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.executionMask = mask(handlerClass);
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
}
前面已经展示了,pipeline调用head节点的invokeXXX来处理inbound事件,我们来看AbstractChannelHandlerContext如何实现相关方法,以ChannelActive事件为例。
//静态方法,让handler在对应的EventLoop内执行
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
//下面是老套路,如果当前线程在所属eventLoop内,就地执行,否则包装为一个task提交给eventLoop。
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
//执行handler方法处理事件
private void invokeChannelActive() {
//依据节点状态判断是否执行
if (invokeHandler()) {
try {
//调用handler.channelActive方法
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
//否则继续传递事件
fireChannelActive();
}
}
//ADD_COMPLETE状态的节点,才执行handler;
// (!ordered && handlerState == ADD_PENDING) 这个条件暂时没理解什么意思
private boolean invokeHandler() {
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
//向下一个节点传递ChannelActive事件
@Override
public ChannelHandlerContext fireChannelActive() {
invokeChannelActive(findContextInbound(MASK_CHANNEL_ACTIVE));
return this;
}
//顺着Inbound方向,搜寻下一个能处理对应事件的handler节点
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while ((ctx.executionMask & mask) == 0);
return ctx;
}
上面代码虽然不长,信息量挺大:
我们可以提前瞥一眼ChannelInboundHandlerAdapter(InboundHandler抽象基类)的channelActive方法实现:没有做任何处理,调用ChannelHandlerContext.fireChannelActive传递给下一个节点。
public class ChannelInboundHandlerAdapter {
...
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
...
}
outbound请求是从tail节点开始处理的,看AbstractChannelHandlerContext如何实现connect,相关方法如下:
@Override
public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
//找到第一个第一个能处理connect的节点
final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
//老套路,不解释
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null, false);
}
return promise;
}
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while ((ctx.executionMask & mask) == 0);
return ctx;
}
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
//调用handler的connect方法来处理请求
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
connect(remoteAddress, localAddress, promise);
}
}
//这个所谓safeExecute,只是为了适应lazy参数,只有AbstractEventExecutor支持lazyExecute
//还记得否?lazyExecute是指如果当前EventLoop处于阻塞状态,暂时不唤醒,等有其他task激活Eventloop再执行
private static boolean safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg, boolean lazy) {
try {
if (lazy && executor instanceof AbstractEventExecutor) {
((AbstractEventExecutor) executor).lazyExecute(runnable);
} else {
executor.execute(runnable);
}
return true;
} catch (Throwable cause) {
try {
promise.setFailure(cause);
} finally {
if (msg != null) {
ReferenceCountUtil.release(msg);
}
}
return false;
}
}
执行的套路和inbound是类似的,但是有一个明显差别,inbound从head节点开始,头节点处理不了再传递给后继节点;而outbound一上来就查找下一个节点,实际上跳过了tail节点(这只是一个性能优化)。
上面看到connect请求是由OutboundHandler来处理的,但是我们编写netty程序,从来不会编写处理connect请求的handler;大家应该能想到肯定是Netty内置了一个这样的handler,确实是这样,这个handler就是pipeline的头节点:HeadContext。
HeadContext是DefaultChannelPipeline的内部类,定义如下:
//HeadContext即是ChannelHandlerContext,也是Channel(In/Out)boundHandler,它没有其他用途,实现更紧凑
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
//持有Channel的unsafe
private final Unsafe unsafe;
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, HeadContext.class);
unsafe = pipeline.channel().unsafe();
//初始化就进入ADD_COMPLETE状态,无论Channel注册与否
setAddComplete();
}
//Outbound请求处理
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
unsafe.bind(localAddress, promise);
}
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) {
unsafe.connect(remoteAddress, localAddress, promise);
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
unsafe.disconnect(promise);
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
unsafe.close(promise);
}
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
unsafe.deregister(promise);
}
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
unsafe.write(msg, promise);
}
@Override
public void flush(ChannelHandlerContext ctx) {
unsafe.flush();
}
//Inbound事件处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.fireExceptionCaught(cause);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
...
}
可见Head节点处理事件的方式如下:
TailContext是pipeline tail节点的实现,它是一个占位符型的节点,没有具体逻辑。
ChannelHandler在Netty这个事件驱动框架中,担任事件处理器的角色。我们实际写代码的时候,很多情况都是在编自定义的handler。
Netty提供了ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter两个基类,作为实现具体handler的基础。
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
@Skip
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
@Skip
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
//其他ChannelInboundHandler方法的实现都是类似的
}
有了上面的知识,这个类的功能很容易看明白,对于所有的inbound事件,不做任何处理,转发给下一个节点。@Skip注解标注该时间处理方法可以跳过,提高pipline的性能。
ChannelOutboundHandlerAdapter的实现,如出一辙:
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
@Skip
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
@Skip
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
//其他ChannelOutboundHandler方法的实现都是类似的
}
一个肩负具体功能的ChannelHandler,一般仅对某个或某几个事件感兴趣,对于不管兴趣的事件传递给下一个节点即可,从Channel(In/out)boundHandlerAdapter继承刚好满足该需求。
ChannelDuplexHandler代表一个双向的ChannelHandler,它的定义如下:
public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {
@Skip
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
//其他ChannelOutboundHandler方法
}
由于java没有多继承,它只能继承ChannelInboundHandlerAdapter,并将ChannelOutboundHandlerAdapter的功能复制一遍。
Netty为用户预置了很多ChannelHandler,可以开箱即用,大体分为以下几类:
用户实现自定义Handler最常见的功能需求是数据的编解码,实现业务数据和字节数据之间的互相转换;编码器一般叫xxxEncoder,解码器叫xxxDecoder,而同时实现编解码功能的handler一般叫xxxCodec。
比如LengthFieldBasedFrameDecoder和LengthFieldPrepender,分别实现了(长度+body)格式消息解码器和编码器。
应用层网络协议
比如HttpServerCodec(它同时也是一个针对http对象的编解码器)实现对http消息的处理,而WebSocketServerProtocolHandler实现了websocket握手协议;这样一来,用户可以直接处理上层协议数据对象,而不需要处理协议细节。
状态检测
比如IdleStateHandler,能够检测通道的空闲状态(无读、写持续一段时间),发出一个IdleStateEvent事件到Pipeline的下一个节点,我们可以通过另外一个handler来处理该事件,比如可认为通道已经失效,或发送心跳包。
更多的Handler大家请阅读源码里面的示例,基本上很多偏底层的功能Netty都有一些预定义handler能用上
这里倒不是为了刻意介绍WebSocket,而是想以“基于Netty构建WebSocket服务端”来体验本章学习的知识。
WebSocket是基于HTPP协议工作的,HTTP本来是短连接模式(每个请求建立一个连接,完成后就断开),而WebSocket协议在能够将短连接升级为长连接。换句话说,WebSocket通过HTTP协议的格式来传输数据,客户端和服务端之间通过握手数据包约定升级为长连接。
使用Netty启动WebSocket的示例代码如下:
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(5);
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65536));
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/", (String)null, true));
ch.pipeline().addLast(new CustomHandler());
}
})
bootstrap.bind(port).sync();
这四个handler组成了pipeline(还有隐藏的Head和Tail),读数据的时候,底层字节流按inbound方向,字节数据经过HttpServerCodec、HttpObjectAggregator、WebSocketServerProtocolHandler,到达CustomHandler的时候已经变成了WebSocketFrame的形式。而写数据的时候,WebSocketFrame按outhound方向,经过WebSocketServerProtocolHandler、HttpObjectAggregator、HttpServerCodec,变成字节流注入底层socket。
通过websocket的配置案例可知,netty对应用层协议的支持,就是通过提供对应的ChannelHandler来实现的;而我们自己的Handler一般放在最后面,用来处理应用层协议数据。如果我们使用完全自定义的数据协议,那么我们的handler就需要直面字节流了,实现编解码功能了。下一章节住专门讲解“编解码器”。