netty-Netty框架簡介
概述:netty-網絡通信框架庫(第三方庫),
本質是對NIO的封裝。從高層次看netty,它主要為需要開發高性能應用的開發者解決了技術和體系結構問題。首先,它是基于javaNIO2.0的異步和事件驅動實現,保證了高負載下應用程序性能的最大化和可伸縮性。其次,netty也包含一組設計模式,將應用程序邏輯從網絡層解耦,簡化了開發過程,同時也最大限度地提高了可測試性,模塊化以及代碼的重用性。
通過以下手段讓性能得到提升:
使用多路復用技術,提高處理連接的并發性。
零拷貝:
- netty的接收和發送采用direct buffers(直接緩存),使用堆外直接內存進行socket讀寫,避免了數據從堆內拷貝到堆外的過程。
- netty提供了組合buffer對象(compositeByteBuf),將多個ByteBuf從邏輯上合并成一個ByteBuf,實現聚合多個buffer對象進行一次操作。
- 共享ByteBuf:通過slice方法, 將一個ByteBuf分解為多個共享同一個存儲區域的ByteBuf, 避免了內存的拷貝,這在需要進行拆包操作時非常管用
- 通過FileRegion包裝的FileChannel.tranferTo方法進行文件傳輸時(內核空間拷貝到用戶空間), 可以直接將文件緩沖區的數據發送到目標Channel, 減少了通過循環write方式導致的內存拷貝。但是這種方式是需要得到操作系統的零拷貝的支持的,如果netty所運行的操作系統不支持零拷貝的特性,則netty仍然無法做到零拷貝。
內存池: 為了減少堆外直接內存的分配和回收產生的資源消耗問題(回收不易,因為不在堆上,無法被gc),netty提供了基于內存池的緩沖區重用機制。
使用主從Reactor多線程模型,提高并發性。
采用了串行無鎖化設計,在IO線程內部進行串行操作,避免多線程競爭導致的性能下降。(channel注冊到EventLoop中,而EventLoop又是和線程一一綁定,不存在幾個線程競爭一個channel)
默認使用Protobuf的序列化框架。
靈活的TCP參數配置。
netty的實現:
我們通過maven來導入netty。在maven的pom.xml中實現對netty的依賴。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--netty的依賴-->
<!--需要下載什么jar包,就在這里添加,maven會幫我們在網絡上下載-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.12.Final</version>
</dependency>
</dependencies>
Callback(回調函數):
回調函數在編程中被廣泛應用,一般是在完成某個特定的操作后對相關方法進行調用。
netty在內部使用回調來處理事件;當一個回調被觸發時,相關的事件可以被interfaceChannelHandler的實現處理。如channel**時調用ChannelActive方法:
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx)throws Exception {
System.out.println("Client " + ctx.channel().remoteAddress() + connected");
}
}
ChannelFuture:
future一般用在執行異步操作時需要獲取未來某個時候的結果。
netty提供了它自己的實現-ChannelFuture,用于在異步操作時使用。
ChannelFuture提供了幾種額外方法,這些方法可以使我們注冊一個或者多個ChannelFutureListener實例。監聽器的回調方法operationComplete(),將會在對應的操作完成時被調用。然后監聽器可以判斷是成功的還是失敗的。如果是后者,我們檢索產生的Throwable。
通過使用ChannelFutureListener機制可以避免對操作結果進行手動檢查。
//同步連接服務端
//Channel channel= bootstrap.connect("127.0.0.1",6161).sync().channel();
//異步連接server
ChannelFuture future=bootstrap.connect("127.0.0.1",6161).sync();
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
//連接成功
if(channelFuture.isSuccess()){
System.out.println("服務端連接成功");
}else{ //連接失敗
Throwable cause=channelFuture.cause();//得到異常消息
cause.printStackTrace();
}
}
});
System.out.println("客戶端連接上服務器");
future.channel().writeAndFlush("第一次消息");
每個Netty的出站IO操作都將返回一個ChannelFuture,即不會阻塞后續的操作
Unsafe:
Unsafe接口實際上是Channel接口的輔助接口,它不該被用戶代碼直接調用。實際上的IO讀寫操作都是由Unsafe接口負責完成。
Event:
netty使用不同的事件來通知狀態的改變或者操作的狀態。事件可能包括:
- 連接被**或者連接失活
- 數據讀取
- 用戶事件
- 錯誤事件
- 打開或者關閉到遠程節點的連接
- 將數據寫道或者沖刷到socket
每個事件都可以被分發給channelHeadler類中每個用戶實現的方法。這是將事件驅動范式轉換成應用程序邏輯處理比較理想的位置。
對每個事件可以進行,記錄日志,數據轉換,應用程序邏輯處理等操作。
Netty核心組件:
- Bootstrap/ServerBootstrap:
netty應用程序通過設置bootstrap(啟動輔助類)開始,該類提供了一個應用于程序網絡層配置的容器 - Channel:
底層網絡傳輸API必須提供給應用IO操作的接口。這里的channel和NIO的channel是一樣的。 - ChannelHandler:
channelHeadler支持很多協議,并且提供用于數據處理的容器。channelhandler在netty中主要作用就是處理各種事件,這里事件很廣泛,比如可以是連接,異常,數據接收,數據轉換等,多個channelhandler配合使用,可以將業務邏輯分拆在多個channelhandle中。 - ChannelPipeline:
channelpipeline提供了一個容器給channelHandler鏈(實質為雙向連接,入棧處理和出站處理正好方向相反),并提供了一個API用于管理沿著鏈入站和出站事件的流動。每個channel都有自己的channelpipeline,當channel創建時自動創建的。channelHandler怎樣安裝在channelPipeline? 主要是實現了ChannelHandler的抽象ChannelInitializer。其通過serverbootstrap進行注冊。當它的方法initChannel被調用時,這個對象將安裝自定義的channelHandle集到pipeline。當這個操作完成時,ChannelInitializer子類則從ChannelPipeline中自動刪除自身。 - EventLoop:
EventLoop用于處理Channel的IO操作。一個單一的EventLoop通常會處理多個Channel事件。一個EventLoopGroup可以含有多個EventLoop和提供了一種迭代用于檢索清單中的下一個。
EventLoop和線程進行綁定,實現的實質是一個死循環,相當于一個線程維護一個eventloop。 一個eventloop中實際上是有一個select選擇器,我們就是對選擇器死循環進行調用,這樣處理每一個注冊到它上面的channel事件。 - ChannelFuture:
netty中所有的IO操作都是異步的。因為一個操作可能無法立即返回,我們需要有一種方法在以后確定它的結果。因此,netty提供了接口ChannelFuture,它的addListener方法注冊了一個ChannelFutureListener,當操作完成時,可以被通知(不管是否成功)
下圖為幾種組件之間關系
netty組件具體說明:
Channel:
在java中(Socket類),基本的IO操作(bind,connect,read,write),依賴于底層網絡傳輸所提供的功能。netty的channel接口所提供的API降低了直接使用Socket類的復雜性。netty中也提供了多種方式連接的channel:
- NioServerSocketChannel:
- NioSocketChannel:
- NioDatagramChannel:
如圖,每個channel都將會被分配一個ChannelPipeline和ChannelConfig。
ChannelConfig包含了該channel的所有配置設置,并支持熱更新(Channel已經啟動,仍然可以更新配置)
因為channel是獨一無二的,所以為了保證順序將channel聲明為java.lang.Comparable的子接口。因此,如果兩個不同的channel實例返回相同的hash,那么AbstractChannel中方法會拋出Error。
EventLoop&EventLoopGroup:
EventLoop接口代表事件循環,EventLoop是從EventExecutor和ScheduledExcutorService擴展而來,所以可以將任務交給EventLoop執行。
EventLoop主要用于處理連接的聲明周期中所發生的事件,它實現了netty的線程模型部分 的功能 ,線程模型指定了操作系統/編程語言/框架或者應用程序的上下文中的線程管理的方式。如何以及何時創建線程對將對應用程序的性能產生顯著影響。
Channel&EventLoop&EventLoopGroup的協作方式:
再次需要強調的是,我們是將channel注冊到eventLoop中,而eventLoop是和線程綁定的。也可以說channel是和線程綁定的,每個channel的聲明周期只會遇見一個線程,因此實現了無鎖化編程。
Server端兩個EventLoopGroup用途:
第一個用來專門負責綁定到端口的監聽連接事件,它的EventLoop和ServerChannel進行綁定,第二個EventLoopGroup用來處理每個接收到的連接,它的EventLoop和Channel進行綁定。
對于Server端,如果僅由一個EventLoopGroup處理所有請求和連接的話,在并發量很大的情況下,這個EventLoopGroup可能會忙于處理已經接收到的連接而不能即時處理新的請求,用兩個的話,會有專門的線程來處理連接請求,不會導致請求超時的情況,從而顯著提高并發處理能力。
ChannelHandler&ChannelHandlerPipeline
ChannelHandler:
對于開發一個netty的應用而言,主要開發的組件可能就是channelhandler,它充當了所有處理入站和出站數據的應用程序邏輯的內容 ,根據數據流向不同,ChannelHandler可以分為ChannelInboundHandler和ChannelOutboundHandler。
ChannelPipeline:
ChannelPipeline提供了ChannelHandler鏈的容器,并定義了用于該鏈傳播入站和出站事件流的API。 當Channel被創建時,它會被自動地分配到它專屬地channelpipeline。
ChannelHandler安裝到ChannelPipeline中過程如下:
- 一個ChannelInitializer被注冊到ServerBootstrap中;
- 當ChannelInitializer.initchannel()被調用時,ChannelInitializer將在ChannelPipeline中安裝一組ChannelHandler;
- ChannelInitializer將它自己從ChannelPipeline中移除;
事件進入ChannelPipeline時,會被定義的ChannelHandler順序的進行處理:
從客戶端應用程序看,如果事件的運行方向是從客戶端到服務器端,那么我們成為Outbound,反之為Inbound。 需要注意的是,對于inbound操作而言,處理順序是從頭到位,outbound的處理順序為從尾到頭。
注意:Inbound為一條鏈,Outbound為一條鏈。
當ChannelHandler被添加到ChannelPipeline時,它將會被分配一個ChannelHandlerContext,其代表了ChannelHandler和ChannelPipeline之間的綁定。雖然這個對象可以被用于獲取底層的channel,但是實際上主要還是被用于寫出站數據。
在netty中,有兩種消息發送模式。你可以直接寫到channel中,也可以寫到和ChannelHandler相關聯的ChannelHandlerContext對象中。前一種方式會導致消息從ChannelPipeline尾端開始流動,而后者將導致消息從ChannelPipeline中的下一個ChannelHandler開始流動。
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
ChannelPipeline pipeline=nioSocketChannel.pipeline(); //容器 ChannelPipeline
//添加的是channelHandler
//自定義編解碼,替代下面的字符串編解碼
//pipeline.addLast(new ByteToMessageCode());
//接收ByteBuf以分割線結束,如\n \r\n ,添加它,
//那么發送或者接收的消息都必須加換行符
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder()); //字符串解碼
pipeline.addLast(new StringEncoder()); //字符串編碼
pipeline.addLast(new ServerHandler());
//inboundchannelHandler :接收客戶端消息Handler
//outboundchannelHandler :寫數據Handler
}
});
ChannelHandler的類結構:
首先要說明,ChannelInboundHandler和ChannelOutboundHandler都繼承自ChannelHandler,將兩個類別的ChannelHandler混合添加到同一個ChannelPipeline中時,Netty能區分兩者的實現,并確保數據只會在具有相同定向類型的channelhandler之間傳遞。
對于ChannelHandler的實現類而言,可能不需要關注事件處理周期的每個環節,如果把Inbound或者Outbound接口的每個方法都實現,就會帶來很多工作量,netty對于這種情況提供了幾個Adapter(適配)方案:
- ChannelHandlerAdapter:
- ChannelInboundHandlerAdapter:
- ChannelOutboundHandlerAdapter
- ChannelDuplexHandler
編碼器&解碼器:
當netty發送或者接收一個消息時,就會發生一次數據轉換。入棧消息會被解碼:從字節轉換成另一種格式,通常為java對象。如果是出站,則發生編碼:將從當前格式編碼為字節。
netty默認已經提供了一堆編/解碼器,一般的需求以及可以滿足,如果不滿足則可以通過繼承ByteToMessageDecoder或者MessageToByteEncoder來實現自己的編/解碼器。這兩個基類也對應實現了ChannelOutboundHandler 或者 ChannelInboundHandler 接口。
對于解碼器而言,其重寫了channelRead方法,對于每個從入站Channel讀取的消息,這個方法將被調用,隨后它將調用由解碼器提供的decode()方法,并將已解碼的字節轉發給下一個ChannelHandler。
SimpleChannelInboundHandler
因為其是Inbound的,應用程序利用一個ChannelHandler來接收解碼消息,并對該數據應用業務邏輯。其中T為你要處理的消息java類型。
其有一個必須實現的ChannelRead0()方法。目前這個方法基本不再使用,我們通過使用ChannelRead()來進行業務邏輯處理。但是為了兼容老版本netty,因此還保留此抽象方法。
引導程序:ServerBootstrap&Bootstrap
netty的引導類為應用程序的網絡層配置提供了容器,這涉及將一個進程綁定到某個指定的端口,或者將一個進程連接到另一個運行在某個指定主機的指定端口上的進程。
兩種類型的引導類:
- Bootstrap:用于客戶端。
- ServerBootstrap:用于服務器。
需要注意的是,引導一個客戶端只需要一個EventLoopGroup,但是一個ServerBootstrap則需要兩個(也可以傳一個實例,但是為了高并發,以及更快接收客戶端連接,用兩個)。
ServerBootstrap bootstrap=new ServerBootstrap();
//將配置信息傳進去,將對應的事件組注冊進去。
bootstrap.group(boss,worker)//注冊主線程組和工作線程組
//底層調用反射來創建,這里傳入Class對象
.channel(NioServerSocketChannel.class)//主線程組處理的channel類型
//主線程組配置
//是否保持存活-》長連接(TCP),短連接(UDP/TCP),發送一次就斷開為短鏈接,
//多次發送不斷開為長連接,netty默認為長連接
.option(ChannelOption.SO_KEEPALIVE,true)
//發送緩沖區大小
.option(ChannelOption.SO_SNDBUF,1024)
//接收緩沖區大小
.option(ChannelOption.SO_RCVBUF,1024)
//子線程組配置
.childOption(ChannelOption.SO_RCVBUF,1024)
//子事件循環組中每個事件循環的Handle配置
.childHandler(new ChannelInitializer<NioSocketChannel>() {
bootstrap方法:
ServerBootstrap方法:
netty數據傳輸:
OIO和NIO的切換:
使用java自帶API開發IO系統時,如果需要對傳輸方式進行切換,那么可能會由于兩種方式API不兼容的問題需要大面積修改代碼。而netty為其所有的傳輸方式提供了通用的API,這使得只需要修改一下對象申明的類型就能完成傳輸方式的切換。
OIO的代碼:
EventLoopGroup group = new OioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(OioServerSocketChannel.class)
.........
} finally {
group.shutdownGracefully().sync();
}
NIO的代碼:
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.........
} finally {
group.shutdownGracefully().sync();
}
netty內置的傳輸方式:
netty內置了多種類型的傳輸方式,不同的傳輸方式有不同的應用場景與支持的協議,目前常見的類型包括以下幾種:
NIO
NIO類型的傳輸提供了一個所有IO操作的全異步的實現,主要利用了java自帶NIO子系統的選擇器API。
Epoll:
Netty的NIO傳輸基于java提供的異步/非阻塞網絡的通用抽象。
這保證了netty的非阻塞API可以在任何平臺使用,目前epoll只實現于linux平臺上。
OIO:
Netty 的 OIO 傳輸實現代表了一種折中:它可以通過常規的傳輸 API 使用,但是由于它 是建立在 java.net 包的阻塞實現之上的,所以它不是異步的。但是,它仍然非常適合于某些用途。例如,你可能需要移植使用了一些進行阻塞調用的庫(如JDBC)的遺留代碼,而將邏輯轉換為非阻塞的可能也是不切實際的。相反,你可以在短期內使用Netty的OIO傳輸,然后再將你的代碼移植到純粹的異步傳輸上。
Netty是如何能夠使用和用于異步傳輸相同的API來支持OIO的呢?
答案就是, Netty利用了SO_TIMEOUT這個Socket標志,它指定了等待一個I/O操作完成的最大毫秒數。如果操作在指定的時間間隔內沒有完成, 則將會拋出一個SocketTimeout Exception。 Netty將捕獲這個異常并繼續處理循環。在EventLoop下一次運行時,它將再次嘗試。這實際上也是類似于Netty這樣的異步框架能夠支持OIO的唯一方式。
參考博客:
智能推薦
Shrio安全框架簡介
本文介紹了Shrio安全框架的基礎概念,帶你初步認識了解Shrio安全框架,一起學習,共同進步 文章目錄 一.Shrio概念介紹 二.應用場景 三.基本功能介紹 四.核心組件 一.Shrio概念介紹 Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和...
springMVC框架簡介
1.SpringMVC概述 • Spring為展現層提供的基于MVC設計理念的優秀的Web框架,是目前最主流的MVC框架之一 • Spring3.0后全面超越Struts2,成為最優秀的MVC框架  ...
spring框架簡介
spring框架簡介 概述 Spring 是最受歡迎的企業級Java 應用程序開發框架。數以百萬的來自世界各地的開發人員使用 Spring 框架來創建好性能、易于測試、可重用的代碼。 Spring 框架是一個開源的Java 平臺,它最初是由 Rod Johnson 編寫的,并且 2003 年 6 月首次在Apache 2.0 許可下發布。 當談論到大小和透明度時, Spring 是輕量級的。 Sp...
【Executor框架】簡介
目錄 1 前言 2 Executor框架的兩級調度模型 3 Executor框架的結構 3.1 類與接口 3.2 組成部分 3.3 使用介紹 4 Executor框架的成員 4.1 ThreadPoolExecutor 4.2 ScheduledThreadPoolExecutor 4.3 Future接口 4.4 Runnable接口和 Call...
vue框架簡介
MVVM框架概述 什么是vue 是一套構建用戶界面的漸進式(用到哪一塊就用哪一塊,不需要全部用上)前端框架,Vue 的核心庫只關注視圖層 vue的兼容性 Vue.js 不支持 IE8 及其以下版本,因為 Vue.js 使用了 IE8 不能模擬的 ECMA...
猜你喜歡
java集合框架簡介
一、集合可以看作是一種容器,用來存儲對象信息。所有集合類都位于java.util包下,但支持多線程的集合類位于java.util.concurrent包下。 Java集合類主要由兩個根接口Collection和Map派生出來的,Collection派生出了三個子接口:List、Set、Queue(Java5新增的隊列),因此Java集合大致也可分成List、Set、Queue、Map四種接口體系,...
Hibernate框架簡介
what Hibernate是一個開源的,實現對象關系映射ORM思想的,輕量級的持久化框架 ORM:(Object Relational Mapping的縮寫)對象關系映射,即將實體的變化翻譯成sql腳本,并執行到數據庫中去, 即實體的變化映射...
Python Web框架簡介
我們都知道,在開發工作中,使用現成的框架或者庫來做開發的話,可以節省大量的工作,這也是開發人員常說的,不要重復造輪子。在使用Python做Web開發的時候,有哪些web框架可以來幫我們提高效率,加快網站上線呢?我們來看這些常用的Web開發框架。 1. Flask 基于BSD協議開源的Web框架,它是一個輕量級的Web框架,啟動非常迅速且簡單。它依賴于Werkzeug這個WSGI這個WSGI工具(用...
UI框架簡介(三)
我們繼續上面內容啊! 我個人建議呢!框架不需要我們去寫,那么我們拿到一套框架,首先應該應該看哪里呢?你想啊,如果你從框架入口區看的話,是可以,但是它所涉及到的繼承和各種管理,各種關聯會有很多的。看起來比較費勁。我就覺得從一個簡單的類看起來比較容易。那么框架中什么樣的比較容易讀懂呢? 答案就是:工具層,這些類只是提供工具方法的,方便其他類去調用的,看起來簡單,易理解。那么我們為什么要看這寫類呢?因為...