參考文獻:極客時間傅健老師的《Netty源碼剖析與實戰》Talk is cheap.show me the code!
三種I/O模式
BIO:Block I/O,即同步並阻塞的IO;BIO就是傳統的java.io包下的代碼實現
NIO:New IO(non-blocking IO):同步非阻塞的IO,jdk1.4及以上版本提供
AIO:Async IO: 異步非阻塞IO,jdk1.7
阻塞和非阻塞
阻塞:沒有數據傳輸過來時,讀會阻塞直到有數據;緩沖區滿時,寫操作也會阻塞。
非阻塞: 非阻塞遇到這些情況都是直接返回。
同步和異步
同步:數據就緒后需要自己去讀是同步。
異步:數據就緒后直接讀好再回調給程序是異步。
Netty對三種IO的支持
首先Netty是都支持三種IO模式的,准確的來說是曾經都支持過,因為BIO的被Netty給過期了,AIO被Netty給刪除了,具體原因這就不多贅述;知道BIO在Netty被稱為OIO,NIO在多平台下都有對應的支持,有人會問為啥有common的支持了還有Linux等其他的意義嗎,這好比全棧和后端前端之分一樣,一個通用一個專用的區別。
Netty切換IO模式
如上圖所示,對應的實現類都差不多,甚至可以看出都是頭不一樣,如果NIO的通用是NioEventLoopGroup,而OIO的實現則是OioEventLoopGroup,先看之前的一個demo
public class MyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap sb = new ServerBootstrap(); sb.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new MyServerHandler()); } }); ChannelFuture f = sb.bind(8090).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
圖上標粗的就是切換的模式的關鍵點。現在看看切換成OIO的代碼
public class MyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new OioEventLoopGroup(); EventLoopGroup workerGroup = new OioEventLoopGroup(); try { ServerBootstrap sb = new ServerBootstrap(); sb.group(bossGroup, workerGroup).channel(OioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new MyServerHandler()); } }); ChannelFuture f = sb.bind(8090).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
上面代碼改動也就是標粗的那些。運行起來是完全沒問題的。
那么具體是怎么做的呢,我們看看源碼就知道了;
點進channel()方法里,核心步驟在此:
不難發現傳入的是OioServerSocketChannel.class,由channelFactory工廠創建返回,進入ReflectiveChannelFactory();
上圖的代碼中有“this.constructor = clazz.getConstructor();”獲取無參構造;
可以看出“return constructor.newInstance();”返回泛型“T” 就是要使用的IO模式。
總結來說:Netty實現IO模式的切換就是泛型+反射+工廠實現的。
除此之外,還有一點,"EventLoopGroup bossGroup = new NioEventLoopGroup();";實際上,它就相當於一個死循環,在“NioEventLoop.java”中,有個run(),如下圖源碼,可以看出它是個死循環(for (;;) {}),現在可以簡單的理解它就是循環監聽、處理事件的。
@Override protected void run() { for (;;) { try { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.BUSY_WAIT: // fall-through to SELECT since the busy-wait is not supported with NIO case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 'wakenUp.compareAndSet(false, true)' is always evaluated // before calling 'selector.wakeup()' to reduce the wake-up // overhead. (Selector.wakeup() is an expensive operation.) // // However, there is a race condition in this approach. // The race condition is triggered when 'wakenUp' is set to // true too early. // // 'wakenUp' is set to true too early if: // 1) Selector is waken up between 'wakenUp.set(false)' and // 'selector.select(...)'. (BAD) // 2) Selector is waken up between 'selector.select(...)' and // 'if (wakenUp.get()) { ... }'. (OK) // // In the first case, 'wakenUp' is set to true and the // following 'selector.select(...)' will wake up immediately. // Until 'wakenUp' is set to false again in the next round, // 'wakenUp.compareAndSet(false, true)' will fail, and therefore // any attempt to wake up the Selector will fail, too, causing // the following 'selector.select(...)' call to block // unnecessarily. // // To fix this problem, we wake up the selector again if wakenUp // is true immediately after selector.select(...). // It is inefficient in that it wakes up the selector for both // the first case (BAD - wake-up required) and the second case // (OK - no wake-up required). if (wakenUp.get()) { selector.wakeup(); } // fall through default: } } catch (IOException e) { // If we receive an IOException here its because the Selector is messed up. Let's rebuild // the selector and retry. https://github.com/netty/netty/issues/8566 rebuildSelector0(); handleLoopException(e); continue; } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { try { processSelectedKeys(); } finally { // Ensure we always run tasks. runAllTasks(); } } else { final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { // Ensure we always run tasks. final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } } catch (Throwable t) { handleLoopException(t); } // Always handle shutdown even if the loop processing threw an exception. try { if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { return; } } } catch (Throwable t) { handleLoopException(t); } } }
我只想做的更好,僅此而已