在學習netty源碼之前,應該對netty的基本用法有所了解,由于netty大多數時候用于開發服務器端程序,因此下面以一個時間服務器為例,演示Netty的基本使用,并對主要概念進行介紹。
## 2.1 服務器啟動程序
時間服務器很簡單,每次收到`QUERY TIME ORDER`請求后返回當前時間。
1. main方法中通過ServerBootstrap啟動netty服務器
```
//創建兩個線程組,專門用于網絡事件的處理,Reactor線程組
//一個用來接收客戶端的連接,
//一個用來進行SocketChannel的網絡讀寫
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workGroup=new NioEventLoopGroup();
try{
//輔助啟動類
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workGroup) // 注冊兩個線程組
.channel(NioServerSocketChannel.class)//創建的channel為NioServerSocketChannel【nio-ServerSocketChannel】
.option(ChannelOption.SO_BACKLOG, 1024) // 設置TCP屬性
.childOption(ChannelOption.SO_KEEPALIVE, true) //配置accepted的channel屬性
.childHandler(new ChildChannelHandler());//處理IO事件的處理類,處理網絡事件
ChannelFuture f=b.bind(port).sync();//綁定端口后同步等待
f.channel().closeFuture().sync();//阻塞
}catch(Exception e){
e.printStackTrace();
}finally{
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
```
2. 定義ChannelInitializer,會在ServerChannel注冊到事件循環后觸發initChannel事件。
```
// ChannelHandler初始化處理器
class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}
```
3. TimeServerHandler 里面負責處理業務邏輯,發送當前時間
```
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf=(ByteBuf) msg;//將msg轉換成Netty的ByteBuf對象
byte[] req=new byte[buf.readableBytes()];
buf.readBytes(req);
String body=new String(req,"GBK");
System.out.println("The time server receive order : "+body);
String currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";
ByteBuf resp=Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);//只是寫入緩沖區
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();//通過網絡發送
}
}
```
## 2.2 過程解析
1. 創建輔助啟動類ServerBootstrap,并設置相關配置:
* group() 設置處理Accept事件和讀寫操作的事件循環組
* channel() 設置通道類型為NioServerSocketChannel,這是netty自己定義的Channel,指的是服務器通道,內部包含java中的ServerSocketChannel,相應的還有客戶端通道NioSocketChannel。
* option()/childOption() 設置服務器通道的選項和建立連接后的客戶端通道的選項
* childHandler() 設置子處理器,內部需要將戶自定義的處理器加入到netty中,這涉及到Channel,ChannelHandler和Pipeline,后續會有講解
2.調用bind()方法綁定端口,sync()會阻塞等待處理請求。這是因為bind()方法是一個異步過程,會立即返回一個ChannelFuture對象,調用sync()會等待執行完成。
3.獲得Channel的closeFuture阻塞等待關閉,服務器Channel關閉時closeFuture會完成。
> Future的使用參見【第四章 Future和Promise】
## 2.3 相關概念
在學習Netty的源碼之前,需要對Netty的主要概念進行了解,主要是初步明白每個類負責的任務是什么,能夠完成哪些工作。當然,每個概念的具體實現會在后續章節中進行介紹。
**Channel**
這里的Channel與Java的Channel不是同一個,是netty自己定義的通道;Netty的Channel是對網絡連接處理的抽象,負責與網絡進行通訊,支持NIO和OIO兩種方式;內部與網絡socket連接,通過channel能夠進行I/O操作,如讀、寫、連接和綁定。
通過Channel可以執行具體的I/O操作,如read, write, connect, 和bind。在Netty中,所有I/O操作都是異步的;Netty的服務器端處理客戶端連接的Channel創建時可以設置父Channel。例如:ServerSocketChannel接收到請求創建SocketChannel,SocketChannel的父為ServerSocketChannel。
**ChannelHandler與ChannelPipeline**
ChannelHandler是通道處理器,用來處理I/O事件或攔截I/O操作,ChannelPipeline字如其名,是一個雙向流水線,內部維護了多個ChannelHandler,服務器端收到I/O事件后,每次順著ChannelPipeline依次調用ChannelHandler的相關方法。
ChannelHandler是個接口,通常我們在Netty中需要使用下面的子類:
* ChannelInboundHandler 用來處理輸入的I/O事件
* ChannelOutboundHandler 用來處理輸出的I/O事件
另外,下面的adapter類提供了
* ChannelInboundHandlerAdapter 用來處理輸入的I/O事件
* ChannelOutboundHandlerAdapter 用來處理輸出的I/O事件
* ChannelDuplexHandler 可以用來處理輸入和輸出的I/O事件
Netty的ChannelPipeline和ChannelHandler機制類似于Servlet和Filter過濾器/攔截器,每次收到請求會依次調用配置好的攔截器鏈。Netty服務器收到消息后,將消息在ChannelPipeline中流動和傳遞,途經的ChannelHandler會對消息進行處理,ChannelHandler分為兩種inbound和outbound,服務器read過程中只會調用inbound的方法,write時只尋找鏈中的outbound的Handler。
ChannelPipeline內部維護了一個雙向鏈表,Head和Tail分別代表表頭和表尾,Head作為總入口和總出口,負責底層的網絡讀寫操作;用戶自己定義的ChannelHandler會被添加到鏈表中,這樣就可以對I/O事件進行攔截和處理;這樣的好處在于用戶可以方便的通過新增和刪除鏈表中的ChannelHandler來實現不同的業務邏輯,不需要對已有的ChannelHandler進行修改。

如圖所示,在服務器初始化后,ServerSocketChannel的會創建一個Pipeline,內部維護了ChannelHanlder的雙向鏈表,讀取數據時,會依次調用ChannelInboundHandler子類的channelRead()方法,例如:讀取到客戶端數據后,依次調用解碼-業務邏輯-直到Tail。
而寫入數據時,會從用戶自定義的ChannelHandler出發查找ChannelOutboundHandler的子類,調用channelWrite(),最終由Head的write()向socket寫入數據。例如:寫入數據會通過業務邏輯的組裝--編碼--寫入socket(Head的write)。
**EventLoop與EventLoopGroup**
EventLoop是事件循環,EventLoopGroup是運行在線程池中的事件循環組,Netty使用了Reactor模型,服務器的連接和讀寫放在線程池之上的事件循環中執行,這是Netty獲得高性能的原因之一。事件循環內部會打開selector,并將Channel注冊到事件循環中,事件循環不斷的進行select()查找準備就緒的描述符;此外,某些系統任務也會被提交到事件循環組中運行。
**ServerBootstrap**
ServerBootstrap是輔助啟動類,用于服務端的啟動,內部維護了很多用于啟動和建立連接的屬性。包括:
* EventLoopGroup group 線程池組
* channel是通道
* channelFactory 通道工廠,用于創建channel
* localAddress 本地地址
* options 通道的選項,主要是TCP連接的屬性
* attrs 用來設置channel的屬性,
* handler 通道處理器
## 2.4 啟動過程
了解了上面的概念后,我們再來根據程序說明一下服務器的啟動過程,主要分為四個階段:
* 配置config:設置啟動器/服務器通道/客戶端通道等相關配置;
* 初始化init:主要功能是打開java的serversocketchannel,內部會初始化Netty的Channel及其ChannelPipeline;
* 注冊register:將初始化后的Netty-Channel注冊到事件循環的selector上面。具體過程:將打開netty的Channel注冊到線程池組的selector上;觸發Pipeline上面ChannelHandler的channelRegistered,至此注冊完畢;
* 綁定bind:將java的ServerSocketChannel綁定到本地的端口上面,結束后使用fireChannelActive通知Pipeline里的ChannelHandle,執行其channelActive方法;
由于注冊階段是異步的,綁定階段會與之同時進行,因此注冊階段完畢后會判斷綁定階段是否結束從而觸發channelActive。在啟動完畢后,會建立下圖中的連接結構:

Netty的Channel一端與java的Channel相連接,可以進行網絡I/O操作;另一端與Pipeline連接,用來執行業務邏輯。一旦事件循環組中的EventLoop在循環中select()到準備就緒的I/O描述符后,就會交給NettyChannel處理,NettyChannel交給Pipeline的鏈表進行業務邏輯處理。