设计模式——责任链模式:责任链模式(Chain of Responsibility Pattern)为请求创建了一个处理对象的链。发起请求和具体处理请求的过程进行解耦:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递。
实现责任链模式需要4个要素:处理器抽象类,具体的处理器实现类,保存处理器信息,处理执行
那么在netty中又是如何使用责任链的呢?首先所有处理器的信息保存在Pipeline管道,创建channel的时候会自动创建资格专有的pipeline入站事件和出站操作会调用pipeline上的处理器
入站事件和出站事件
入站事件通常是指I/O线程生成了入站数据。(通俗理解:从socket底层自己往上冒上来的事件都是入站)比如EventLoop收到selector的OP_READ事件,入站处理器调用SocketChannel.read(ByteBuffer),接收到数据后,这将导致通道的ChannelPipeline中包含的下一个中的channelRead方法被调用。
出站事件:经常指I/O线程执行实际的输出操作。(通俗理解:想主动往socket底层操作的事件都是出站事件)。比如bind方法用意是请求server socket绑定到给定的SocketAddress,这将导致通道的ChannelPipeline中包含的下一个出站处理器中的bind方法被调用。
在netty中有很多事件的定义:
而Pipeline中的Handler是什么呢?
ChannelHeadler:用于处理I/O事件或者拦截I/O操作,并转发到ChannelPipeline中下一个处理器。这个顶级接口定义功能很弱,实际使用时会去实现下面两大子接口:处理入站I/O事件的ChannelInboundHandler、处理出站I/O操作的ChannelOutboundHandler
适配器类:为了方便开发,避免所有handler去实现一遍接口方法,Netty提供了简单的实现类:
- ChannelInboundHandlerAdapter处理入站I/O事件
- ChannelOutboundHandlerAdapter处理出站I/O事件
- ChannelDuplexHandler支持同时处理入站和出站事件
ChannelHandlerContext:实际存储在Pipeline中的并非是ChannelHandler,而是上下文对象。将Handler包裹在上下文对象中,通过上下文对象与它所属的ChannelPipeline交互,向上或向下传递事件或者修改pipeline都是通过上下文对象。
那么如何维护Pipeline中的handler呢?ChannelPipeline是线程安全的,ChannelHandler可以在任何时候添加或者删除。例如你可以在即将交换敏感信息时插入加密处理程序,并在交换后删除它。一般操作,初始化的时候增加进去,较少删除。下面是Pipeline中管理的API
通道创建时会会构建一个Pipeline,同时里面会保留链的头和尾(HeadContext-TailContext),跟着源码中注册方法register(),首先从bind()方法开始如何绑定端口,初始化的事件会进入register()方法,首先创建通道,
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();// Tony: 1.创建/初始化ServerSocketChannel对象,并注册到Selector
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// Tony: 等注册完成之后,再绑定端口。 防止端口开放了,却不能处理请求
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);// Tony: 实际操作绑定端口的代码
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();//2.创建通道
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// Tony: (一开始初始化的group)MultithreadEventLoopGroup里面选择一个eventLoop进行绑定
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
//3.初始化通道的参数,如网络参数,自定义属性
void init(Channel channel) throws Exception {
Map<ChannelOption<?>, Object> options = this.options0();
synchronized(options) {
setChannelOptions(channel, options, logger);
}
Map<AttributeKey<?>, Object> attrs = this.attrs0();
synchronized(attrs) {
Iterator var5 = attrs.entrySet().iterator();
while(true) {
if (!var5.hasNext()) {
break;
}
Entry<AttributeKey<?>, Object> e = (Entry)var5.next();
AttributeKey<Object> key = (AttributeKey)e.getKey();
channel.attr(key).set(e.getValue());
}
}
//4.获取到ChannelPipeline对象
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = this.childGroup;
final ChannelHandler currentChildHandler = this.childHandler;
final Entry[] currentChildOptions;
synchronized(this.childOptions) {
currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(0));
}
final Entry[] currentChildAttrs;
synchronized(this.childAttrs) {
currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(0));
}
//5.ChannelInitializer是一个特殊的handler,一般就是在registered之后,
//执行一次,然后销毁。用于初始化channel,此时Pipeline中只有头和尾,说明头和尾
//是Pipeline是创建时自带的。此时加在最后,最后的意思不是链表尾部,链表的尾部一定
//是Tail,此时添加了一个ChannelInitializer
p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
//触发ChannelInitializer时,收到注册成功的事件后,就会执行initChannel方法
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = ServerBootstrap.this.config.handler();
if (handler != null) {
pipeline.addLast(new ChannelHandler[]{handler});
}
ch.eventLoop().execute(new Runnable() {
public void run() {
pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
}
});
}
}});
}
从继承ChannelInboundHandlerAdapter可以看到这个类是处理入站事件的处理器,它其中有一个channelRegistered()方法,当Netty底层散发出一个regist事件的时候回将其触发调用,因为regist是一个入站事件。但是如果handlerAdded()方法被执行了,这个方法就不会被调用,因为initChannel被调用之后,这个handler就会被移除,也就是初始化完成后这个handler会被移除,映照了pipeline上的handler是可以动态的增加和删除,查看源码handlerAdded()方法会先判断这个通道是否已经注册,注册完成后,就会对通道进行初始化
@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
// We use a ConcurrentMap as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
// ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
private final ConcurrentMap<ChannelHandlerContext, Boolean> initMap = PlatformDependent.newConcurrentHashMap();
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
protected abstract void initChannel(C ch) throws Exception;
@Override
@SuppressWarnings("unchecked")
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
// the handler.
if (initChannel(ctx)) {// Tony: 收到注册成功的事件,先执行initChannel(ctx),在执行方法重载的initChannel(ch)
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
// miss an event.
ctx.pipeline().fireChannelRegistered();
} else {
// Called initChannel(...) before which is the expected behavior, so just forward the event.
ctx.fireChannelRegistered();
}
}
/**
* Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (logger.isWarnEnabled()) {
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
}
ctx.close();
}
/**
* {@inheritDoc} If override this method ensure you call super!
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
// This should always be true with our current DefaultChannelPipeline implementation.
// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
// will be added in the expected order.
initChannel(ctx);
}
}
@SuppressWarnings("unchecked")
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
try {// Tony: 这个init方法一般就是创建channel时,实现的那个initchannel方法
initChannel((C) ctx.channel());
} catch (Throwable cause) {
// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
// We do so to prevent multiple calls to initChannel(...).
exceptionCaught(ctx, cause);
} finally {// Tony: ChannelInitializer执行结束之后,会把自己从pipeline中删除掉,避免重复初始化
remove(ctx);
}
return true;
}
return false;
}
private void remove(ChannelHandlerContext ctx) {
try {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
} finally {
initMap.remove(ctx);
}
}
}
初始化完成后,此时pipeline会有三个handler,除了头和尾还有一个ServerBootstrap匿名实现类用来初始化,从断点中可以清晰的看出
接下来会调用register注册方法,会在group中选取一个EventLoop然后调用其register,实际在调用register方法就是再去调用channel的Unsafe().register()方法,也就是调用AbstractChannel对象的register方法,在这个方法中首先拿到EventLoop,eventloop默认是不执行的,只有当有任务提交时才执行,所以此时理论上eventloop没有执行,所以会以一个任务提交的方式执行,调用EventLoop的execute()方法,这个方法首先判断方法的调用者是不是EventLoop同一个线程,并将其增加到队列中,不是同一个线程则调用启动方法吗,启动完成后,因为EventLoop是没有线程的,提交之后由线程创建器executor创建线程执行run方法,run方法就是在不断轮询selector中的事件,处理任务队列里面的任务。register就是将NIO的channel和EventLoop里面的selector进行绑定,初始化完成后负责初始化的handler会被移除,此时责任链上还会存在配置中写的handler和处理Acceptor连接事件的handler,此时有4个handler,而头部handler不负责处理实际事件,而是去寻找下一个入站事件的handler,一般为loginhandler(日志记录),然后继续传播,直到一个inboundhandler不继续传播或者进入Tail,
除了register方法还有bind方法,bind方法时出站事件执行顺序和入站事件相反
请求过来以后又是如何处理的呢?我们通过Accept事件获取请求,所以我们应该去看accept入站事件是如何处理的,
首先需要关注的是EventLoop里面的run方法,看run方法中做了什么 ,因为是它在不断轮询,从而散播出事件,拿到事件后调用processSelectedKeys
然后通过processSelectedKeysPlain()方法处理事件,然后取到selectedKey后,去判断事件类型,当为read和accept事件时会调用Unsafe.read();
当服务器收到这样一个accept事件后,EventLoop回去将具体的连接,也就是serversocket对象,变为NIOserversocket对象(netty中socket对象),此时通过轮询得到新链接了,然后散发出read事件,开始责任链的调用,上面服务端上责任链上有四个handler,按照处理器的顺序,处理事件,在loginhandler中用来记录日志,在ServerBootstrap这个处理器中,客户端的连接变为channel对象,这个对象不会是服务端的这个EventLoop处理也不是服务端的这个channel处理,而是交给一个新的EventLoop处理,而这个新的eventloop就是我们在服务器启动时配置的I/O线程组以及accept线程组。
此时新创建的连接数据已经将数据传输过来,就会触发数据读取或者I/O读取的事件,在pipeline分析的关键4要素:什么事件、有哪些处理器、那些会被触发、执行顺序
用户在管道中有一个或者多个channelhandler来接收I/O事件(例如读取)和请求I/O操作(例如写入和关闭)
一个典型的服务器在每个通道的管道中都有以下处理程序,但是根据协议和业务逻辑的复杂性和特征,可能会有所不同:
协议解码器--将二进制数据(例如ByteBuf)转换为Java对象
协议编码器--将java对象转化为二进制数据
业务逻辑处理程序--执行实际的业务逻辑(例如访问数据库)