首先聲明,本文是爲Netty新手準備的,因此事無鉅細的會把步驟列出來,老手們就不用在我這篇文章上浪費時間了,要否則你會嫌我墨跡的。css
Netty是一個開源的異步事件驅動的網絡應用程序框架,用於快速開發可維護的高性能協議服務器和客戶端。html
Netty的創始人是韓國人trustin lee,他如今韓國line公司工做,早前應用較多的Mina也是這牛人的做品。java
Netty目前的項目leader是德國人Norman maurer(以前在Redhat,全職開發Netty),也是《Netty in action》的做者,目前是蘋果公司高級工程師,同時也常常參加netty相關的技術會議,這兩大牛長下面這樣:
程序員
Netty的優勢,簡單一句話:使用簡單、功能強大、性能強悍。數據庫
Netty的特色:apache
高併發:Netty 是一款基於 NIO(Nonblocking IO,非阻塞IO)開發的網絡通訊框架,對比於 BIO(Blocking I/O,阻塞IO),他的併發性能獲得了很大提升。bootstrap
傳輸快:Netty 的傳輸依賴於零拷貝特性,儘可能減小沒必要要的內存拷貝,實現了更高效率的傳輸。設計模式
封裝好:Netty 封裝了 NIO 操做的不少細節,提供了易於使用調用接口。數組
Netty的優點:緩存
使用簡單:封裝了 NIO 的不少細節,使用更簡單。
功能強大:預置了多種編解碼功能,支持多種主流協議。
定製能力強:能夠經過 ChannelHandler 對通訊框架進行靈活地擴展。
性能高:經過與其餘業界主流的 NIO 框架對比,Netty 的綜合性能最優。
穩定:Netty 修復了已經發現的全部 NIO 的 bug,讓開發人員能夠專一於業務自己。
社區活躍:Netty 是活躍的開源項目,版本迭代週期短,bug 修復速度快。
Netty高性能表如今哪些方面?
IO 線程模型:同步非阻塞,用最少的資源作更多的事。
內存零拷貝:儘可能減小沒必要要的內存拷貝,實現了更高效率的傳輸。
內存池設計:申請的內存能夠重用,主要指直接內存。內部實現是用一顆二叉查找樹管理內存分配狀況。
串形化處理讀寫:避免使用鎖帶來的性能開銷。
高性能序列化協議:支持 protobuf 等高性能序列化協議。
BIO、NIO和AIO的區別是什麼?
這三個概念分別對應三種通信模型:阻塞、非阻塞、非阻塞異步,概念這裏就不寫了,你們能夠度娘搜一下,網上好多博客說Netty對應NIO,準確來講,應該是既能夠是NIO,也能夠是AIO,就看你怎麼實現,這三個的區別以下:
BIO:一個鏈接一個線程,客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,線程開銷大。僞異步IO:將請求鏈接放入線程池,一對多,但線程仍是很寶貴的資源。
NIO:一個請求一個線程,但客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。
AIO:一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理。
BIO是面向流的,NIO是面向緩衝區的;BIO的各類流是阻塞的。而NIO是非阻塞的;BIO的Stream是單向的,而NIO的channel是雙向的。
NIO的特色:事件驅動模型、單線程處理多任務、非阻塞I/O,I/O讀寫再也不阻塞,而是返回0、基於block的傳輸比基於流的傳輸更高效、更高級的IO函數zero-copy、IO多路複用大大提升了Java網絡應用的可伸縮性和實用性。基於Reactor線程模型。
學技能都是爲了可以應用到實際工做中去,誰也不是爲了學而學、弄着玩不是,那麼Netty能作什麼呢?主要是在兩個方面:
如今物聯網的應用無處不在,大量的項目都牽涉到應用傳感器和服務器端的數據通訊,Netty做爲基礎通訊組件、可以輕鬆解決以前有較高門檻的通訊系統開發,你不用再爲如何解析各種簡單、或複雜的通信協議而薅頭髮了,有過這方面開發經驗的程序員會有更深入、或者說刻骨銘心的體會。
如今互聯網系統講究的都是高併發、分佈式、微服務,各種消息滿天飛,Netty在這類架構裏面的應用可謂是如魚得水,若是你對當前的各類應用服務器不爽,那麼徹底能夠基於Netty來實現本身的HTTP服務器,FTP服務器,UDP服務器,RPC服務器,WebSocket服務器,Redis的Proxy服務器,MySQL的Proxy服務器等等。
直接的好處是:可以有進大廠、拿高薪的機會,業內好多著名的公司在招聘高級/資深Java工程師時基本上都要求熟練掌握、或熟悉Netty。
這個名單還能夠很長很長。。。
做爲一個學Java的,若是沒有研究過Netty,那麼你對Java語言的使用和理解僅僅停留在表面水平,會點SSH,寫幾個MVC,訪問數據庫和緩存,這些只是初、中等Java程序員乾的事。若是你要進階,想了解Java服務器的深層高階知識,Netty絕對是一個必需要過的門檻。
間接地好處是:多款開源框架中應用了Netty,掌握了Netty,就具備分析這些開源框架的基礎了,也就是有了成爲技術大牛的基礎。
這些開源框架有哪些呢?簡單羅列一些典型的,以下:
阿里分佈式服務框架 Dubbo 的 RPC 框架;
淘寶的消息中間件 RocketMQ;
Hadoop 的高性能通訊和序列化組件 Avro 的 RPC 框架;
開源集羣運算框架 Spark;
分佈式計算框架 Storm;
併發應用和分佈式應用 Akka;
名單依然很長很長。。。。
在開始動手以前,必要的基礎概念仍是要知道的,要否則代碼敲下來,功能卻是實現了,但對Netty仍是一頭霧水,這就不是本文要達到的目的了。
本示例須要用到的基礎知識主要有如下幾方面的東東,這些知識點最好有一個大概的瞭解,要否則,看實例會有必定的困難。
- 掌握Java基礎
- 掌握Maven基礎
- 熟悉IntelliJ IDEA集成開發工具的使用,這個工具簡稱IDEA
- 知道TCP、Socket的基本概念
I/O:各類各樣的流(文件、數組、緩衝、管道。。。)的處理(輸入輸出)。
Channel:通道,表明一個鏈接,每一個Client請對會對應到具體的一個Channel。
ChannelPipeline:責任鏈,每一個Channel都有且僅有一個ChannelPipeline與之對應,裏面是各類各樣的Handler。
handler:用於處理出入站消息及相應的事件,實現咱們本身要的業務邏輯。
EventLoopGroup:I/O線程池,負責處理Channel對應的I/O事件。
ServerBootstrap:服務器端啓動輔助對象。
Bootstrap:客戶端啓動輔助對象。
ChannelInitializer:Channel初始化器。
ChannelFuture:表明I/O操做的執行結果,經過事件機制,獲取執行結果,經過添加監聽器,執行咱們想要的操做。
ByteBuf:字節序列,經過ByteBuf操做基礎的字節數組和緩衝區。
基礎環境準備主要有三個方面:JDK安裝及環境變量設置、Maven安裝及環境變量設置、IDEA安裝及基本設置。
JDK下載,能夠從官方如今,也能夠度娘上隨便搜下載連接,最新版是JDK14,我這裏下載的是JDK8,用8仍是14哪一個版本無所謂,均可以,但要注意一點的是,如今從JDK的官網Oracle下載須要帳號了,沒帳號的可下不了啦,不知道在搞什麼東東。
官網下載地址:https://www.oracle.com ,截圖以下:
下載完,一路Next安裝完,在建立Java環境變量設置,[此電腦]右鍵-->[屬性]-->[高級系統設置]-->[環境變量]-->[系統變量],截圖以下:
Java環境變量建立完畢後,在DOS窗口執行命令:java -version,測試一下是否正常
Maven功能很強大,但你們不用擔憂、本實例中僅僅是利用其便利的jar包依賴、jar包依賴傳遞,基本上沒有任何學習成本。
jar包依賴、jar包依賴傳遞的概念以下圖,清楚明瞭,都不用多作解釋:
Maven是下載,解壓縮後,配置環境變量後就能用,不用安裝的。
下載地址https://downloads.apache.org/maven/maven-3/3.6.3/binaries/
安裝:下載壓縮包,解壓,文件夾拷貝到所想存儲的位置(如C盤根目錄)
配置環境變量,和Java的環境變量配置同樣的,建立MAVEN_HOME,指向Maven文件夾,再在path中添加進去就行,以下圖:
因爲直接衝Maven的中央倉庫中自動下載jar包較慢,通常在Maven的配置文件中,增長阿里雲的公共倉庫配置,這樣會顯著加快jar包的下載速度,以下:
上面的環境變量設置完後,經過DOS窗口中輸入命令:mvn -version 進行驗證是否成功,以下:
IDEA的下載和安裝就很少說了,其版本分旗艦版和社區版,旗艦版收費,社區版免費,社區版不支持html、js、css等,但對於本實例,社區版就夠用了,但若是你不在意那點銀子,能夠考慮旗艦版,一步到位,萬一後面咱們還要作WEB系統開發能夠省得折騰。
其安裝不用多說,一路Next就行,安裝完後,在其配置裏面指定一下JDK、Maven的位置就好了,以下圖:
Maven指定:[File]-->[setting]-->[Build,Excution,Deployment]-->[Build Tools]-->[Maven]
JDK指定:[File]-->[Project Structure]-->[Project Setting]-->[Project]
新建工程
填寫包名及工程名稱
Maven配置
生成工程,自動建立Maven的依賴文件
在pom.xml中配置Netty依賴
通過上面的步驟,咱們的Maven工程就已經建立完畢,如今能夠編寫Netty的第一個程序,這個程序很簡單,傳輸一個字符串,雖然程序很簡單,可是已經可以大致上反映Netty開發通訊程序的一個總體流程了。
Netty開發的基本流程很簡潔,服務器端和客戶端都是這個套路,以下:
Netty開發的實際過程,這是一個簡化的過程,但已經把大概流程表達出來了,綠色的表明客戶端流程、藍色的表明服務器端流程,注意標紅的部分,見下圖:
建立Handler
首先建立Handler類,該類用於接收服務器端發送的數據,這是一個簡化的類,只重寫了消息讀取方法channelRead0、捕捉異常方法exceptionCaught。
客戶端的Handler通常繼承的是SimpleChannelInboundHandler,該類有豐富的方法,心跳、超時檢測、鏈接狀態等等。
代碼以下:
package com.jcj.helloworld; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; /** * @Auther: 江成軍 * @Date: 2020/6/1 11:12 * @Description: 通用handler,處理I/O事件 */ @ChannelHandler.Sharable public class HandlerClientHello extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { /** * @Author 江成軍 * @Date 2020/6/1 11:17 * @Description 處理接收到的消息 **/ System.out.println("接收到的消息:"+byteBuf.toString(CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { /** * @Author 江成軍 * @Date 2020/6/1 11:20 * @Description 處理I/O事件的異常 **/ cause.printStackTrace(); ctx.close(); } }
代碼說明:
@ChannelHandler.Sharable,這個註解是爲了線程安全,若是你不在意是否線程安全,不加也能夠。
SimpleChannelInboundHandler
,這裏的類型能夠是ByteBuf,也能夠是String,還能夠是對象,根據實際狀況來。 channelRead0,消息讀取方法,注意名稱中有個0。
ChannelHandlerContext,通道上下文,代指Channel。
ByteBuf,字節序列,經過ByteBuf操做基礎的字節數組和緩衝區,由於JDK原生操做字節麻煩、效率低,因此Netty對字節的操做進行了封裝,實現了指數級的性能提高,同時使用更加便利。
CharsetUtil.UTF_8,這個是JDK原生的方法,用於指定字節數組轉換爲字符串時的編碼格式。
建立客戶端啓動類
客戶端啓動類根據服務器端的IP和端口,創建鏈接,鏈接創建後,實現消息的雙向傳輸。
代碼較簡潔,以下:
package com.jcj.helloworld; import com.sun.org.apache.bcel.internal.generic.ATHROW; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.CharsetUtil; import java.net.InetSocketAddress; /** * @Auther: 江成軍 * @Date: 2020/6/1 11:24 * @Description: 客戶端啓動類 */ public class AppClientHello { private final String host; private final int port; public AppClientHello(String host, int port) { this.host = host; this.port = port; } public void run() throws Exception { /** * @Author 江成軍 * @Date 2020/6/1 11:28 * @Description 配置相應的參數,提供鏈接到遠端的方法 **/ EventLoopGroup group = new NioEventLoopGroup();//I/O線程池 try { Bootstrap bs = new Bootstrap();//客戶端輔助啓動類 bs.group(group) .channel(NioSocketChannel.class)//實例化一個Channel .remoteAddress(new InetSocketAddress(host,port)) .handler(new ChannelInitializer<SocketChannel>()//進行通道初始化配置 { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new HandlerClientHello());//添加咱們自定義的Handler } }); //鏈接到遠程節點;等待鏈接完成 ChannelFuture future=bs.connect().sync(); //發送消息到服務器端,編碼格式是utf-8 future.channel().writeAndFlush(Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8)); //阻塞操做,closeFuture()開啓了一個channel的監聽器(這期間channel在進行各項工做),直到鏈路斷開 future.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new AppClientHello("127.0.0.1",18080).run(); } }
因爲代碼中已經添加了詳盡的註釋,這裏只對極個別的進行說明:
ChannelInitializer,通道Channel的初始化工做,如加入多個handler,都在這裏進行。
bs.connect().sync(),這裏的sync()表示採用的同步方法,這樣鏈接創建成功後,才繼續往下執行。
pipeline(),鏈接創建後,都會自動建立一個管道pipeline,這個管道也被稱爲責任鏈,保證順序執行,同時又能夠靈活的配置各種Handler,這是一個很精妙的設計,既減小了線程切換帶來的資源開銷、避免好多麻煩事,同時性能又獲得了極大加強。
建立Handler
和客戶端同樣,只重寫了消息讀取方法channelRead(注意這裏不是channelRead0)、捕捉異常方法exceptionCaught。
另外服務器端Handler繼承的是ChannelInboundHandlerAdapter,而不是SimpleChannelInboundHandler,至於這二者的區別,這裏不贅述,你們自行百度吧。
代碼以下:
package com.jcj.helloworld; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; /** * @Auther: 江成軍 * @Date: 2020/6/1 11:47 * @Description: 服務器端I/O處理類 */ @ChannelHandler.Sharable public class HandlerServerHello extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //處理收到的數據,並反饋消息到到客戶端 ByteBuf in = (ByteBuf) msg; System.out.println("收到客戶端發過來的消息: " + in.toString(CharsetUtil.UTF_8)); //寫入併發送信息到遠端(客戶端) ctx.writeAndFlush(Unpooled.copiedBuffer("你好,我是服務端,我已經收到你發送的消息", CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //出現異常的時候執行的動做(打印並關閉通道) cause.printStackTrace(); ctx.close(); } }
以上代碼很簡潔,你們注意和客戶端Handler類進行比較。
建立服務器端啓動類
服務器端啓動類比客戶端啓動類稍顯複雜一點,先貼出代碼以下:
package com.jcj.helloworld; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import java.net.InetSocketAddress; /** * @Auther: 江成軍 * @Date: 2020/6/1 11:51 * @Description: 服務器端啓動類 */ public class AppServerHello { private int port; public AppServerHello(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup group = new NioEventLoopGroup();//Netty的Reactor線程池,初始化了一個NioEventLoop數組,用來處理I/O操做,如接受新的鏈接和讀/寫數據 try { ServerBootstrap b = new ServerBootstrap();//用於啓動NIO服務 b.group(group) .channel(NioServerSocketChannel.class) //經過工廠方法設計模式實例化一個channel .localAddress(new InetSocketAddress(port))//設置監聽端口 .childHandler(new ChannelInitializer<SocketChannel>() { //ChannelInitializer是一個特殊的處理類,他的目的是幫助使用者配置一個新的Channel,用於把許多自定義的處理類增長到pipline上來 @Override public void initChannel(SocketChannel ch) throws Exception {//ChannelInitializer 是一個特殊的處理類,他的目的是幫助使用者配置一個新的 Channel。 ch.pipeline().addLast(new HandlerServerHello());//配置childHandler來通知一個關於消息處理的InfoServerHandler實例 } }); //綁定服務器,該實例將提供有關IO操做的結果或狀態的信息 ChannelFuture channelFuture= b.bind().sync(); System.out.println("在" + channelFuture.channel().localAddress()+"上開啓監聽"); //阻塞操做,closeFuture()開啓了一個channel的監聽器(這期間channel在進行各項工做),直到鏈路斷開 channelFuture.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync();//關閉EventLoopGroup並釋放全部資源,包括全部建立的線程 } } public static void main(String[] args) throws Exception { new AppServerHello(18080).run(); } }
代碼說明:
EventLoopGroup,實際項目中,這裏建立兩個EventLoopGroup的實例,一個負責接收客戶端的鏈接,另外一個負責處理消息I/O,這裏爲了簡單展現流程,讓一個實例把這兩方面的活都幹了。
NioServerSocketChannel,經過工廠經過工廠方法設計模式實例化一個channel,這個在你們尚未可以熟練使用Netty進行項目開發的狀況下,不用去深究。
到這裏,咱們就把服務器端和客戶端都寫完了 ,如何運行呢,先在服務器端啓動類上右鍵,點Run 'AppServerHello.main()'菜單運行,見下圖:
而後,再一樣的操做,運行客戶端啓動類,就能看見效果了。
本文的內容就到這裏結束了,但願本文可以讓你們對Netty有一個總體的認識,並大概瞭解其開發流程。
Netty的功能不少,本文只是一個入門的介紹,若是你們對於Netty開發有興趣,能夠關注我並給我留言,我會根據關注和留言狀況,陸續再撰寫Netty實戰開發的文章。
獲得確定和正向反饋,纔有繼續寫下去的願望和動力,畢竟寫這種事無鉅細的文章,仍是挺費精力的。
若是對Netty很感興趣,同時又等不及文章的龜速更新,能夠參考一下視頻
51CTO:Netty教程:十二個實例帶你輕鬆掌握Netty
騰訊課堂:Netty教程:十二個實例帶你輕鬆掌握Netty
CSDN學院:Netty教程:十二個實例帶你輕鬆掌握Netty
網易雲課堂:Netty教程:十二個實例帶你輕鬆掌握Netty