Netty筆記

Netty是一款NIO客戶端服務器框架,可以快速輕鬆地開發協議服務器和客戶端等網絡應用程序。 它極大地簡化並簡化了TCP和UDP套接字服務器等網絡編程。

'快速和簡單'並不意味着由此產生的應用程序將受到可維護性或性能問題的困擾。 Netty的設計經驗非常豐富,包括FTP,SMTP,HTTP以及各種基於二進制和文本的傳統協議等。 因此,Netty成功地找到了一種方法來實現輕鬆的開發,性能,穩定性和靈活性,而無需妥協。

Features

Design

用於各種傳輸類型的統一API - 阻塞和非阻塞套接字

基於靈活和可擴展的事件模型,可以清晰地分離問題

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

真正的無連接數據報套接字支持(自3.1以來)

Ease of use

沒有額外的依賴關係,JDK 5(Netty 3.x)或6(Netty 4.x)就足夠了

注意:某些組件(如HTTP / 2)可能有更多要求。 請參閱需求頁面瞭解更多信息

Performance

Better throughput, lower latency(吞吐量更好,延遲更低)

Less resource consumption(更少的資源消耗)

Minimized unnecessary memory copy(最小化不必要的內存拷貝)

Security

Complete SSL/TLS and StartTLS support(完整的SSL / TLS和StartTLS支持)

---------------------------------------------------------------------------------

netty 4.x

Preface

The Problem

現在我們使用通用應用程序或庫來相互通信。例如,我們經常使用HTTP客戶端庫從Web服務器檢索信息並通過Web服務調用遠程過程調用。但是,通用協議或其實現有時不能很好地擴展。這就好比我們不使用通用HTTP服務器來交換巨大的文件,電子郵件和接近實時的消息,例如財務信息和多人遊戲數據。所需要的是高度優化的專用於特殊用途的協議實現。例如,您可能希望實施針對基於AJAX的聊天應用程序,媒體流或大型文件傳輸進行優化的HTTP服務器。另一個不可避免的情況是,您必須處理傳統專有協議以確保與舊系統的互操作性。在這種情況下重要的是我們可以多快地實現該協議,同時不會犧牲最終應用程序的穩定性和性能。

The Solution

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

“快速而簡單”並不意味着由此產生的應用程序將受到可維護性或性能問題的困擾。 Netty的設計經驗非常豐富,包括FTP,SMTP,HTTP以及各種基於二進制和文本的傳統協議等。 因此,Netty成功地找到了一種方法來實現輕鬆的開發,性能,穩定性和靈活性,而無需妥協。

有些用戶可能已經找到了其他宣稱具有相同優勢的網絡應用程序框架,並且您可能想問一下Netty與他們有何不同之處。 答案是基於netty的哲學。 Netty旨在爲您提供在第一天的API和實施方面最舒適的體驗。 這不是有形的,但你會意識到,當你閱讀本指南並與Netty一起玩時,這一哲學將使你的生活變得更加輕鬆。

Getting Started

本章將以簡單的例子來介紹Netty的核心結構,以便您快速入門。 當你在本章結尾時,你將能夠在Netty之上立即編寫客戶端和服務器。

Before Getting Started

運行本章中示例的最低要求只有兩個; 最新版本的Netty和JDK 1.6或更高版本。

Writing a Discard Server

世界上最簡單的協議不是'Hello, World!' 而是DISCARD。 這是一個協議,丟棄任何收到的數據沒有任何迴應。

要實施DISCARD協議,您只需要忽略所有收到的數據。 讓我們從處理器實現開始,它處理由Netty生成的I / O事件。

package io.netty.example.discard;

import io.netty.buffer.ByteBuf;

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

/**
 * Handles a server-side channel.
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

1、DiscardServerHandler繼承了ChannelInboundHandlerAdapter,它是ChannelInboundHandler的一個實現。 ChannelInboundHandler提供了可以覆蓋的各種事件處理程序方法。 目前,只需擴展ChannelInboundHandlerAdapter而不是自己實現處理程序接口即可。

2、我們在這裏覆蓋channelRead()事件處理程序方法。 每當接收到來自客戶端的新數據時,都會使用收到的消息調用此方法。 在這個例子中,接收到的消息的類型是ByteBuf。

3、爲了實現DISCARD協議,處理程序必須忽略收到的消息。 ByteBuf是一個引用計數的對象,必須通過release()方法明確釋放。 請記住,處理程序的職責是釋放傳遞給處理程序的任何引用計數的對象。 通常,channelRead()處理程序方法的實現如下所示:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        // Do something with msg
    } finally {
        ReferenceCountUtil.release(msg);
    }
}

4、當由於I / O錯誤或由於在處理事件時拋出異常而導致的處理程序實現導致Netty引發異常時,會使用Throwable調用exceptionCaught()事件處理程序方法。 在大多數情況下,應該記錄捕獲到的異常並關閉其相關通道,儘管此方法的實現可能因您想要處理異常情況而需要做的不同而有所不同。 例如,您可能希望在關閉連接之前發送包含錯誤代碼的響應消息。

到現在爲止還挺好。 我們已經實施了DISCARD服務器的前半部分。 現在剩下的就是編寫用DiscardServerHandler啓動服務器的main()方法。

package io.netty.example.discard;
    
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;
    
/**
 * Discards any incoming data.
 */
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)
    
            // 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 Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new DiscardServer(port).run();
    }
}

1、NioEventLoopGroup是一個處理I / O操作的多線程事件循環。 Netty爲不同類型的傳輸提供了各種EventLoopGroup實現。 在這個例子中,我們正在實現一個服務器端應用程序,因此將使用兩個NioEventLoopGroup。 第一個通常稱爲“boss”,接受傳入的連接。 第二個通常稱爲“worker”,一旦老闆接受連接並將接受的連接註冊給工作人員,則處理接受連接的流量。 使用多少個線程以及它們如何映射到創建的通道取決於EventLoopGroup實現,甚至可以通過構造函數進行配置。

2、ServerBootstrap是設置服務器的輔助類。 您可以直接使用Channel設置服務器。 但是,請注意,這是一個乏味的過程,在大多數情況下您不需要這樣做。

3、在這裏,我們指定使用NioServerSocketChannel類來實例化一個新的Channel來接受傳入的連接。

4、此處指定的處理程序將始終由新接受的通道評估。 ChannelInitializer是一個特殊的處理程序,旨在幫助用戶配置新的通道。 您很可能希望通過添加一些處理程序(如DiscardServerHandler)來配置新通道的ChannelPipeline,以實現您的網絡應用程序。 隨着應用程序變得複雜,您可能會向流水線添加更多處理程序,並最終將此匿名類提取到頂級類中。

5、您還可以設置特定於Channel實現的參數。 我們正在編寫一個TCP / IP服務器,因此我們可以設置套接字選項,如tcpNoDelay和keepAlive。 請參閱ChannelOption的apidocs和特定的ChannelConfig實現,以獲得關於支持的ChannelOptions的概述。

6、你有沒有注意到option()和childOption()? option()用於接受傳入連接的NioServerSocketChannel。 childOption()適用於父ServerChannel接受的通道,在這種情況下是NioServerSocketChannel。

7、我們現在準備好了。 剩下的就是綁定到端口並啓動服務器。 在這裏,我們綁定到機器中所有NIC(網絡接口卡)的端口8080。 您現在可以根據需要多次調用bind()方法(使用不同的綁定地址)。

恭喜! 你剛剛在Netty之上完成了你的第一臺服務器。

Looking into the Received Data

現在我們已經編寫了我們的第一臺服務器,我們需要測試它是否真的有效。 測試它的最簡單方法是使用telnet命令。 例如,您可以在命令行中輸入telnet localhost 8080並輸入內容。

但是,我們可以說服務器工作正常嗎? 我們無法真正瞭解,因爲它是一個丟棄服務器。 你根本得不到任何迴應。 爲了證明它確實有效,讓我們修改服務器以打印它收到的內容。

我們已經知道,只要收到數據,channelRead()方法就會被調用。 讓我們將一些代碼放入DiscardServerHandler的channelRead()方法中:

@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)
    }
}

1、這個效率低下的循環實際上可以簡化爲:System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))

2、或者,你可以在這裏做in.release()。

如果您再次運行telnet命令,您將看到服務器打印收到的內容。

Writing an Echo Server

到目前爲止,我們一直在消費數據而沒有做出任何迴應。 然而,服務器通常應該對請求做出響應。 讓我們學習如何通過實現ECHO協議向客戶端寫入響應消息,其中接收到的數據將被髮回。

與我們在前面部分中實現的丟棄服務器唯一的區別在於,它將接收到的數據發送回來,而不是將接收到的數據打印到控制檯。 因此,重新修改channelRead()方法就足夠了:

@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg); // (1)
        ctx.flush(); // (2)
    }
1、一個ChannelHandlerContext對象提供了各種操作,使您能夠觸發各種I / O事件和操作。 在這裏,我們調用write(Object)逐字寫入收到的消息。 請注意,我們沒有發佈收到的消息,與我們在DISCARD示例中所做的不同。 這是因爲當Netty寫出來時,Netty會爲你發佈它。

2、ctx.write(Object)不會將消息寫出來。 它在內部緩衝,然後通過ctx.flush()刷新到線路。 另外,爲簡潔起見,你可以調用ctx.writeAndFlush(msg)。

如果你再次運行telnet命令,你會看到服務器發回你發送給它的任何東西。

Writing a Time Server

在本節中實現的協議是TIME協議。 它與前面的例子不同之處在於,它發送的消息包含一個32位整數,但未收到任何請求,並在發送消息後關閉連接。 在本例中,您將學習如何構建和發送消息,並在完成時關閉連接。

因爲我們將忽略任何收到的數據,但只要連接建立就發送消息,我們不能使用此次的channelRead()方法。 相反,我們應該重寫channelActive()方法。 以下是實現:

package io.netty.example.time;

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(final ChannelHandlerContext ctx) { // (1)
        final ByteBuf time = ctx.alloc().buffer(4); // (2)
        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
        
        final ChannelFuture f = ctx.writeAndFlush(time); // (3)
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                assert f == future;
                ctx.close();
            }
        }); // (4)
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章