简介
netty为什么快呢?这是因为netty底层应用了JAVA的NIO技术,并在其根底上进行了性能的优化,尽管netty不是单纯的JAVA nio,然而netty的底层还是基于的是nio技术。
nio是JDK1.4中引入的,用于区别于传统的IO,所以nio也能够称之为new io。
nio的三大外围是Selector,channel和Buffer,本文咱们将会深刻探索NIO和netty之间的关系。
NIO罕用用法
在解说netty中的NIO实现之前,咱们先来回顾一下JDK中NIO的selector,channel是怎么工作的。对于NIO来说selector次要用来承受客户端的连贯,所以个别用在server端。咱们以一个NIO的服务器端和客户端聊天室为例来解说NIO在JDK中是怎么应用的。
因为是一个简略的聊天室,咱们抉择Socket协定为根底的ServerSocketChannel,首先就是open这个Server channel:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));serverSocketChannel.configureBlocking(false);
而后向server channel中注册selector:
Selector selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
尽管是NIO,然而对于Selector来说,它的select办法是阻塞办法,只有找到匹配的channel之后才会返回,为了屡次进行select操作,咱们须要在一个while循环外面进行selector的select操作:
while (true) { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectedKeys.iterator(); while (iter.hasNext()) { SelectionKey selectionKey = iter.next(); if (selectionKey.isAcceptable()) { register(selector, serverSocketChannel); } if (selectionKey.isReadable()) { serverResponse(byteBuffer, selectionKey); } iter.remove(); } Thread.sleep(1000); }
selector中会有一些SelectionKey,SelectionKey中有一些示意操作状态的OP Status,依据这个OP Status的不同,selectionKey能够有四种状态,别离是isReadable,isWritable,isConnectable和isAcceptable。
当SelectionKey处于isAcceptable状态的时候,示意ServerSocketChannel能够承受连贯了,咱们须要调用register办法将serverSocketChannel accept生成的socketChannel注册到selector中,以监听它的OP READ状态,后续能够从中读取数据:
private static void register(Selector selector, ServerSocketChannel serverSocketChannel) throws IOException { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); }
当selectionKey处于isReadable状态的时候,示意能够从socketChannel中读取数据而后进行解决:
private static void serverResponse(ByteBuffer byteBuffer, SelectionKey selectionKey) throws IOException { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.read(byteBuffer); byteBuffer.flip(); byte[] bytes= new byte[byteBuffer.limit()]; byteBuffer.get(bytes); log.info(new String(bytes).trim()); if(new String(bytes).trim().equals(BYE_BYE)){ log.info("说再见不如不见!"); socketChannel.write(ByteBuffer.wrap("再见".getBytes())); socketChannel.close(); }else { socketChannel.write(ByteBuffer.wrap("你是个坏蛋".getBytes())); } byteBuffer.clear(); }
下面的serverResponse办法中,从selectionKey中拿到对应的SocketChannel,而后调用SocketChannel的read办法,将channel中的数据读取到byteBuffer中,要想回复音讯到channel中,还是应用同一个socketChannel,而后调用write办法回写音讯给client端,到这里一个简略的回写客户端音讯的server端就实现了。
接下来就是对应的NIO客户端,在NIO客户端须要应用SocketChannel,首先建设和服务器的连贯:
socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9527));
而后就能够应用这个channel来发送和承受音讯了:
public String sendMessage(String msg) throws IOException { byteBuffer = ByteBuffer.wrap(msg.getBytes()); String response = null; socketChannel.write(byteBuffer); byteBuffer.clear(); socketChannel.read(byteBuffer); byteBuffer.flip(); byte[] bytes= new byte[byteBuffer.limit()]; byteBuffer.get(bytes); response =new String(bytes).trim(); byteBuffer.clear(); return response; }
向channel中写入音讯能够应用write办法,从channel中读取音讯能够应用read办法。
这样一个NIO的客户端就实现了。
尽管以上是NIO的server和client的根本应用,然而基本上涵盖了NIO的所有要点。接下来咱们来具体理解一下netty中NIO到底是怎么应用的。
NIO和EventLoopGroup
以netty的ServerBootstrap为例,启动的时候须要指定它的group,先来看一下ServerBootstrap的group办法:
public ServerBootstrap group(EventLoopGroup group) { return group(group, group); }public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { ...}
ServerBootstrap能够承受一个EventLoopGroup或者两个EventLoopGroup,EventLoopGroup被用来解决所有的event和IO,对于ServerBootstrap来说,能够有两个EventLoopGroup,对于Bootstrap来说只有一个EventLoopGroup。两个EventLoopGroup示意acceptor group和worker group。
EventLoopGroup只是一个接口,咱们罕用的一个实现就是NioEventLoopGroup,如下所示是一个罕用的netty服务器端代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FirstServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 绑定端口并开始接管连贯 ChannelFuture f = b.bind(port).sync(); // 期待server socket敞开 f.channel().closeFuture().sync();
这里和NIO相干的有两个类,别离是NioEventLoopGroup和NioServerSocketChannel,事实上在他们的底层还有两个相似的类别离叫做NioEventLoop和NioSocketChannel,接下来咱们别离解说一些他们的底层实现和逻辑关系。
NioEventLoopGroup
NioEventLoopGroup和DefaultEventLoopGroup一样都是继承自MultithreadEventLoopGroup:
public class NioEventLoopGroup extends MultithreadEventLoopGroup
他们的不同之处在于newChild办法的不同,newChild用来构建Group中的理论对象,NioEventLoopGroup来说,newChild返回的是一个NioEventLoop对象,先来看下NioEventLoopGroup的newChild办法:
protected EventLoop newChild(Executor executor, Object... args) throws Exception { SelectorProvider selectorProvider = (SelectorProvider) args[0]; SelectStrategyFactory selectStrategyFactory = (SelectStrategyFactory) args[1]; RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) args[2]; EventLoopTaskQueueFactory taskQueueFactory = null; EventLoopTaskQueueFactory tailTaskQueueFactory = null; int argsLength = args.length; if (argsLength > 3) { taskQueueFactory = (EventLoopTaskQueueFactory) args[3]; } if (argsLength > 4) { tailTaskQueueFactory = (EventLoopTaskQueueFactory) args[4]; } return new NioEventLoop(this, executor, selectorProvider, selectStrategyFactory.newSelectStrategy(), rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory); }
这个newChild办法除了固定的executor参数之外,还能够依据NioEventLoopGroup的构造函数传入的参数来实现更多的性能。
这里参数中传入了SelectorProvider、SelectStrategyFactory、RejectedExecutionHandler、taskQueueFactory和tailTaskQueueFactory这几个参数,其中前面的两个EventLoopTaskQueueFactory并不是必须的。
最初所有的参数都会传递给NioEventLoop的构造函数用来结构出一个新的NioEventLoop。
在具体解说NioEventLoop之前,咱们来研读一下传入的这几个参数类型的理论作用。
SelectorProvider
SelectorProvider是JDK中的类,它提供了一个动态的provider()办法能够从Property或者ServiceLoader中加载对应的SelectorProvider类并实例化。
另外还提供了openDatagramChannel、openPipe、openSelector、openServerSocketChannel和openSocketChannel等实用的NIO操作方法。
SelectStrategyFactory
SelectStrategyFactory是一个接口,外面只定义了一个办法,用来返回SelectStrategy:
public interface SelectStrategyFactory { SelectStrategy newSelectStrategy();}
什么是SelectStrategy呢?
先看下SelectStrategy中定义了哪些Strategy:
int SELECT = -1; int CONTINUE = -2; int BUSY_WAIT = -3;
SelectStrategy中定义了3个strategy,别离是SELECT、CONTINUE和BUSY_WAIT。
咱们晓得个别状况下,在NIO中select操作自身是一个阻塞操作,也就是block操作,这个操作对应的strategy是SELECT,也就是select block状态。
如果咱们想跳过这个block,从新进入下一个event loop,那么对应的strategy就是CONTINUE。
BUSY_WAIT是一个非凡的strategy,是指IO 循环轮询新事件而不阻塞,这个strategy只有在epoll模式下才反对,NIO和Kqueue模式并不反对这个strategy。
RejectedExecutionHandler
RejectedExecutionHandler是netty本人的类,和 java.util.concurrent.RejectedExecutionHandler相似,然而是特地针对SingleThreadEventExecutor来说的。这个接口定义了一个rejected办法,用来示意因为SingleThreadEventExecutor容量限度导致的工作增加失败而被回绝的状况:
void rejected(Runnable task, SingleThreadEventExecutor executor);
EventLoopTaskQueueFactory
EventLoopTaskQueueFactory是一个接口,用来创立存储提交给EventLoop的taskQueue:
Queue<Runnable> newTaskQueue(int maxCapacity);
这个Queue必须是线程平安的,并且继承自java.util.concurrent.BlockingQueue.
解说完这几个参数,接下来咱们就能够具体查看NioEventLoop的具体NIO实现了。
NioEventLoop
首先NioEventLoop和DefaultEventLoop一样,都是继承自SingleThreadEventLoop:
public final class NioEventLoop extends SingleThreadEventLoop
示意的是应用繁多线程来执行工作的EventLoop。
首先作为一个NIO的实现,必须要有selector,在NioEventLoop中定义了两个selector,别离是selector和unwrappedSelector:
private Selector selector; private Selector unwrappedSelector;
在NioEventLoop的构造函数中,他们是这样定义的:
final SelectorTuple selectorTuple = openSelector(); this.selector = selectorTuple.selector; this.unwrappedSelector = selectorTuple.unwrappedSelector;
首先调用openSelector办法,而后通过返回的SelectorTuple来获取对应的selector和unwrappedSelector。
这两个selector有什么区别呢?
在openSelector办法中,首先通过调用provider的openSelector办法返回一个Selector,这个Selector就是unwrappedSelector:
final Selector unwrappedSelector;unwrappedSelector = provider.openSelector();
而后查看DISABLE_KEY_SET_OPTIMIZATION是否设置,如果没有设置那么unwrappedSelector和selector实际上是同一个Selector:
DISABLE_KEY_SET_OPTIMIZATION示意的是是否对select key set进行优化:
if (DISABLE_KEY_SET_OPTIMIZATION) { return new SelectorTuple(unwrappedSelector); } SelectorTuple(Selector unwrappedSelector) { this.unwrappedSelector = unwrappedSelector; this.selector = unwrappedSelector; }
如果DISABLE_KEY_SET_OPTIMIZATION被设置为false,那么意味着咱们须要对select key set进行优化,具体是怎么进行优化的呢?
先来看下最初的返回:
return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
最初返回的SelectorTuple第二个参数就是selector,这里的selector是一个SelectedSelectionKeySetSelector对象。
SelectedSelectionKeySetSelector继承自selector,构造函数传入的第一个参数是一个delegate,所有的Selector中定义的办法都是通过调用
delegate来实现的,不同的是对于select办法来说,会首先调用selectedKeySet的reset办法,上面是以isOpen和select办法为例察看一下代码的实现:
public boolean isOpen() { return delegate.isOpen(); } public int select(long timeout) throws IOException { selectionKeys.reset(); return delegate.select(timeout); }
selectedKeySet是一个SelectedSelectionKeySet对象,是一个set汇合,用来存储SelectionKey,在openSelector()办法中,应用new来实例化这个对象:
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
netty理论是想用这个SelectedSelectionKeySet类来治理Selector中的selectedKeys,所以接下来netty用了一个高技巧性的对象替换操作。
首先判断零碎中有没有sun.nio.ch.SelectorImpl的实现:
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { return Class.forName( "sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader()); } catch (Throwable cause) { return cause; } } });
SelectorImpl中有两个Set字段:
private Set<SelectionKey> publicKeys; private Set<SelectionKey> publicSelectedKeys;
这两个字段就是咱们须要替换的对象。如果有SelectorImpl的话,首先应用Unsafe类,调用PlatformDependent中的objectFieldOffset办法拿到这两个字段绝对于对象示例的偏移量,而后调用putObject将这两个字段替换成为后面初始化的selectedKeySet对象:
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) { // Let us try to use sun.misc.Unsafe to replace the SelectionKeySet. // This allows us to also do this in Java9+ without any extra flags. long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField); long publicSelectedKeysFieldOffset = PlatformDependent.objectFieldOffset(publicSelectedKeysField); if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) { PlatformDependent.putObject( unwrappedSelector, selectedKeysFieldOffset, selectedKeySet); PlatformDependent.putObject( unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet); return null; }
如果零碎设置不反对Unsafe,那么就用反射再做一次:
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true); if (cause != null) { return cause; } cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true); if (cause != null) { return cause; } selectedKeysField.set(unwrappedSelector, selectedKeySet); publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
在NioEventLoop中咱们须要关注的一个十分重要的重写办法就是run办法,在run办法中实现了如何执行task的逻辑。
还记得后面咱们提到的selectStrategy吗?run办法通过调用selectStrategy.calculateStrategy返回了select的strategy,而后通过判断
strategy的值来进行对应的解决。
如果strategy是CONTINUE,这跳过这次循环,进入到下一个loop中。
BUSY_WAIT在NIO中是不反对的,如果是SELECT状态,那么会在curDeadlineNanos之后再次进行select操作:
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()); switch (strategy) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.BUSY_WAIT: // fall-through to SELECT since the busy-wait is not supported with NIO case SelectStrategy.SELECT: long curDeadlineNanos = nextScheduledTaskDeadlineNanos(); if (curDeadlineNanos == -1L) { curDeadlineNanos = NONE; // nothing on the calendar } nextWakeupNanos.set(curDeadlineNanos); try { if (!hasTasks()) { strategy = select(curDeadlineNanos); } } finally { // This update is just to help block unnecessary selector wakeups // so use of lazySet is ok (no race condition) nextWakeupNanos.lazySet(AWAKE); } // fall through default:
如果strategy > 0,示意有拿到了SelectedKeys,那么须要调用processSelectedKeys办法对SelectedKeys进行解决:
private void processSelectedKeys() { if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }
下面提到了NioEventLoop中有两个selector,还有一个selectedKeys属性,这个selectedKeys存储的就是Optimized SelectedKeys,如果这个值不为空,就调用processSelectedKeysOptimized办法,否则就调用processSelectedKeysPlain办法。
processSelectedKeysOptimized和processSelectedKeysPlain这两个办法差异不大,只是传入的要解决的selectedKeys不同。
解决的逻辑是首先拿到selectedKeys的key,而后调用它的attachment办法拿到attach的对象:
final SelectionKey k = selectedKeys.keys[i]; selectedKeys.keys[i] = null; final Object a = k.attachment(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a); } else { NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); }
如果channel还没有建设连贯,那么这个对象可能是一个NioTask,用来解决channelReady和channelUnregistered的事件。
如果channel曾经建设好连贯了,那么这个对象可能是一个AbstractNioChannel。
针对两种不同的对象,会去别离调用不同的processSelectedKey办法。
对第一种状况,会调用task的channelReady办法:
task.channelReady(k.channel(), k);
对第二种状况,会依据SelectionKey的readyOps()的各种状态调用ch.unsafe()中的各种办法,去进行read或者close等操作。
总结
NioEventLoop尽管也是一个SingleThreadEventLoop,然而通过应用NIO技术,能够更好的利用现有资源实现更好的效率,这也就是为什么咱们在我的项目中应用NioEventLoopGroup而不是DefaultEventLoopGroup的起因。
本文已收录于 http://www.flydean.com/05-2-netty-nioeventloop/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!