1. 程式人生 > >Netty入門 - 秒懂

Netty入門 - 秒懂

Netty 入門

瘋狂創客圈 Java 分散式聊天室【 億級流量】實戰系列之 -入門【 部落格園 總入口


文章目錄

前言:

問題: 我們需要高度優化的協議

​ 現在我們使用通用應用程式或包進行通訊。例如,我們經常使用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 Handler 處理器

編寫一個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)
  @Override
  public 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);
         }
  }
  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
  													throws Exception { // (5)
  //        super.exceptionCaught(ctx, cause);
      cause.printStackTrace();
      ctx.close();
  }
  }

​ DiscardServerHandler 繼承自ChannelInboundHandlerAdapter,它是 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 伺服器

​ 到目前位置一切順利。我們已經實現了DISCARD伺服器的前半部分。現在剩下的是寫入使用DiscardServerHandler啟動伺服器的main()方法。

​ 我們建立另外一個類:DiscardServer,實現 Discard 服務的功能。

​ 如下:

 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)
                      @Override
                      public 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執行緒組。一般的伺服器,都使用兩個以上的執行緒組。

  • 第一個,通常稱為“Boss”,用於接受客戶端的連線。

  • 第二個,通常稱為“Worker”,一旦“Boss” 接受客戶端的連線,並將接受的連線註冊給“Worker”。

    “Boss”負責連線的監聽,“Worker”負責處理連線的輸入和輸出。

    一個執行緒組,包含一條或者多條執行緒。

啟動幫助類

​ ServerBootstrap是一個幫助類,用於設定伺服器。可以不用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的概述。

設定Channel 通道的選項

​ 你有沒有注意到option()和childOption()?

​ option()用於配置 伺服器連線監聽通道 ,也就是 NioServerSocketChannel。

​ childOption()用於配置每一個客戶端連線成功的通道 —— NioSocketChannel。

​ 我們現在準備好了。剩下的是繫結到埠並啟動伺服器。這裡,我們繫結機器中埠 (比如 8080)。如果有多個地址,您現在可以根據需要呼叫 bind()方法多次(具有不同的繫結地址)。

​ 恭喜,到了現在這個階段我們已經完成了。下面可以進行嘗試,那麼在嘗試之前,我要說一句,這個例子非常好,就是一點比較費解,即使我開始測試。

步驟是:

  • 首先啟動伺服器的main方法。
  • 然後,通過telnet ,本機8080埠傳送內容,看看是否成功

測試:傳送訊息到Discard伺服器

我們開啟cmd,輸入 telnet,進入一個新的視窗:

img

我們使用 telnet 命令:open localhost 8080 ,開啟Discard伺服器的telnet連線。

如下圖:

img

如果忘記了命令,可以使用幫助命令。 這個幫助命令為: ?/help

連線成功後,我們可以輸入任何內容,比如 hello,在idea控制檯會挨個字元輸出:

img

寫在最後

​ 至此為止,可以看到,Netty 的開發,其實是容易入門滴。

​ 下一篇: Netty 的 Echo 伺服器,比這個例子稍微複雜一點點。


瘋狂創客圈 Java 死磕系列

  • Java (Netty) 聊天程式【 億級流量】實戰 開源專案實戰