Netty 常见面试题总结
1 Netty的channel 和nio的 channel 有什么联系
Netty的Channel内部包着JDK Channel的一个对象,且该Channel设置为非阻塞模式。
2 Netty客户端启动过程
首先BootStrap需要配置一些东西。
1 工作线程池EventLoopGroup,
2 指定NIOChannel.class
3 PipeLine的handlers
4 调用BootStrap的connect方法
启动过程是,先通过反射构造出来Channel的实现类,然后初始化Channel的Pipeline,然后把指定的Handlers装载到Pipeline上
最关键的一步,将Channel注册到EventLoop上。最后就是调用JDK NIO的channel的connect方法了
3 请问Channel是怎么注册到EventLoop的
NioEventLoop是一个只有一个线程的线程池,线程的的工作就是处理IO事件,也可以处理用户提交的任务。
NioEventLoop内部持有一个Selector。
所谓注册就是将Netty的Channel内部的JDK Channel注册到Selector上
4 EventLoop的工作流程
它内部的线程启动之后,run方法内部是一个for(;;)死循环。根据策略选择调用selectNow还是select
selectNow非阻塞select是阻塞的
选择哪种策略的依据是NioEventLoop中是否有本地任务需要执行,如果有则选择selectNow,否则select
接下来如果真的有数据过来,就是处理SelectionKeys的逻辑了,这部分逻辑就是nio提供的接口处理
把Socket缓冲区的数据load到ByteBuffer中,然后就是调用Channel的Pipeline的fireChannelRead方法,对这个消息进行处理
这个ByteBuffer在PipeLine中流转,经过一个个Handler的处理,并最终把处理过后的数据交给业务逻辑
5 Handler中的小优化mask
mask是一个bitmap,用来表示该handler是否实现了某些接口的某个方法,如果对应的bit是0,那就避免一次空的调用
6 Netty是怎么做到异步处理的
Promise接口,Promise接口继承了Future接口,实现类是ChannelPromise,内部有一个Channel对象,每个Netty的Channel对象都持有分配给它的EventLoop。
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { private final Channel parent; private final ChannelId id; private final Unsafe unsafe; private final DefaultChannelPipeline pipeline; private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false); private final AbstractChannel.CloseFuture closeFuture = new AbstractChannel.CloseFuture(this); private volatile SocketAddress localAddress; private volatile SocketAddress remoteAddress; private volatile EventLoop eventLoop;
利用线程池的特性,可以知道什么时候线程执行结束,这样就可以给ChannelPromise注册Listener
首先,Promise必须持有一个EventLoop,这样的线程池,因为只有持有线程池才能做到通知的时候异步通知,因为同步通知可能通知里面的业务实现的逻辑很长很长肯定不能用同步
public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> { private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultPromise.class); private static final InternalLogger rejectedExecutionLogger = InternalLoggerFactory.getInstance(DefaultPromise.class.getName() + ".rejectedExecution"); private static final int MAX_LISTENER_STACK_DEPTH = Math.min(8, SystemPropertyUtil.getInt("io.netty.defaultPromise.maxListenerStackDepth", 8)); @SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result"); private static final Signal SUCCESS = Signal.valueOf(DefaultPromise.class, "SUCCESS"); private static final Signal UNCANCELLABLE = Signal.valueOf(DefaultPromise.class, "UNCANCELLABLE"); private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(ThrowableUtil.unknownStackTrace( new CancellationException(), DefaultPromise.class, "cancel(...)")); private volatile Object result; private final EventExecutor executor; /** * One or more listeners. Can be a {@link GenericFutureListener} or a {@link DefaultFutureListeners}. * If {@code null}, it means either 1) no listeners were added yet or 2) all listeners were notified. * * Threading - synchronized(this). We must support adding listeners when there is no EventExecutor. */ private Object listeners;
比如在启动逻辑中
AbstractBootstrap
final ChannelFuture initAndRegister() { Channel channel = null; try { channel = channelFactory.newChannel(); 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); } ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } }
给返回的Promise添加listener,这就是异步的原理
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { return regFuture; } if (regFuture.isDone()) { // At this point we know that the registration was complete and successful. ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); 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; } }
SingleThreadEventLoop.register
@Override public ChannelFuture register(Channel channel) { return register(new DefaultChannelPromise(channel, this)); }
@Override public ChannelFuture register(final Channel channel, final ChannelPromise promise) { if (channel == null) { throw new NullPointerException("channel"); } if (promise == null) { throw new NullPointerException("promise"); } channel.unsafe().register(this, promise); return promise; }
AbstractChannel.AbstractUnsafe
@Override public final void register(EventLoop eventLoop, final ChannelPromise promise) { if (eventLoop == null) { throw new NullPointerException("eventLoop"); } if (isRegistered()) { promise.setFailure(new IllegalStateException("registered to an event loop already")); return; } if (!isCompatible(eventLoop)) { promise.setFailure( new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); return; } AbstractChannel.this.eventLoop = eventLoop; if (eventLoop.inEventLoop()) { register0(promise); } else { try { eventLoop.execute(new Runnable() { @Override public void run() { register0(promise); } }); } catch (Throwable t) { logger.warn( "Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t); closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); } } }
private void register0(ChannelPromise promise) { try { // check if the channel is still open as it could be closed in the mean time when the register // call was outside of the eventLoop if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } boolean firstRegistration = neverRegistered; doRegister(); neverRegistered = false; registered = true; // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the // user may already fire events through the pipeline in the ChannelFutureListener. pipeline.invokeHandlerAddedIfNeeded(); safeSetSuccess(promise);
7 EventLoopGroup和EventLoop有什么关系
EventLoopGroup是池子,EventLoop是单个线程,确切地说是单线程的线程池。EventLoopGroup并非严格的线程池,并没有继承JDK中的ExecutorService,它主要的作用是创建一组EventLoop,个数是用户输入或者cpu核数2倍,并为每一个Channel分配EventLoop,通过它的next()方法。
EventLoop中的线程是在第一次执行execute方法时才创建的,是通过Netty自己的ThreadPerTaskExecutor负责创建Thread
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); public ThreadPerTaskExecutor(ThreadFactory threadFactory) { this.threadFactory = threadFactory; } @Override public void execute(Runnable command) { threadFactory.newThread(command).start(); }
调用的地方是 EventLoop第一次执行execute的地方,
public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } // 判断当前线程是否是EventLoop线程 boolean inEventLoop = inEventLoop(); // 加入任务队列 addTask(task); if (!inEventLoop) { // 不是的话 启动eventLoop线程 startThread();
private void doStartThread() { assert thread == null; // 往executor提交一个任务,就是开始运行eventLoop了 executor.execute(new Runnable() {//这里的executor正是ThreadPerTaskExecutor,这里就相当于是创建线程并启动 @Override public void run() { thread = Thread.currentThread(); if (interrupted) { thread.interrupt(); } boolean success = false; updateLastExecutionTime(); try { // 执行NioEventLoop的run方法,是一个循环 SingleThreadEventExecutor.this.run(); success = true;
8 ServerBootStrap的bind流程
1)首先调用 AbstractBootstrap 中的 doBind()方法完成 NioServerSocketChannel 实例的初始化和注册。
2)然后调用 NioServerSocketChannel 实例的 bind()方法。
3)NioServerSocketChannel 实例的 bind()方法最终调用 sun.nio.ch.Net 中的 bind()和 listen()完成端口绑定和客户端连接监听。
4)sun.nio.ch.Net 中的 bind()和 listen()底层都是 JVM 进行的系统调用。
5)bind 完成后会进入 NioEventLoop 中的死循环,不断执行以下三个过程
-
select:轮训注册在其中的 Selector 上的 Channel 的 IO 事件
-
processSelectedKeys:在对应的 Channel 上处理 IO 事件
-
runAllTasks:再去以此循环处理任务队列中的其他任务
9 worker的EventLoop里的死循环是在哪里启动的(存疑)
在bossgroup收到accept事件后,会调用一个handler--ServerBootStrapAcceptor


注意那个eventloop.execute就是启动死循环的地方
浙公网安备 33010602011771号