先看一下我Netty的啟動類
private void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .option(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES)); ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); ch.pipeline().addLast(new ProtobufDecoder(ChannelRequestProto.ChannelRequest.getDefaultInstance())); ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); ch.pipeline().addLast(new ProtobufEncoder()); ch.pipeline().addLast(new HeartBeatServerHandler()); ch.pipeline().addLast(new XtsCoreServerHandler()); } }); ChannelFuture future = bootstrap.bind().sync(); future.channel().closeFuture().sync(); } catch (Exception e) { bossGroup.shutdownGracefully().sync(); workerGroup.shutdownGracefully().sync(); } }
Netty先建立了兩個事件迴圈組 EventLoopGroup ,這個就對應了上文提到的模型, 第一個事件迴圈組的職責是 負責接收新的客戶端連線 並 把客戶端Channel註冊到多路複用器上面。 第二個事件迴圈組的職責是 處理客戶端的讀寫等事件。
Netty使用 ServerBootstrap 這個鏈式程式設計的方式把啟動服務端的程式碼給串聯起來,使用非常方便和優雅。所以我們在看原始碼的同時也要去好好品讀Netty原始碼書寫的巧妙,理解設計思想和方式,巧妙地運用到我們自己的程式碼中。接下來我會把原始碼貼出來,並把重要的部分進行解釋和著重指出
看下 group 方法
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { super.group(parentGroup); // 第一個事件迴圈組,我們叫父迴圈組吧,它是繼續呼叫了父類的group方法,也就是 AbstractBootstrap 並且賦給 group 成員變數
if (childGroup == null) { throw new NullPointerException("childGroup"); } if (this.childGroup != null) { throw new IllegalStateException("childGroup set already"); } this.childGroup = childGroup; // 第二個事件迴圈組, 我們叫子迴圈組吧, 直接賦值給 childGroup 這個成員變數 return this; // 返回自己,這就是鏈式程式設計的原因 }
接下來是 channel 方法, 設定了 channal 的型別是 NioServerSocketChannel (當然客戶端是 NioSocketChannel),這裡在建立的時候用了反射的方法來建立例項,後面涉及到再具體說。
public B channel(Class<? extends C> channelClass) { if (channelClass == null) { throw new NullPointerException("channelClass"); } return channelFactory(new ReflectiveChannelFactory<C>(channelClass)); // NioServerSocketChannel 使用了 ReflectiveChannelFactory 工廠封裝,並且設定到了 AbstractBootstrap
// 的 channelFactory 的這個成員變數中, 注意 這裡的channal 是 父級 的 channal
}
.option 方法 是在設定 父級 的一些引數資訊,這些就不說了, 當然這裡 .handler(new LoggingHandler(LogLevel.INFO)) 方法 也是為父級的事件迴圈器設定的 handler 。
.childHandler(new ChannelInitializer<SocketChannel>() { // 當然這裡是設定 子事件迴圈組器的 handlers ,但是這裡的 initChannel 方法不是在這裡呼叫的,這個後面會具體提到。 protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES)); ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); ch.pipeline().addLast(new ProtobufDecoder(ChannelRequestProto.ChannelRequest.getDefaultInstance())); ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); ch.pipeline().addLast(new ProtobufEncoder()); ch.pipeline().addLast(new HeartBeatServerHandler()); ch.pipeline().addLast(new XtsCoreServerHandler()); } });
好了,接下來進入正式部分。
ChannelFuture future = bootstrap.bind().sync(); // 就是從這裡的bind()進入
進入doBind方法
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { return regFuture; } ... 省略一大波程式碼 }
進入 initAndRegister
final ChannelFuture initAndRegister() { Channel channel = null; try { channel = channelFactory.newChannel(); // 這裡就是建立一個父級的channel 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); } ...省略一大波程式碼 }
這就是我前面為什麼著重指出 NioServerSocketChannel 使用了 ReflectiveChannelFactory 工廠封裝 , 就是上圖這裡了。然後進入 ReflectiveChannelFactory
使用了 NioServerSocketChannel 的無參構造方法例項化這個類。ok,那我們來看下NioServerSocketChannel 的 無參構造方法。
傳入一個預設的多路複用器建立器
使用它來呼叫 openServerSocketChannel() 方法來建立一個ServerSocketChannel, 具體這個方法就是NIO裡面的內容了,有興趣的自己去看一下。
看到這裡,我們知道channel建立完成(為父級使用),返回到這裡
大家千萬不要忽視一點,這裡有個this,我當時就沒注意到這裡,粗心了,導致其中有一步始終想不通,後來重新仔細看的時候,打自己的心都有了。
這裡繼續呼叫了另外一個有參的構造方法。
不斷呼叫父類構造方法,就進入到
這裡設定了父級的成員變數channel,並且把感興趣的key設定為16(接收新的客戶端),並且設定非阻塞。這裡在第一篇啟動NIO服務端的時候,也有這句,大家應該也記得。
我們繼續看呼叫的父類構造方法。
我們知道了為Channel設定了一個ID,並且建立了Pipleline.並且初始化了兩個上下文分別為頭和尾,通過連結串列連結。
我們說到這裡,簡單回顧一下Pipeline. 我們開看下ChannelPipeline官方說明
講到了, Pipeline 是 Channel中出站和入站操作的處理器或攔截器的一個列表。同時官方給出了一個表單我也貼出來
下圖說明了I/O讀寫事件是怎麼在PipeLine中的Handlers中傳遞的。需要通過 ChannelHandlerContext, 例如 ChannelHandlerContext#fireChannelRead(Object) 和 ChannelHandlerContext#write(Object)
這點了解Netty的一下子應該就看得明白,後面設計到PipeLine的地方我們再展開講解,繼續回到NioServerSocketChannel 的有參構造方法,繼續往下看。
這裡為剛剛建立的channel建立了一個配置類,並且是一個內部類。傳入了channel和套接字。
不斷往下跟,看到這裡
這裡傳入了一個小記憶體分配器,也就是說為這個channel初始化了一個分配器。
ok,我們來簡單說下這個分配器,後面在Netty的記憶體模型部分,我們再細說。
構造方法,傳入了三個預設值,並且說明了 預設分配緩衝區的大小為1024 ,最小是64,最大是65536
通篇看一下,看到了一個非常重要的靜態程式碼塊
依次往sizeTable新增元素:[16 , (512-16)]之間16的倍數。即,16、32、48...496
然後再往sizeTable中新增元素:[512 , 512 * (2^N)),N > 1; 直到數值超過Integer的限制(2^31 - 1);
根據sizeTable長度構建一個靜態成員常量陣列SIZE_TABLE,並將sizeTable中的元素賦值給SIZE_TABLE陣列。注意List是有序的,所以是根據插入元素的順序依次的賦值給SIZE_TABLE,SIZE_TABLE從下標0開始。SIZE_TABLE為預定義好的以從小到大的順序設定的可分配緩衝區的大小值的陣列。因為AdaptiveRecvByteBufAllocator作用是可自動適配每次讀事件使用的buffer的大小。這樣當需要對buffer大小做調整時,只要根據一定邏輯從SIZE_TABLE中取出值,然後根據該值建立新buffer即可。