網絡應用框架Netty快速入門

一 初遇Netty

Netty是什麼?java

Netty 是一個提供 asynchronous event-driven (異步事件驅動)的網絡應用框架,是一個用以快速開發高性能、可擴展協議的服務器和客戶端。面試

Netty能作什麼?bootstrap

Netty 是一個 NIO 客戶端服務器框架,使用它能夠快速簡單地開發網絡應用程序,好比服務器(HTTP服務器,FTP服務器,WebSocket服務器,Redis的Proxy服務器等等)和客戶端的協議。Netty 大大簡化了網絡程序的開發過程好比 TCP 和 UDP 的 socket 服務的開發。緩存

Netty爲何好?安全

Netty是創建在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象,使用它你能夠更容易利用Java NIO提升服務端和客戶端的性能。服務器

Netty的特性微信

1. 設計網絡

1.1 統一的API,適用於不一樣的協議(阻塞和非阻塞)

1.2 基於可擴展和靈活的事件驅動模型

1.3高度可定製的線程模型 - 單線程,一個或多個線程池,如SEDA

1.4真正的無鏈接數據報套接字支持(自3.1以來)
複製代碼

2. 性能框架

2.1更好的吞吐量,低延遲

 2.2更省資源

 2.3儘可能減小沒必要要的內存拷貝
複製代碼

3. 安全異步

完整的SSL / TLS和StartTLS協議的支持
複製代碼

4. 易用性

4.1 官方有詳細的使用指南

 4.2 對環境要求很低
複製代碼

NIO和IO的區別是什麼?

1. 一個面向字節一個面向緩衝;

IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。

2. NIO是非阻塞IO,IO是阻塞IO

阻塞意味着當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入,該線程在此期間不能再幹任何事情了。而非阻塞不會這樣。

二 Netty使用

環境要求:

  • JDK 7+
  • Maven 3.2.x
  • Netty 4.x

Maven依賴:

<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
			<version>4.0.32.Final</version>
		</dependency>
複製代碼

如下Netty examples來源: 官方文檔

2.1 寫個拋棄服務器

DiscardServerHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/** * handler 是由 Netty 生成用來處理 I/O 事件的。 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
    /** * 這裏咱們覆蓋了 chanelRead() 事件處理方法。 * 每當從客戶端收到新的數據時,這個方法會在收到消息時被調用。 *((ByteBuf) msg).release():丟棄數據 */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // 默默地丟棄收到的數據
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // 當出現異常就關閉鏈接
        cause.printStackTrace();
        ctx.close();
    }
}
複製代碼

目前咱們已經實現了 DISCARD 服務器的一半功能,剩下的須要編寫一個 main() 方法來啓動服務端的 DiscardServerHandler。

DiscardServer.java

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) {
        this.port = port;
    }

    public void run() throws Exception {
    	//在這個例子中咱們實現了一個服務端的應用,所以會有2個 NioEventLoopGroup 會被使用。
        //第一個常常被叫作‘boss’,用來接收進來的鏈接。
    	//第二個常常被叫作‘worker’,用來處理已經被接收的鏈接,一旦‘boss’接收到鏈接,就會把鏈接信息註冊到‘worker’上。
    	EventLoopGroup bossGroup = new NioEventLoopGroup(); 
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
        	//啓動 NIO 服務的輔助啓動類
            ServerBootstrap serverBootstrap = new ServerBootstrap(); 
            //用於處理ServerChannel和Channel的全部事件和IO。
            serverBootstrap.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 = serverBootstrap.bind(port).sync(); // (7)

            // 等待服務器 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();
    }
}
複製代碼

2.2 查看收到的數據

咱們剛剛已經編寫出咱們第一個服務端,咱們須要測試一下他是否真的能夠運行。最簡單的測試方法是用 telnet 命令。例如,你能夠在命令行上輸入telnet localhost 8080 或者其餘類型參數。

然而咱們能說這個服務端是正常運行了嗎?事實上咱們也不知道,由於他是一個 discard 服務,你根本不可能獲得任何的響應。爲了證實他仍然是在正常工做的,讓咱們修改服務端的程序來打印出他到底接收到了什麼。

咱們已經知道 channelRead() 方法是在數據被接收的時候調用。讓咱們放一些代碼到 DiscardServerHandler 類的 channelRead() 方法。

修改DiscardServerHandler類的channelRead(ChannelHandlerContext ctx, Object msg)方法以下:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf in = (ByteBuf) msg;
    try {
        while (in.isReadable()) { // (1)
            System.out.print((char) in.readByte());
            System.out.flush();
        }
    } finally {
        ReferenceCountUtil.release(msg); // (2)
    }
}
複製代碼

再次驗證,cmd下輸入:telnet localhost 8080。你將會看到服務端打印出了他所接收到的消息。

以下:

你在dos界面輸入的消息會被顯示出來

效果圖

2.3 寫個應答服務器

到目前爲止,咱們雖然接收到了數據,但沒有作任何的響應。然而一個服務端一般會對一個請求做出響應。讓咱們學習怎樣在 ECHO 協議的實現下編寫一個響應消息給客戶端,這個協議針對任何接收的數據都會返回一個響應。

和 discard server 惟一不一樣的是把在此以前咱們實現的 channelRead() 方法,返回全部的數據替代打印接收數據到控制檯上的邏輯。所以,須要把 channelRead() 方法修改以下:

@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg); 
        ctx.flush(); 
    }
複製代碼

再次驗證,cmd下輸入:telnet localhost 8080。你會看到服務端會發回一個你已經發送的消息。 以下:

下一篇咱們會學習如何用Netty實現聊天功能。

歡迎關注個人微信公衆號(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取):

微信公衆號
相關文章
相關標籤/搜索