1. 程式人生 > >Netty入門例項教程

Netty入門例項教程

因為接下來的專案要用到netty,所以就瞭解一下這個程式,奈何網上的教程都是稍微有點基礎的,所以,就寫一篇對於netty零基礎的,順便也記錄一下。

先扔幾個參考學習的網頁: 

關於NIO基礎的知識:https://my.oschina.net/andylucc/blog/614295

            http://www.cnblogs.com/dolphin0520/p/3919162.html 

          http://blog.csdn.net/wuxianglong/article/details/6604817  

我這裡所使用的:

maven依賴:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha2</version>
</dependency>

好了,我們開始。

一、首先,你要建立一個java工程,至於普通工程還是maven工程,看自己喜好,因為我這裡只是做為學習,就建了一個普通工程,然後把下載的jar包扔到lib中,引入即可。

  

二、接下來我們要搞清楚,netty是什麼玩意。

  官方那個給出的介紹是:Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、事件驅動的網路應用程式框架和工具,用以快速開發高效能、高可靠性的網路伺服器和客戶端程式。

  然後我們簡單理解一下,這玩意就是個程式,幹什麼的?netty是封裝java socket noi的。 類似的功能是 apache的mina。

  相對於Tomcat這種Web Server(顧名思義主要是提供Web協議相關的服務的),Netty是一個Network Server,是處於Web Server更下層的網路框架,也就是說你可以使用Netty模仿Tomcat做一個提供HTTP服務的Web容器。

  說白了,就是一個好使的處理Socket的東西。要是想了解詳細點,可以去看看官方的介紹。

三、回到正題我們開始寫所謂的“Hello World"

  這裡插一下,就是我們的的通訊是建立在一定的協議之上的,就比如我們常用的Web工程,前臺(瀏覽器)傳送一個請求,後臺做出相應返回相應的結果,這個通訊的過程亦是如此。

  在netty官方指南里面有講,世上最簡單的協議不是'Hello, World!' 而是 DISCARD(拋棄服務)

。這個協議將會拋棄任何收到的資料,而不響應。就是你客戶端傳送訊息,好,傳送過去了,伺服器也收到了,但是拋棄了。

  說白了,就是你發一條訊息給我,我收到了,但是我直接就把訊息拋棄了,不理你的。

其次,關於netty ,首先要搞清楚,這是建立在客戶端和服務端之間的。

我們先說服務端,服務端建立相應的規則,然後執行起來,等待客戶端訪問或者傳送”訊息“。好了,我們先建立服務端程式碼:

第一步:先建立相應的規則

package _01discard;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

/**
 * 服務端處理通道.這裡只是列印一下請求的內容,並不對請求進行任何的響應 DiscardServerHandler 繼承自
 * ChannelHandlerAdapter, 這個類實現了ChannelHandler介面, ChannelHandler提供了許多事件處理的介面方法,
 * 然後你可以覆蓋這些方法。 現在僅僅只需要繼承ChannelHandlerAdapter類而不是你自己去實現介面方法。
 *
 */
public class DiscardServerHandler extends ChannelHandlerAdapter {
    /**
     * 這裡我們覆蓋了chanelRead()事件處理方法。 每當從客戶端收到新的資料時, 這個方法會在收到訊息時被呼叫,
     * 這個例子中,收到的訊息的型別是ByteBuf
     * 
     * @param ctx
     *            通道處理的上下文資訊
     * @param msg
     *            接收的訊息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

        try {
            ByteBuf in = (ByteBuf) msg;
            // 列印客戶端輸入,傳輸過來的的字元
            System.out.print(in.toString(CharsetUtil.UTF_8));
        } finally {
            /**
             * ByteBuf是一個引用計數物件,這個物件必須顯示地呼叫release()方法來釋放。
             * 請記住處理器的職責是釋放所有傳遞到處理器的引用計數物件。
             */
            // 拋棄收到的資料
            ReferenceCountUtil.release(msg);
        }

    }

    /***
     * 這個方法會在發生異常時觸發
     * 
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**
         * exceptionCaught() 事件處理方法是當出現 Throwable 物件才會被呼叫,即當 Netty 由於 IO
         * 錯誤或者處理器在處理事件時丟擲的異常時。在大部分情況下,捕獲的異常應該被記錄下來 並且把關聯的 channel
         * 給關閉掉。然而這個方法的處理方式會在遇到不同異常的情況下有不 同的實現,比如你可能想在關閉連線之前傳送一個錯誤碼的響應訊息。
         */
        // 出現異常就關閉
        cause.printStackTrace();
        ctx.close();
    }

}

第二步:我們需要應用相應的規則。就是說,我們建立了接收訊息的規則,但是光建立規則有什麼用,僅僅只是一個規則,我們需要把這個規則”應用“起來,通常就是我們通常的”執行“。

package _01discard;

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;

/**
 * 丟棄任何進入的資料 啟動服務端的DiscardServerHandler
 */
public class DiscardServer {
    private int port;

    public DiscardServer(int port) {
        super();
        this.port = port;
    }

    public void run() throws Exception {

        /***
         * NioEventLoopGroup 是用來處理I/O操作的多執行緒事件迴圈器,
         * Netty提供了許多不同的EventLoopGroup的實現用來處理不同傳輸協議。 在這個例子中我們實現了一個服務端的應用,
         * 因此會有2個NioEventLoopGroup會被使用。 第一個經常被叫做‘boss’,用來接收進來的連線。
         * 第二個經常被叫做‘worker’,用來處理已經被接收的連線, 一旦‘boss’接收到連線,就會把連線資訊註冊到‘worker’上。
         * 如何知道多少個執行緒已經被使用,如何對映到已經建立的Channels上都需要依賴於EventLoopGroup的實現,
         * 並且可以通過建構函式來配置他們的關係。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        System.out.println("準備執行埠:" + port);
        try {
            /**
             * ServerBootstrap 是一個啟動NIO服務的輔助啟動類 你可以在這個服務中直接使用Channel
             */
            ServerBootstrap b = new ServerBootstrap();
            /**
             * 這一步是必須的,如果沒有設定group將會報java.lang.IllegalStateException: group not
             * set異常
             */
            b = b.group(bossGroup, workerGroup);
            /***
             * ServerSocketChannel以NIO的selector為基礎進行實現的,用來接收新的連線
             * 這裡告訴Channel如何獲取新的連線.
             */
            b = b.channel(NioServerSocketChannel.class);
            /***
             * 這裡的事件處理類經常會被用來處理一個最近的已經接收的Channel。 ChannelInitializer是一個特殊的處理類,
             * 他的目的是幫助使用者配置一個新的Channel。
             * 也許你想通過增加一些處理類比如NettyServerHandler來配置一個新的Channel
             * 或者其對應的ChannelPipeline來實現你的網路程式。 當你的程式變的複雜時,可能你會增加更多的處理類到pipline上,
             * 然後提取這些匿名類到最頂層的類上。
             */
            b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new DiscardServerHandler());// demo1.discard
                    // ch.pipeline().addLast(new
                    // ResponseServerHandler());//demo2.echo
                    // ch.pipeline().addLast(new
                    // TimeServerHandler());//demo3.time
                }
            });
            /***
             * 你可以設定這裡指定的通道實現的配置引數。 我們正在寫一個TCP/IP的服務端,
             * 因此我們被允許設定socket的引數選項比如tcpNoDelay和keepAlive。
             * 請參考ChannelOption和詳細的ChannelConfig實現的介面文件以此可以對ChannelOptions的有一個大概的認識。
             */
            b = b.option(ChannelOption.SO_BACKLOG, 128);
            /***
             * option()是提供給NioServerSocketChannel用來接收進來的連線。
             * childOption()是提供給由父管道ServerChannel接收到的連線,
             * 在這個例子中也是NioServerSocketChannel。
             */
            b = b.childOption(ChannelOption.SO_KEEPALIVE, true);
            /***
             * 繫結埠並啟動去接收進來的連線
             */
            ChannelFuture f = b.bind(port).sync();
            /**
             * 這裡會一直等待,直到socket被關閉
             */
            f.channel().closeFuture().sync();
        } finally {
            /***
             * 關閉
             */
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
  

   //將規則跑起來
    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new DiscardServer(port).run();
        System.out.println("server:run()");
    }
}

複製程式碼

第三步:我們現在相應的規則已經建立,並且”執行“規則的程式碼也OK,所以執行上面的   public static void main(String[] args) 啟動服務端。

  

此時服務端已經執行起來了,為等待訪問的狀態。

客戶端

因為這是一個簡單的demo,所以我們使用telnet 來充當client使用。當然,專案中肯定是根據需求來定製的。

首先開啟終端,我這裡是windows 系統,就以此為例,開啟cmd 視窗;

  

  鍵入  telnet 127.0.0.1 8080 回車,進入telnet 終端

  

  這裡補充一下,win系統預設是不開啟telnet 客戶端的,需要的朋友去控制面板>>程式>>開啟或者關閉windows功能裡面 勾選上,如下圖

  

好了,到了telnet 客戶端這一步,就可以測試訊息了

補充,預設的telnet 客戶端輸入是不顯示的,不過反正輸入之後控制檯有輸出就表示你這個過程跑通了。如:

 你也在終端使用 ctrl+] 來顯示,如:

走到這裡,整個過程大概是什麼樣子的,心裡就有個數了,然後就和學習普通框架一樣,開始的進階之路吧。