在學習netty源碼之前胰耗,應該對netty的基本用法有所了解振亮,由于netty大多數(shù)時候用于開發(fā)服務器端程序,因此下面以一個時間服務器為例穗泵,演示Netty的基本使用,并對主要概念進行介紹谜疤。
2.1 服務器啟動程序
時間服務器很簡單佃延,每次收到QUERY TIME ORDER
請求后返回當前時間。
- main方法中通過ServerBootstrap啟動netty服務器
//創(chuàng)建兩個線程組,專門用于網(wǎng)絡事件的處理夷磕,Reactor線程組
//一個用來接收客戶端的連接苇侵,
//一個用來進行SocketChannel的網(wǎng)絡讀寫
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try{
//輔助啟動類
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workGroup) // 注冊兩個線程組
.channel(NioServerSocketChannel.class)//創(chuàng)建的channel為NioServerSocketChannel【nio-ServerSocketChannel】
.option(ChannelOption.SO_BACKLOG, 1024) // 設置TCP屬性
.childOption(ChannelOption.SO_KEEPALIVE, true) //配置與cient連接的channel屬性
.childHandler(new ChildChannelHandler());//處理IO事件的處理類,處理網(wǎng)絡事件
ChannelFuture f = b.bind(port).sync();//綁定端口后同步等待
f.channel().closeFuture().sync();//阻塞等待
}catch(Exception e){
e.printStackTrace();
}finally{
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
- 定義ChannelInitializer企锌,會在ServerChannel注冊到事件循環(huán)后觸發(fā)initChannel事件榆浓。
// ChannelHandler初始化處理器
class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}
- TimeServerHandler 里面負責處理業(yè)務邏輯,發(fā)送當前時間
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);//只是寫入緩沖區(qū)
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();//通過網(wǎng)絡發(fā)送
}
}
2.2 過程解析
- 創(chuàng)建輔助啟動類ServerBootstrap撕攒,并設置相關配置:
- group() 設置處理Accept事件和讀寫操作的事件循環(huán)組
- channel() 設置通道類型為NioServerSocketChannel陡鹃,這是netty自己定義的Channel烘浦,指的是服務器通道,內部包含java原生的服務器端通道
ServerSocketChannel
萍鲸,相應的還有客戶端通道NioSocketChannel
闷叉。 - option()/childOption() 設置服務器通道的選項和建立連接后的客戶端通道的選項
- childHandler() 設置子處理器,內部需要將用戶自定義的處理器加入到netty的pipeline中脊阴,這涉及到Channel握侧,ChannelHandler和Pipeline嘿期,后續(xù)會有講解 品擎。
2.調用bind()方法綁定端口,sync()會阻塞等待處理請求备徐。這是因為bind()方法是一個異步過程萄传,會立即返回一個ChannelFuture對象,調用sync()會等待執(zhí)行完成蜜猾。
3.獲得Channel的closeFuture阻塞等待關閉秀菱,服務器Channel關閉時closeFuture會完成并釋放。
Future的使用參見 Netty源碼分析-04 Future和Promise
2.3 相關概念
在學習Netty的源碼之前蹭睡,需要對Netty的主要概念進行了解衍菱,主要是初步明白每個類負責的任務是什么,能夠完成哪些工作肩豁。當然运悲,每個概念的具體實現(xiàn)會在后續(xù)章節(jié)中進行介紹矾瑰。
Channel
這里的Channel與Java的Channel不是同一個伸眶,是netty自己定義的通道饺蚊;Netty的Channel是對網(wǎng)絡連接處理的抽象竭宰,負責與網(wǎng)絡進行通訊雨膨,支持NIO和OIO兩種方式留量;內部與網(wǎng)絡socket連接吼具,通過channel能夠進行I/O操作雄人,如讀从橘、寫、連接和綁定础钠。
通過Channel可以執(zhí)行具體的I/O操作恰力,如read, write, connect, 和bind。在Netty中旗吁,所有I/O操作都是異步的踩萎;Netty的服務器端處理客戶端連接的Channel創(chuàng)建時可以設置父Channel。例如:ServerSocketChannel接收到請求創(chuàng)建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中流動和傳遞槽畔,途經(jīng)的ChannelHandler會對消息進行處理,ChannelHandler分為兩種inbound和outbound胁编,服務器read過程中只會調用inbound的方法厢钧,write時只尋找鏈中的outbound的Handler。
ChannelPipeline內部維護了一個雙向鏈表嬉橙,Head和Tail分別代表表頭和表尾早直,Head作為總入口和總出口,負責底層的網(wǎng)絡讀寫操作市框;用戶自己定義的ChannelHandler會被添加到鏈表中霞扬,這樣就可以對I/O事件進行攔截和處理;這樣的好處在于用戶可以方便的通過新增和刪除鏈表中的ChannelHandler來實現(xiàn)不同的業(yè)務邏輯枫振,不需要對已有的ChannelHandler進行修改喻圃。
如圖所示,在服務器初始化后粪滤,ServerSocketChannel的會創(chuàng)建一個Pipeline斧拍,內部維護了ChannelHanlder的雙向鏈表,讀取數(shù)據(jù)時杖小,會依次調用ChannelInboundHandler子類的channelRead()方法肆汹,例如:讀取到客戶端數(shù)據(jù)后,依次調用解碼-業(yè)務邏輯-直到Tail予权。
而寫入數(shù)據(jù)時昂勉,會從用戶自定義的ChannelHandler出發(fā)查找ChannelOutboundHandler的子類,調用channelWrite()扫腺,最終由Head的write()向socket寫入數(shù)據(jù)岗照。例如:寫入數(shù)據(jù)會通過業(yè)務邏輯的組裝--編碼--寫入socket(Head的write)。
EventLoop與EventLoopGroup
EventLoop是事件循環(huán),EventLoopGroup是運行在線程池中的事件循環(huán)組谴返,Netty使用了Reactor模型煞肾,服務器的連接和讀寫放在線程池之上的事件循環(huán)中執(zhí)行,這是Netty獲得高性能的原因之一嗓袱。事件循環(huán)內部會打開selector籍救,并將Channel注冊到事件循環(huán)中,事件循環(huán)不斷的進行select()查找準備就緒的描述符渠抹;此外蝙昙,某些系統(tǒng)任務也會被提交到事件循環(huán)組中運行。
ServerBootstrap
ServerBootstrap是輔助啟動類梧却,用于服務端的啟動奇颠,內部維護了很多用于啟動和建立連接的屬性。包括:
- EventLoopGroup group 線程池組
- channel是通道
- channelFactory 通道工廠放航,用于創(chuàng)建channel
- localAddress 本地地址
- options 通道的選項烈拒,主要是TCP連接的屬性
- attrs 用來設置channel的屬性,
- handler 通道處理器
2.4 啟動過程
了解了上面的概念后广鳍,我們再來根據(jù)程序說明一下服務器的啟動過程荆几,主要分為四個階段:
- 配置config:設置啟動器/服務器通道/客戶端通道等相關配置;
- 初始化init:主要功能是打開java的serversocketchannel,內部會初始化Netty的Channel及其ChannelPipeline赊时;
- 注冊register:將初始化后的Netty-Channel注冊到事件循環(huán)的selector上面吨铸。具體過程:將打開netty的Channel注冊到線程池組的selector上;觸發(fā)Pipeline上面ChannelHandler的channelRegistered祖秒,至此注冊完畢诞吱;
- 綁定bind:將java的ServerSocketChannel綁定到本地的端口上面,結束后使用fireChannelActive通知Pipeline里的ChannelHandle竭缝,執(zhí)行其channelActive方法房维;
由于注冊階段是異步的,綁定階段會與之同時進行抬纸,因此注冊階段完畢后會判斷綁定階段是否結束從而觸發(fā)channelActive咙俩。在啟動完畢后,會建立下圖中的連接結構:
Netty的Channel一端與java的Channel相連接松却,可以進行網(wǎng)絡I/O操作;另一端與Pipeline連接溅话,用來執(zhí)行業(yè)務邏輯晓锻。一旦事件循環(huán)組中的EventLoop在循環(huán)中select()到準備就緒的I/O描述符后,就會交給NettyChannel處理飞几,NettyChannel交給Pipeline的鏈表進行業(yè)務邏輯處理砚哆。