日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Netty从零开始(一)

發布時間:2025/3/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Netty从零开始(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

現在我們使用通用應用程序或包進行通信。例如,我們經常使用HTTP客戶端庫從Web服務器檢索信息,并通過Web服務調用遠程過程調用。然而,通用協議或其實現有時不能很好地擴展。這就像我們不使用通用HTTP服務器來交換大量文件,電子郵件和近實時消息,如財務信息和多人游戲數據。需要的是高度優化的協議實現,專門用于特殊目的。例如,您可能希望實現針對基于AJAX的聊天應用程序,媒體流或大型文件傳輸進行了優化的HTTP服務器。你甚至可以設計和實施一個全新的協議,這個協議是根據你的需要而定制的。另一個不可避免的情況是當您必須處理舊版專有協議以確保與舊系統的互操作性。在這種情況下重要的是我們能夠快速實現該協議,而不會犧牲最終應用程序的穩定性和性能。

方案

Netty項目是為了快速開發可維護的高性能高可擴展性協議服務器和客戶端而努力提供異步事件驅動的網絡應用程序框架和工具。換句話說,Netty是一個NIO客戶端服務器框架,可以快速輕松地開發諸如協議服務器和客戶端之類的網絡應用程序。它大大簡化了網絡編程流程,如TCP和UDP套接字服務器開發。

?

“快速和容易”并不意味著由此產生的應用程序將遭受可維護性或性能問題的困擾。Netty經過精心設計,實現了許多協議,如FTP,SMTP,HTTP以及各種基于二進制和基于文本的傳統協議。因此,Netty成功地找到了一種方法來實現輕松的開發,性能,穩定性和靈活性,而無需妥協。

?

有些用戶可能已經找到了聲稱具有相同優勢的其他網絡應用程序框架,您可能想問問Netty與他們的區別。答案是它建立的哲學。Netty旨在為您提供API和執行方面最舒適的體驗,從第一天開始。這不是有形的東西,但你會意識到,這個哲學將使你的生活更容易,當你閱讀本指南和玩Netty的時候。

?

好了,以上就是關于netty的一個官網的初步介紹吧。下面進入搭建最簡單的服務器的環節,我這里會按照官網的思路走,不過不會完全一點不差。好了,我們開始:

建立項目

首先我們需要建立項目,如下圖所示:

項目名稱是NettyDemo,官網建議使用JDK1.6以上,我這里使用的JDK1.8,然后加入使用maven導入Netty依賴:

<dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.6.Final</version></dependency> </dependencies>

那么現在我們可以正式開始我們的項目編寫了。

?

編寫一個Discard服務器(按我理解就是啥也不干的服務器,別著急反駁,往下看)

世界上最簡單的協議不是“hello world”,而是。。。。什么也不做的協議Discard,丟棄的意思,服務端丟棄,那就是啥也不做的協議唄(嘗試把協議理解為用戶自定義功能)。

想要實現一個Discard協議,那么我們唯一需要做的就是忽略所有接收到的數據。讓我們從處理器實現開始,它處理由netty生成的I/O事件。

首先我們創建一個java包:netty_beginner,然后在里面創建一個類DiscardServerHandler

類的內容如下:

package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;/*** Created by moon on 2017/4/5.*/ public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // (2) // super.channelRead(ctx, msg);((ByteBuf) msg).release(); // (3) // ByteBuf in = (ByteBuf) msg; // try { // while (in.isReadable()) { // System.out.print((char) in.readByte()); // System.out.flush(); // } // } finally { // ReferenceCountUtil.release(msg); // }}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // (5) // super.exceptionCaught(ctx, cause);cause.printStackTrace();ctx.close();} }

?

  • DiscardServerHandler?繼承自ChannelInboundHandlerAdapter,它是?ChannelInboundHandler.?ChannelInboundHandler的實現。提供可以覆蓋的各種事件處理程序方法。現在,只需要擴展ChannelInboundHandlerAdapter即可,而不是自己實現處理程序接口。
  • 在這里,我們重寫通道讀取channelRead()事件處理方法。每當從客戶端收到新數據時,都會使用接收到的消息調用此方法。在這個例子中,接收到的消息的類型是ByteBuf。
  • 為了實現DISCARD協議,處理程序必須忽略收到的消息。ByteBuf是一個引用計數對象,必須通過release()方法顯式釋放。請記住,處理程序有責任釋放傳遞給處理程序的引用計數對象。通常,channelRead()處理方法的實現方式如下: @Override public void channelRead(ChannelHandlerContext ctx, Object msg) {try {// Do something with msg} finally {ReferenceCountUtil.release(msg);} }
  • 當由于I / O錯誤或由于在處理事件時拋出異常而使得Netty拋出異常時,exceptionCaught()?事件將會被Throwable拋出。在大多數情況下,應該記錄捕獲到的異常,并在此關閉其關聯的通道,雖然這種方法的實現可以根據你想要處理的異常情況而有所不同。例如,您可能希望在關閉連接之前發送帶有錯誤代碼的響應消息。
  • 到目前位置一切順利。我們已經實現了DISCARD服務器的前半部分。現在剩下的是寫入使用DiscardServerHandler啟動服務器的main()方法。我們創建另外一個類:DiscardServer如下:

    package netty_beginner;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;/*** Created by moon on 2017/4/5.*/ public class DiscardServer {private int port;public DiscardServer(int port) {this.port = port;}public void run() throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap(); // (2)b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3).childHandler(new ChannelInitializer<SocketChannel>() { // (4)@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new DiscardServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128) // (5).childOption(ChannelOption.SO_KEEPALIVE, true); // (6)// Bind and start to accept incoming connections.ChannelFuture f = b.bind(port).sync(); // (7)// Wait until the server socket is closed.// In this example, this does not happen, but you can do that to gracefully// shut down your server.f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}public static void main(String[] args) throws InterruptedException {int port;if (args.length > 0) {port = Integer.parseInt(args[0]);} else {port = 8080;}new DiscardServer(port).run();} }

    ?

  • NioEventLoopGroup是一個處理I / O操作的多線程事件循環。Netty為不同類型的傳輸提供了各種EventLoopGroup實現。在這個例子中,我們正在實現一個服務器端應用程序,因此將使用兩個NioEventLoopGroup。第一個,通常稱為“老板”,接受傳入的連接。第二個,通常稱為“工人”,一旦老板接受連接并將接受的連接注冊給工作人員,就處理接受的連接的流量。使用多少線程以及它們如何映射到創建的通道取決于EventLoopGroup實現,甚至可以通過構造函數進行配置。
  • ServerBootstrap是一個幫助類,用于設置服務器。您可以直接使用Channel設置服務器。但是請注意,這是一個繁瑣的過程,在大多數情況下您不需要這樣做。
  • 在這里,我們指定使用NioServerSocketChannel類來實例化一個新的Channel來接受傳入的連接。(可以這么理解,每個客戶端連接我們服務端,我們都會為他們創建一個channel,那么這個channel對于面向對象的我們來說就是一個類,我們同意對于我們接受到的連接都初始化為:NioServerSocketChannel)
  • 這里指定的處理程序將始終由新接受的Channel進行評估。ChannelInitializer是一個特殊的處理程序,旨在幫助用戶配置新的Channel。很可能您想通過添加一些處理程序(如DiscardServerHandler)來配置新Channel的ChannelPipeline來實現您的網絡應用程序。隨著應用程序的復雜化,您可能會在管道中添加更多的處理程序,并將這個匿名類最終提取到頂級類中。(個人感覺說白了就是想自己實現包含自己處理邏輯的Channel,但是又需要包含一些通用的原有功能,咋辦,繼承唄,這就是為什么上面的DiscardServerHandler繼承netty的類)
  • 您還可以設置特定于Channel實現的參數。我們正在編寫一個TCP / IP服務器,因此我們可以設置套接字選項,如tcpNoDelay和keepAlive。請參閱ChannelOption的apidocs和特定的ChannelConfig實現,以獲得有關支持的ChannelOptions的概述。
  • 你有沒有注意到option()和childOption()?option()用于接受傳入連接的NioServerSocketChannel。childOption()用于在這種情況下由父級ServerChannel接受的通道,即NioServerSocketChannel。(我的理解就是前者用于配置我們父級Channel,后者用于配置我們自定義的子級Channel)。
  • 我們現在準備好了。剩下的是綁定到端口并啟動服務器。這里,我們綁定機器中所有NIC(網絡接口卡)的端口到8080。您現在可以根據需要調用bind()方法多次(具有不同的綁定地址)。
  • 恭喜,到了現在這個階段我們已經完成了。下面可以進行嘗試,那么在嘗試之前,我要說一句,這個例子非常好,就是一點比較費解,即使我開始測試,往本機8080端口發送內容,我們根本看不出來是否成功,因為我們把內容忽略了 - -!。所以改一下,我們的DiscardServerHandler改成如下,打印收到的字符:

    ?

    package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil;/*** Created by moon on 2017/4/5.*/ public class DiscardServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // super.channelRead(ctx, msg); // ((ByteBuf) msg).release();ByteBuf in = (ByteBuf) msg;try {while (in.isReadable()) {System.out.print((char) in.readByte());System.out.flush();}} finally {ReferenceCountUtil.release(msg);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // super.exceptionCaught(ctx, cause);cause.printStackTrace();ctx.close();} }

    ?

    ?

    然后我們啟動運行我們的main方法:

    由于我用的win,不是linux,所以下面的操作可能有人不好使,因為有的win默認沒有啟動telnet,這個自己網上百度,很容易。我們打開cmd,輸入telnet,進入一個新的窗口:

    ?

    ?

    ?

    然后我們可以查看幫助,輸入?/help,查看win下的使用方式:

    注意o,也就是open是我們所需的,我們使用命令:open localhost 8080如下圖:

    這說明已經連上了,別跟我似的一直以為在連接ing。。。。。 ?

    那么我們現在就可以聯系了,由于我們的邏輯是一個字符一個字符輸出,所以我們輸入hello,在idea控制臺會挨個字符輸出:

    那么到這里,說明我們的服務端小demo成功。

    ?

    寫一個Echo服務器

    到目前為止,我們一直都在假設服務端是沒有響應的。然而,服務器通常應該響應請求。讓我們學習如何通過實現ECHO協議向客戶端寫入響應消息,其中任何接收到的數據都將被發回。

    與前面部分實現的Discard服務器的唯一區別在于它將接收到的數據發回,而不是將接收的數據輸出到控制臺.因此,再次修改channelRead()方法就行了:

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ctx.write(msg); // (1)ctx.flush(); // (2) }

    ?

    ChannelHandlerContext對象提供了各種可以觸發各種I / O事件和操作的操作。在這里,我們調用write(Object)來逐字寫入接收到的消息。請注意,我們沒有像DISCARD示例中那樣發布接收的消息。這是因為,當Netty發布給電子郵件時,Netty會為您報告。

    如果再次運行telnet命令,您將看到服務器發送回發送給它的任何內容。(自行查看)

    ?

    編寫一個時間服務器

    本節中實現的協議是TIME協議。它與前面的示例不同之處在于,它發送一個包含32位整數的消息,而不接收任何請求,并在發送消息后關閉連接。在此示例中,您將學習如何構建和發送消息,并在完成時關閉連接。

    因為我們不是將忽略任何接收到的數據,而是在建立連接后立即發送消息,這次我們不能使用channelRead()方法。相反,我們應該覆蓋channelActive()方法。所以我們創建一個新的類TimeServerHandler,以下是實現:

    package netty_beginner;import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;/*** Created by moon on 2017/4/5.*/ public class TimeServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(final ChannelHandlerContext ctx) throws Exception {final ByteBuf time = ctx.alloc().buffer(4);time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));final ChannelFuture f = ctx.writeAndFlush(time);f.addListener(new ChannelFutureListener() {public void operationComplete(ChannelFuture future) throws Exception {assert f == future;ctx.close();}});}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {super.exceptionCaught(ctx, cause);} }

    ?

  • 如上所述,當建立連接并準備好發送時,將調用channelActive()方法。我們來寫一個32位整數,表示這個方法當前的時間。
  • 要發送一條新消息,我們需要分配一個包含消息的新緩沖區。我們要寫一個32位整數,因此我們需要一個容量至少為4個字節的ByteBuf。通過ChannelHandlerContext.alloc()獲取當前的ByteBufAllocator,并分配一個新的緩沖區。
  • 像往常一樣,我們編寫構造的消息。但等等,翻轉的地方在哪里?在NIO發送消息之前,我們是否曾經調用過java.nio.ByteBuffer.flip()?ByteBuf沒有這樣的方法,因為它有兩個指針;一個用于讀操作,另一個用于寫操作。當你寫入東西到ByteBuf中,寫索引增加,而讀索引并沒有改變。讀者索引和作者索引分別表示消息的開始和結束位置。相比之下,如果不調用flip方法,NIO緩沖區不能提供干凈的方式來確定消息內容的起始和結束位置。當您忘記翻轉緩沖區時,您將會遇到麻煩,因為你會發送不正確甚至是空的內容。在Netty中不會發生這樣的錯誤,因為我們針對不同的操作類型有不同的指針。你會發現它使你的生活更容易,當你習慣了 - 一個沒有翻轉的生活!另外要注意的是,ChannelHandlerContext.write()(和writeAndFlush())方法返回一個ChannelFuture。ChannelFuture表示尚未發生的I / O操作。這意味著任何請求的操作可能尚未執行,因為所有操作在Netty中都是異步的。例如,即使在發送消息之前,以下代碼也可能會關閉連接: Channel ch = ...; ch.writeAndFlush(message); ch.close(); 因此,您需要在ChannelFuture完成之后調用close()方法,該方法由write()方法返回,并且在寫入操作完成后通知其監聽器。請注意,close()也可能不會立即關閉連接,并且它返回ChannelFuture。
  • 當寫請求完成時,我們如何得到通知?這就像將ChannelFutureListener添加到返回的ChannelFuture一樣簡單。在這里,我們創建了一個新的匿名ChannelFutureListener,當操作完成時關閉通道。或者,您可以使用預定義的監聽器簡化代碼: f.addListener(ChannelFutureListener.CLOSE);
  • 要測試我們的時間服務器是否按預期工作,可以使用UNIX rdate命令:

    $ rdate -o <port> -p <host>

    ?

    由于我的是win我就不測試了。

    好了,這次就先到這里,明天繼續接下來的內容。

    1、為什么編程中建議使用netty而不是用jdk nio?

    如果對nio了解比較透徹的話,就不會糾結這個問題了,畢竟市面上流行的中間件,如mycat ,spark都是用的nio,當然使用netty的更多,如dubbo; 我們需要知道nio的原理,同時也不必亂...

    總結

    以上是生活随笔為你收集整理的Netty从零开始(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。