關於netty的內存管理:
netty在4.1.x版本默認使用的allocator是PooledByteBufAllocator
,在分配ByteBuf
的時候,引入了新的alloc機制——jemalloc
,netty內部自己按照jemalloc
的機制,實現了一個java版的(這個有待考證,目前我翻了一下源碼,好像是java實現的,沒有調用native的部分)
關於Channel.write和ChannelHandlerContext.write:
ChannelHandlerContext
的write
方法,把值寫到channelPipline
的後一個channelHandler
節點中,而channel.write
不同,因爲write是個outbound事件,所以DefaultChannelPipeline
直接找到tail部分的context,調用其write()方法是從尾部向前傳遞的。(這個時候,頭指的是朝向外部的一端,尾部指朝向本地的一端)
更詳細的說明:
http://blog.csdn.net/zxhoo/article/details/17264263
關於netty的ByteBuf:
netty大塊來說有兩種buf,一種分配在堆內存中,一種分配在堆外內存(就是系統內存)裏。各有各的好處。
1.分配在堆外,避免了每次調用I/O操作之前或者之後,將緩衝區的內容複製到一箇中間緩衝區,缺點也很明顯——堆外收集和分配比較昂貴,使用需要複製到堆內存中。
2.分配在堆內存中的,byteBuf.hasArray()
將會返回true,說明在堆內存中有一個支持數組
。
關於ChannelHandlerContext:
每當有ChannelHandler
被添加到ChannelPipeline
中,都會創建一個ChannelHandlerContext
,二者之間的綁定關係是永遠不會改變的,所以可以緩存某個ChannelHandler
的ChannelHandlerContext
。但是一個channelHandler
可以被安裝到多個ChannelPipeline
中,但是需要加@Sharable
註解。
關於netty的異常處理:
如果入站處理中出現了異常,異常會像所有入站事件一樣向後傳遞,如果一直傳遞到底都沒有被處理,那麼netty將會warn級別記錄,並且嘗試釋放該異常。
關於netty的EventLoop:
Netty in action這本書中講的是:一個EventLoop
由一個線程來支撐,這個我翻了一下源碼,看到的確實是這個樣子。後面接着說:如果提交任務的線程就是支撐EventLoop
的線程,那麼任務將直接被執行;如果不是,那麼EventLoop
將調度該任務以便稍後執行,並將它放到內部的隊列中。
這個隊列是個LinkedBlockingQueue
,我把DefaultEventLoop
的execute方法(繼承自SingleThreadEventExecutor
)拿出來:
@Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } boolean inEventLoop = inEventLoop(); if (inEventLoop) { addTask(task); } else { startThread(); addTask(task); if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
再看看inEventLoop方法:
@Override public boolean inEventLoop(Thread thread) { //這裏我省略了一個方法,入參的thread是從Thread.currentThread()方法返回的,即當前線程 return thread == this.thread; }
這麼來看我們可以看到,應該是:是支撐EventLoop
的線程,把任務放到隊列中,不是的直接執行。不知道是作者筆誤?
繼續debug了一下netty,我發現,startThread()
這個方法的調用是在AbstractChannel
初始化的時候就執行了的,這個時候整個線程以及跑起來了,所以這個時候只要添加任何一個任務,就會直接拿出來執行,不知道作者的意思是不是這樣的。但是其實還有一個問題,如果這個時候taskQueue
中不是空的,那麼就算調用任務的線程是支撐EventLoop
的線程,這個任務也不會馬上執行(因爲這個任務不管怎麼樣都是在taskQueue中),只能保證比其他別的線程調用的任務稍微快一點點執行(其他線程的任務要先調用startThread
這個方法)。
關於Netty的傳輸類型和EventLoopGroup:
我們看一下這種代碼:EventLoopGroup e = new NioEventLoopGroup(); BootStrap b = new BootStrap(); b.channel(OioSocketChannel.class) ...
以上這段代碼將會報錯,因爲我們選用不兼容的傳輸。BootStrap.handler
方法尤其重要,因爲它要配置好ChannelPipeline
。
Netty的優雅關閉:
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group)... Future<?> f = group.shutdownGracefully(); f.syncUninterruptibly()
關於netty的解碼器的一些細節:
對於編碼器和解碼器來說,一旦消息被編碼或者解碼,它就會被ReferenceCountUtil.release(message)
調用自動釋放。如果不想消息被釋放,可以調用ReferenceCountUtil.retain(message)
增加引用計數。
關於netty中的WebSocket:
WebSocketServerHandshaker
有好多種實現,比如WebSocketServerHandshaker13
,就是13版的WebSocket實現。WebSocketServerProtocolHandler
按照WebSocket規範的要求,處理WebSocket升級握手、PingWebSocketFrame
、PongWebSocketFrame
和CloseWebSocketFrame
。WebSocket
升級之前的ChannelPipeline
中的狀態:(這裏只是根據我們的例子中的handler)
HtthRequestDecoder -> HttpResponseEncoder -> HttpObjectAggregator
-> HttpRequestHandler -> WebSocketServerProtocolHandler
-> TextWebSocketFrameHandler
HttpObjectAggregator
:將一個HttpMessage和跟隨它的多個HttpContent聚合爲單個FullHttpRequest或者FullHttpResponse(取決於它是被用來處理請求還是響應)。安裝這個之後,ChannelPipeline中的下一個ChannelHandler將只會收到完整的HTTP請求或響應。
WebSocket協議升級完成之後,WebSocketServerProtocolHandler
將會把HttpRequestDecoder
替換爲WebSocketFrameDecoder
,把HTTPResponseEncoder
替換爲WebSocketFrameEncoder
。爲了性能最大化,它將移除任何不再被WebSocket連接所需要的ChannelHandler
。也包含了HttpObjectAggregator
和HttpRequestHandler
。Netty根據客戶端支持的版本,自動選擇WebSocketFrameEncoder
和Decoder,下面是升級之後的Pipeline:
WebSocketFrameDecoder13 -> WebSocketFrameEncoder13
-> WebSocketServerProtocolHandler -> TextWebSocketFrameHandler
(假設我們選用13版的WebSocket協議)
關於WebSocket的握手:
我們上文說道WebSocketServerProtocolHandler
會做很多替換Handler和移除Handler的方法,WebSocketServerProtocolHandler
重寫了handlerAdded
方法,會在管道中添加一個WebSocketServerProtocolHandshakeHandler
,這個方法在channelRead的時候會執行握手handshaker.handshake
,這個handshaker是WebSocketServerHandshaker
的實體,我們可以看到這個握手過程中,會進行handler的替換、移除。
關於WebSocket的Idle狀態:
觸發IdleStateEvent
的是IdleStateHandler
,這個Handler的channelActive
、channelRegistried
和handlerAdded
的時候schedule一個Task,不停地來觸發IdleStateEvent
。
關於Netty的bind過程:
ServerBootStrap爲例(簡稱sbs),new一個sbs的時候,什麼也不會做,但是在調用sbs的channel方法的時候,會初始化ChannelFactory,默認用的是ReflectiveChannelFactory。bind的時候會初始化一個channel,這個channel是從ChannelFactory中創建出來的,然後在initAndRegister中調用:ChannelFuture regFuture = config().group().register(channel);
這裏這個group方法返回的就是group方法傳入的EventLoopGroup。這就會在EventLoopGroup中註冊一個Channel。我們以NioEventLoopGroup
爲例,調用了父類MultithreadEventLoopGroup
的register方法,這裏會先使用EventExecutorChooser
來選擇一個EventLoop,然後調用EventLoop的Channel註冊到一個EventLoop鍾,這就是我們之前說的,一個EventLoop會綁定很多Channel,但是一個Channel只會綁在一個EventLoop上。