Netty入門 - 秒懂

Netty 入門

<font size=3 face="黑體" > 瘋狂創客圈 Java 分佈式聊天室【 億級流量】實戰系列之 -入門【 博客園 總入口 】</font >html


@[toc]java

前言:

問題: 咱們須要高度優化的協議面試

​ 如今咱們使用通用應用程序或包進行通訊。例如,咱們常用HTTP客戶端庫從Web服務器檢索信息,並經過Web服務調用遠程過程調用。然而,通用協議或其實現有時不能很好地擴展。這就像咱們不使用通用HTTP服務器來交換大量文件,電子郵件和近實時消息,如財務信息和多人遊戲數據。編程

​ 咱們須要的是高度優化的協議實現,專門用於特殊目的。例如,您可能但願實現針對基於AJAX的聊天應用程序,媒體流或大型文件傳輸進行了優化的HTTP服務器。你甚至能夠設計和實施一個全新的協議,這個協議是根據你的須要而定製的。另外一個不可避免的狀況是當您必須處理舊版專有協議以確保與舊系統的互操做性。在這種狀況下重要的是咱們可以快速實現該協議,而不會犧牲最終應用程序的穩定性和性能。bootstrap

方案api

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

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

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

好了,以上就是關於netty的一個官網的初步介紹app

​ 下面進入搭建最簡單的服務器的環節,我這裏會按照官網的思路走,不過不會徹底一點不差。

​ 好了,咱們開始。

創建項目

首先咱們須要創建項目,以下圖所示:

項目名稱是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) 聊天程序【 億級流量】實戰 開源項目實戰
相關文章
相關標籤/搜索