首頁技術(shù)文章正文

Netty快速入門教程[java培訓]

更新時間:2020-04-07 來源:黑馬程序員 瀏覽量:


Netty是一個提供 asynchronous event-driven (異步事件驅(qū)動)的網(wǎng)絡(luò)應(yīng)用框架,是一個用以快速開發(fā)高性能、高可靠性協(xié)議的服務(wù)器和客戶端。換句話說,Netty 是一個 NIO 客戶端服務(wù)器框架,使用它可以快速簡單地開發(fā)網(wǎng)絡(luò)應(yīng)用程序,比如服務(wù)器和客戶端的協(xié)議。Netty 大大簡化了網(wǎng)絡(luò)程序的開發(fā)過程比如 TCP 和 UDP 的 socket 服務(wù)的開發(fā)。 “快速和簡單”并不意味著應(yīng)用程序會有難維護和性能低的問題,Netty 是一個精心設(shè)計的框架,它從許多協(xié)議的實現(xiàn)中吸收了很多的經(jīng)驗比如 FTP、SMTP、HTTP、許多二進制和基于文本的傳統(tǒng)協(xié)議.因此,Netty 已經(jīng)成功地找到一個方式,在不失靈活性的前提下來實現(xiàn)開發(fā)的簡易性,高性能,穩(wěn)定性。推薦了解黑馬程序員java培訓課程。


讓我們開始吧


本章圍繞Netty 的核心架構(gòu),通過簡單的示例帶你快速入門。當你讀完本章節(jié),你馬上就可以用 Netty 寫出一個客戶端和服務(wù)器。


開始之前


在開始之前我們先說明下開發(fā)環(huán)境,我們使用netty-4.1.30這個版本,jdk使用1.8及以上版本。


<dependency>

    <groupId>io.netty</groupId>

    <artifactId>netty-all</artifactId>

    <version>4.1.30.Final</version>

</dependency>


jdk請自行下載。


先來個丟棄服務(wù)


世上最簡單的協(xié)議不是'Hello, World!' 而是 DISCARD(丟棄)。這個協(xié)議將會丟掉任何收到的數(shù)據(jù),而不響應(yīng)。 為了實現(xiàn) DISCARD 協(xié)議,你只需忽略所有收到的數(shù)據(jù)。讓我們從 handler (處理器)的實現(xiàn)開始,handler 是由Netty 生成用來處理 I/O 事件的。


先創(chuàng)建一個處理器


package com.netty.first;

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

/**

* 處理服務(wù)端 channel.

*/

public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)

System.out.println(msg);

// 默默地丟棄收到的數(shù)據(jù)

((ByteBuf) msg).release(); // (3)

   }

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)

// 當出現(xiàn)異常就關(guān)閉連接

cause.printStackTrace();

ctx.close();

   }

}


(1) DiscardServerHandler 繼承自 ChannelInboundHandlerAdapter ,這個類實現(xiàn)了ChannelInboundHandler接口,ChannelInboundHandler提供了許多事件處理的接口方法,然后你可以覆蓋這些方法?,F(xiàn)在僅僅只需要繼承ChannelInboundHandlerAdapter 類而不是你自己去實現(xiàn)接口方法。 

(2)這里我們覆蓋了chanelRead() 事件處理方法。每當從客戶端收到新的數(shù)據(jù)時,這個方法會在收到消息時被調(diào)用,這個例子中,收到的消息的類型是 ByteBuf。

(3)為了實現(xiàn)DISCARD協(xié)議,處理器不得不忽略所有接受到的消息。ByteBuf是一個引用計數(shù)對象,這個對象必須顯示地調(diào)用release() 方法來釋放。請記住處理器的職責是釋放所有傳遞到處理器的引用計數(shù)對象。通常, channelRead() 方法的實現(xiàn)就像下面的這段代碼:


@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

    try {

// Do something with msg

   } finally {

ReferenceCountUtil.release(msg);

   }

}

(4)exceptionCaught()事件處理方法是當出現(xiàn)Throwable對象才會被調(diào)用,即當Netty由于IO錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應(yīng)該被記錄下來并且把關(guān)聯(lián)的 channel 給關(guān)閉掉。然而這個方法的處理方式會在遇到不同異常的情況下有不同的實現(xiàn),比如你可能想在關(guān)閉連接之前發(fā)送一個錯誤碼的響應(yīng)消息。


編寫服務(wù)端代碼

目前為止一切都還不錯,我們已經(jīng)實現(xiàn)了DISCARD服務(wù)器的一半功能,剩下的需要編寫一個main()方法來啟動服務(wù)端的DiscardServerHandler 。


package com.netty.first;

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;

/**

* 丟棄任何進入的數(shù)據(jù)

*/

public class DiscardServer {

    private int port;

    public DiscardServer(int port) {

        this.port = port;

   }

    public void run() throws Exception {

        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)

            // 綁定端口,開始接收進來的連接

            ChannelFuture f = b.bind(port).sync(); // (7)

             // 等待服務(wù)器 socket 關(guān)閉 。

            // 在這個例子中,這不會發(fā)生,但你可以優(yōu)雅地關(guān)閉你的服務(wù)器。

            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();

   }

}


1、NioEventLoopGroup是用來處理I/O操作的多線程事件循環(huán)器,Netty提供了許多不同的EventLoopGroup的實現(xiàn)用來處理不同的傳輸。在這個例子中我們實現(xiàn)了一個服務(wù)端的應(yīng)用,因此會有2個 NioEventLoopGroup 會被使用。第一個經(jīng)常被叫做‘boss’,用來接收進來的連接。第二個經(jīng)常被叫做‘worker’,用來處理已經(jīng)被接收的連接,一旦‘boss’接收到連接,就會把連接信息注冊到‘worker’上。如何知道多少個線程已經(jīng)被使用,如何映射到已經(jīng)創(chuàng)建的 Channel上都需要依賴于 EventLoopGroup 的實現(xiàn),并且可以通過構(gòu)造函數(shù)來配置他們的關(guān)系。

2、ServerBootstrap是一個啟動NIO服務(wù)的輔助啟動類。你可以在這個服務(wù)中直接使用 Channel,但是這會是一個復(fù)雜的處理過程,在很多情況下你并不需要這樣做。

3、這里我們指定使用NioServerSocketChannel類來舉例說明一個新的 Channel 如何接收進來的連接。

4、這里的事件處理類經(jīng)常會被用來處理一個最近的已經(jīng)接收的Channel。ChannelInitializer是一個特殊的處理類,他的目的是幫助使用者配置一個新的Channel。也許你想通過增加一些處理類比如DiscardServerHandler 來配置一個新的 Channel 或者其對應(yīng)的ChannelPipeline 來實現(xiàn)你的網(wǎng)絡(luò)程序。當你的程序變的復(fù)雜時,可能你會增加更多的處理類到 pipline 上,然后提取這些匿名類到最頂層的類上。

5、你可以設(shè)置這里指定的Channel實現(xiàn)的配置參數(shù)。我們正在寫一個TCP/IP的服務(wù)端,因此我們被允許設(shè)置socket的參數(shù)選項比如tcpNoDelay 和 keepAlive。請參考 ChannelOption 和詳細的ChannelConfig實現(xiàn)的接口文檔以此可以對ChannelOption 的有一個大概的認識。 

6、你關(guān)注過 option() 和 childOption() 嗎?option() 是提供給NioServerSocketChannel 用來接收進來的連接。childOption() 是提供給由父管道 ServerChannel接收到的連接,在這個例子中也是 NioServerSocketChannel。

7、我們繼續(xù),剩下的就是綁定端口然后啟動服務(wù)。這里我們在機器上綁定了機器所有網(wǎng)卡上的8080端口。當然現(xiàn)在你可以多次調(diào)用bind() 方法(基于不同綁定地址)。恭喜!你已經(jīng)熟練地完成了第一個基于 Netty 的服務(wù)端程序。


查看收到的數(shù)據(jù)

現(xiàn)在我們已經(jīng)編寫出我們第一個服務(wù)端,我們需要測試一下他是否真的可以運行。最簡單的測試方法是用telnet命令。例如,你可以在命令行上輸入telnet localhost 8080或者其他類型參數(shù)。

netty快速入門01

netty快速入門02


在telnet終端中輸入任意字符,服務(wù)端向控制臺輸出信息。證明服務(wù)端接收到客戶端發(fā)送的消息了。但是我們并不能看到服務(wù)端接收到了什么東西,我們可以把channelRead方法改成如下內(nèi)容:


@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)

    //System.out.println(msg);

    ByteBuf message = (ByteBuf) msg;

    System.out.println(message.toString(CharsetUtil.US_ASCII));

    // 默默地丟棄收到的數(shù)據(jù)

   ((ByteBuf) msg).release(); // (3)

}


這樣控制臺就可以看到客戶端發(fā)送的數(shù)據(jù)了。

netty快速入門03


寫個應(yīng)答服務(wù)器

到目前為止,我們雖然接收到了數(shù)據(jù),但沒有做任何的響應(yīng)。然而一個服務(wù)端通常會對一個請求作出響應(yīng)。讓我們學習怎樣在ECHO協(xié)議的實現(xiàn)下編寫一個響應(yīng)消息給客戶端,這個協(xié)議針對任何接收的數(shù)據(jù)都會返回一個響應(yīng)。

和 discard server 唯一不同的是把在此之前我們實現(xiàn)的channelRead()方法,返回所有的數(shù)據(jù)替代打印接收數(shù)據(jù)到控制臺上的邏輯。因此,需要把channelRead()方法修改如下:


@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

    ctx.write(msg); // (1)

    ctx.flush(); // (2)

}

(1)ChannelHandlerContext對象提供了許多操作,使你能夠觸發(fā)各種各樣的I/O事件和操作。這里我們調(diào)用了write(Object) 方法來逐字地把接受到的消息寫入。請注意不同于DISCARD的例子我們并沒有釋放接受到的消息,這是因為當寫入的時候 Netty 已經(jīng)幫我們釋放了。


(2)ctx.write(Object)方法不會使消息寫入到通道上,他被緩沖在了內(nèi)部,你需要調(diào)用 ctx.flush() 方法來把緩沖區(qū)中數(shù)據(jù)強行輸出。或者你可以用更簡潔的cxt.writeAndFlush(msg)以達到同樣的目的。

如果你再一次運行telnet命令,你會看到服務(wù)端會發(fā)回一個你已經(jīng)發(fā)送的消息。

猜你喜歡:

JDK8有哪些新特性?


分享到:
在線咨詢 我要報名
和我們在線交談!