Netty網絡框架
Netty是一個異步的基於事件驅動的網絡框架。php
1.Netty支持三種IO模型同時支持三種Reactor模式。java
2.Netty支持不少應用層的協議,提供了不少decoder和encoder。bootstrap
3.Netty可以解決TCP長鏈接所帶來的缺陷(粘包、半包等)服務器
4.Netty支持應用層的KeepAlive。網絡
5.Netty規避了JAVA NIO中的不少BUG,性能更好。多線程
1.建立ServerBootstrap服務端啓動對象。框架
2.配置bossGroup和workerGroup,其中bossGroup負責接收鏈接,workerGroup負責處理鏈接的讀寫就緒事件。異步
3.配置父Channel,通常爲NioServerSocketChannel。socket
4.配置子Channel與Handler之間的關係。tcp
5.給父Channel配置參數。
6.給子Channel配置參數。
7.綁定端口,啓動服務。
private void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 配置父Channel .childHandler(new ChannelInitializer<SocketChannel>() { // 配置子Channel與Handler之間的關係 @Override protected void initChannel(SocketChannel socketChannel) { // 往ChannelPipeline中添加ChannelHandler socketChannel.pipeline().addLast( new HttpRequestDecoder(), new HttpObjectAggregator(65535), new HttpResponseEncoder(), new HttpServerHandler() ); } }) .option(ChannelOption.SO_BACKLOG, 128) // 給父Channel配置參數 .childOption(ChannelOption.SO_KEEPALIVE, true); // 給子Channel配置參數
try { // 綁定端口,啓動服務 System.out.println("start server and bind 8888 port ..."); serverBootstrap.bind(8888).sync(); } catch (InterruptedException e) { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
1.建立Bootstrap客戶端啓動對象。
2.配置workerGroup,負責處理鏈接的讀寫就緒事件。
3.配置父Channel,通常爲NioSocketChannel。
4.給父Channel配置參數。
5.配置父Channel與Handler之間的關係。
6.鏈接服務器。
private void start() { EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) // 配置父Channel .option(ChannelOption.SO_KEEPALIVE, true) // 給父Channel配置參數 .handler(new ChannelInitializer<SocketChannel>() { // 配置父Channel與Handler之間的關係 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new TimeClientHandler()); } }); try { bootstrap.connect(new InetSocketAddress(8888)).sync(); // 鏈接服務器 } catch (InterruptedException e) { workerGroup.shutdownGracefully(); } }
channelActive():當創建一個新的Channel時調用該方法
handlerAdd():當往Channel的ChannelPipeline中添加Handler時調用該方法
handlerRemove():當移除ChannelPipeline中的Handler時調用該方法
channelRead():當Channel有數據可讀時調用該方法
exceptionCaught():當在處理事件發生異常時調用該方法
ServerSocketChannel每接收到一個新的鏈接時都會創建一個SocketChannel,而後調用ChannelInitializer的init方法初始化Channel,方法中配置Channel與Handler之間的關係,而後調用Handler的handlerAdd()和channelActive()方法。
ChannelPipeline底層使用雙向鏈表。
當Channel有數據可讀時,會沿着鏈表從前日後尋找有IN性質的Handler進行處理。
當Channel寫入數據時,會沿着鏈表從後往前尋找有OUT性質的Handler進行處理。
將數據寫入到緩衝區
發送緩衝區中的數據並清空
發送
Channel的write方法
緩衝區
Channel的flush方法
SocketChannel
write():將數據寫入到緩衝區flush():發送緩衝區中的數據並進行清空writeAndFlush():將數據寫入到緩衝區,同時發送緩衝區中的數據並進行清空
Channel的writeAndFlush()和flush()方法會從鏈表的最後一個節點開始從後往前尋找有OUT性質的Handler進行處理。
ChannelHandlerContext的writeAndFlush()和flush()方法會從當前節點從後往前尋找有OUT性質的Handler進行處理。
當SocketChannel能夠寫入數據時,將會觸發寫就緒事件,因此通常不能隨便監聽,不然將會一直觸發。
當SocketChannel在寫入數據寫不進時(緩衝區已經滿了),向Selector傳遞要監聽此Channel的寫就緒事件,而後強制發送緩衝區中的數據並進行清空,此時將會觸發寫就緒事件,當Selector處理完寫就緒事件後,應當剔除監聽此Channel的寫就緒事件。
Channel中的全部任務都會放入到其綁定的EventLoop的任務隊列中,而後等待被EventLoop中的線程處理。
因爲Netty中的全部操做都是異步的,所以通常會返回ChannelFuture對象,用於存儲Channel異步執行的結果。
當建立ChannelFuture實例時,isDone()方法返回false,僅當ChannelFuture被設置成成功或者失敗時,isDone()方法才返回true。
能夠往ChannelFuture中添加ChannelFutureListener,當任務被執行完畢後由IO線程自動調用。
ByteBuf有readerIndex和writerIndex兩個指針,默認都爲0,當進行寫操做時移動writerIndex指針,讀操做時移動readerIndex指針。
可讀容量 = writerIndex - readerIndex
*只有read()/write()方法纔會移動指針,get()/set()方法不會移動指針。
*ByteBuf支持動態擴容。
使用ByteBufAllocator來建立和管理ByteBuf,其分別提供PooledByteBufAllocator和UnpooledByteBufAllocator實現類,分別表明池化和非池化。
*Netty同時也提供了Pooled和Unpooled工具類來建立和管理ByteBuf。
每次使用時都從池中取出一個ByteBuf對象,當使用完畢後再放回到池中。
每一個ByteBuf都有一個refCount屬性,僅當refCount屬性爲0時纔將ByteBuf對象放回到池中。
ByteBuf的release()方法可使refCount屬性減1(通常由最後一個訪問ByteBuf的Handler進行處理)
每次使用時都建立一個新的ByteBuf對象。
若是每次使用ByteBuf後卻不進行釋放,那麼有可能發生內存泄漏,對象池中會不停的建立ByteBuf對象。
非池化的ByteBuf對象可以依賴JVM自動進行回收。
池化和非池化的ByteBufAllocator中均可以建立堆內和堆外的ByteBuf對象。
堆外的ByteBuf能夠避免在進行IO操做時數據從堆內內存複製到操做系統內存的過程,因此對於IO操做來講通常使用堆外的ByteBuf,而對於內部業務數據處理來講使用堆內的ByteBuf。
Netty支持BIO、NIO、AIO三種IO模型。
*其中AIO模型只在Netty的5.x版本有提供,但不建議使用,由於Netty再也不維護同時也廢除了5.x版本,其緣由是在Linux中AIO比NIO強不了多少。
只須要將EventLoopGroup和ServerSocketChannel換成相應IO模型的API便可。
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
*默認CPU核數 x 2個EventLoop。
EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();
send
ABC
ABCDEF
DEF
網絡
send
send
ABCDEF
ABC
DEF
網絡
網絡
1.寫入的數據遠小於緩衝區的大小,TCP協議爲了性能的考慮,合併後再進行發送。
1.寫入的數據大於緩衝區的大小,所以必須拆包後再進行傳輸(緩衝區已滿,強制flush)
2.寫入的數據大於協議的MTU(最大傳輸單元),所以必須拆包後再進行傳輸。
長鏈接中能夠發送多個請求,同時TCP協議是流式協議,消息無邊界,因此有一個很棘手的問題,接收方怎麼去知道一個請求中的數據究竟是哪裏到哪裏,以及一個請求中的數據有多是粘包後的結果,同時多個請求中的數據有多是半包後的結果。
1.使用短鏈接,鏈接開始和鏈接結束之間的數據就是請求的數據。
2.使用固定的長度,每一個請求中的數據都使用固定的長度,接收方以接收到固定長度的數據來肯定一個完整的請求數據。
3.使用指定的分隔符,每一個請求中的數據的末尾都加上一個分隔符,接收方以分隔符來肯定一個完整的請求數據。
4.使用特定長度的字段去存儲請求數據的長度,接收方根據請求數據的長度來肯定一個完整的請求數據。
FixedLengthFrameDecoder:使用固定的長度
DelimiterBasedFrameDecoder:使用指定的分隔符
LengthFieldBasedFrameDecoder:使用特定長度的字段去存儲請求數據的長度
正常狀況下雙方創建鏈接後是不會斷開的,KeepAlive就是防止鏈接雙方中的任意一方因爲意外斷開而通知不到對方,致使對方一直持有鏈接,佔用資源。
*創建鏈接須要三次握手、正常斷開鏈接須要四次揮手。
KeepAlive有三個核心參數
net.ipv4.tcp_keepalive_timeout:鏈接的超時時間(默認7200s)
net.ipv4.tcp_keepalive_intvl:發送探測包的間隔(默認75s)
tnet.ipv4.cp_keepalive_probes:發送探測包的個數(默認9個)
這三個參數都是系統參數,會影響部署在機器上的全部應用。
KeepAlive的開關是在應用層開啓的,只有當應用層開啓了KeepAlive,KeepAlive纔會生效。
java.net.Socket.setKeepAlive(boolean on);
當鏈接在指定時間內沒有發送請求時,開啓KeepAlive的一端就會向對方發送一個探測包,若是對方沒有迴應,則每隔指定時間發送一個探測包,總共發送指定個探測包,若是對方都沒有迴應則認爲對方不可用,斷開鏈接。
1.KeepAlive參數是系統參數,對於應用來講不夠靈活。
2.默認檢測一個不可用的鏈接所須要的時間太長。
1.定時任務
客戶端按期向全部已經創建鏈接的服務端發送心跳檢測,若是服務端連續沒有迴應指定個心跳檢測,則認爲對方不可用,此時客戶端應該重連。
服務端按期向全部已經創建鏈接的客戶端發送心跳檢測,若是客戶端連續沒有迴應指定個心跳檢測,則認爲對方不可用,此時應該斷開鏈接。
2.計時器
鏈接在指定時間內沒有發送請求則認爲對方不可用
Netty開啓KeepAlive
Bootstrap.option(ChannelOption.SO_KEEPALIVE,true);ServerBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
Netty提供的KeepAlive機制
Netty提供的IdleStateHandler可以檢測處於Idle狀態的鏈接。
Idle狀態類型
reader_idle:SocketChannel在指定時間內都沒有數據可讀
writer_idle:SocketChannel在指定時間內沒有寫入數據
all_idle:SocketChannel在指定時間內沒有數據可讀或者沒有寫入數據
直接將IdleStateHandler添加到ChannelPipeline便可,當Netty檢測處處於Idle狀態的鏈接時,將會自動調用其Handler的userEventTriggered()方法,用戶只須要在該方法中判斷Idle狀態的類型,而後作出相應的處理。
HTTP的KeepAlive是對長鏈接和短鏈接的選擇,並非保持鏈接存活的一種機制。
HTTP是基於請求和響應的,客戶端發送請求給服務端而後等待服務端的響應,當服務端檢測到請求頭中包含Connection:KeepAlive時,表示客戶端使用長鏈接,此時服務端應該保持鏈接,當檢測到請求頭中包含Connection:close時,表示客戶端使用短鏈接,此時服務端應該主動斷開鏈接。
TCP並非基於請求和響應的,客戶端能夠發送請求給服務端,同時服務端也能夠發送請求給客戶端。