Netty 入門學習教程之簡易聊天室

0x01 Netty 入門學習教程之簡易聊天室

1.1 Netty 簡介

Netty是異步事件驅動的網絡應用程序框架
用於快速開發可維護的高性能協議服務器和客戶端。
img

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

“快速簡便”並不意味着最終的應用程序將遭受可維護性或性能問題的困擾。 Netty經過精心設計,結合了許多協議(例如FTP,SMTP,HTTP以及各種基於二進制和文本的舊式協議)的實施經驗。 結果,Netty成功地找到了一種無需妥協即可輕鬆實現開發,性能,穩定性和靈活性的方法。

1.1 功能

1.1.1 設計

  • 適用於各種傳輸類型的統一API-阻塞和非阻塞套接字
  • 基於靈活且可擴展的事件模型,可將關注點明確分離
  • 高度可定製的線程模型-單線程,一個或多個線程池,例如SEDA
  • 真正的無連接數據報套接字支持(從3.1開始)

1.1.2 使用方便

  • 記錄良好的Javadoc,用戶指南和示例
  • 沒有其他依賴關係,JDK 5(Netty 3.x)或6(Netty 4.x)就足夠了
    注意:某些組件(例如HTTP / 2)可能有更多要求。

1.1.3 性能

  • 更高的吞吐量,更低的延遲
  • 減少資源消耗
  • 減少不必要的內存複製

1.1.4 安全性

  • 安全
  • 完整的SSL / TLS和StartTLS支持

1.1.5 社區

  • 提前發佈,經常發佈
  • 自2003年以來,作者一直在編寫類似的框架,但他仍然發現您的反饋很寶貴!

0x02 Netty 文檔

2.1 Netty 討論社區

開始使用用戶指南和API參考, Netty 社區

2.2 最新穩定版本

當前最新穩定版本是4.1

2.3 第三方文章

我們經常發現世界上有一些人比我們更擅長技術寫作,並且爲社區寫了很多好文章。

https://netty.io/wiki/related-articles.html

2.4 示例

有幾個示例可以幫助您更好地使用Netty。 建議從第一個開始到最後一個結束。

2.4.1 基礎

Echo

Discard

Uptime

2.4.2 文本協議

Telnet

Quoto of the Moment

SecureChat

2.4.3 二進制協議

ObjectEcho

Factorial

WorldClock

2.4.4 Http

Snoop

FileServer

WebSockets

SPDY

CORS Demo

2.4.5 高級篇

ProxyServer

Port unfication

2.4.6 UDT

0x03 Netty 4.x 用戶指南

3.1 問題

Nowadays we use general purpose applications or libraries to communicate with each other. For example, we often use an HTTP client library to retrieve information from a web server and to invoke a remote procedure call via web services. However, a general purpose protocol or its implementation sometimes does not scale very well. It is like how we don’t use a general purpose HTTP server to exchange huge files, e-mail messages, and near-realtime messages such as financial information and multiplayer game data. What’s required is a highly optimized protocol implementation that is dedicated to a special purpose. For example, you might want to implement an HTTP server that is optimized for AJAX-based chat application, media streaming, or large file transfer. You could even want to design and implement a whole new protocol that is precisely tailored to your need. Another inevitable case is when you have to deal with a legacy proprietary protocol to ensure the interoperability with an old system. What matters in this case is how quickly we can implement that protocol while not sacrificing the stability and performance of the resulting application.

如今,我們使用通用應用程序或庫相互通信。例如,我們經常使用HTTP Client庫從Web服務器檢索信息,並通過Web服務調用遠程過程調用(RPC,Remote Procedure call)。但是,通用協議或其實現有時不能很好地擴展。就像我們不使用通用HTTP服務器來交換大文件,電子郵件和近乎實時的消息(例如財務信息和多人遊戲數據)一樣。所需要的是專門用於特殊目的的高度優化的協議實現。

例如,您可能想實現針對基於AJAX的聊天應用程序,媒體流或大文件傳輸進行了優化的HTTP服務器。您甚至可能想要設計和實現完全適合您的需求的全新協議。

另一個不可避免的情況是,您必須處理舊的專有協議以確保與舊系統的互操作性。在這種情況下,重要的是我們能夠在不犧牲最終應用程序的穩定性和性能的情況下,以多快的速度實現該協議。

3.2 解決方案

Netty項目致力於提供一個異步事件驅動的網絡應用程序框架和工具,以快速開發可維護的高性能和高可擴展性協議服務器和客戶端。

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

“快速簡便”並不意味着最終的應用程序將遭受可維護性或性能問題的困擾。 Netty經過精心設計,結合了從許多協議(例如FTP,SMTP,HTTP以及各種基於二進制和文本的舊式協議)的實施中獲得的經驗。結果,Netty成功地找到了一種無需妥協即可輕鬆實現開發,性能,穩定性和靈活性的方法。

一些用戶可能已經發現其他聲稱具有相同優勢的網絡應用程序框架,您可能想問一下Netty與他們有何不同。答案是它所基於的哲學。 Netty旨在從第一天開始就API和實施方面爲您提供最舒適的體驗。這不是有形的東西,但是您會認識到,當您閱讀本指南並與Netty一起玩時,這種哲學將使您的生活更加輕鬆。

3.3 入門

本章將通過簡單的示例介紹Netty的核心構造,以使您快速入門。 在本章結束時,您將可以立即在Netty之上編寫客戶端和服務器。

如果您喜歡自上而下的學習方法,則可能要從第2章,體系結構概述開始,然後回到此處。

3.3.1 入門準備

運行本章中的示例的最低要求只有兩個; Netty和JDK 1.6或更高版本的最新版本。 Netty的最新版本可在項目下載頁面中找到。 要下載正確版本的JDK,請訪問您首選的JDK供應商的網站。

在閱讀時,您可能對本章介紹的類有更多疑問。 如果您想進一步瞭解它們,請參考API參考。 爲了方便起見,本文檔中的所有類名都鏈接到在線API參考。 另外,請不要猶豫,與Netty項目社區聯繫,並讓我們知道是否有任何不正確的信息,語法或拼寫錯誤,以及您是否有任何好的想法來幫助改進文檔。

JDK 1.5 (1.6 for Netty 4+)

3.3.2 編寫Discord服務器

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

要實現DISCARD協議,您唯一需要做的就是忽略所有接收到的數據。 讓我們直接從處理程序實現開始,該實現處理Netty生成的I / O事件。

3.3.2.1 pom.xml 中添加依賴

pom.xml 添加如下依賴:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
            <version>4.1.47.Final</version>
            <scope>compile</scope>
        </dependency>

完整pom.xml 如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xingyun</groupId>
    <artifactId>netty-discard-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 設置當前項目源碼使用字符編碼爲UTF-8 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 設置當前項目所需要的JDK版本 Open JDK下載地址:https://jdk.java.net/ -->
        <java.version>1.8</java.version>
        <!-- 設置當前項目編譯所需要的JDK版本 Open JDK下載地址:https://jdk.java.net/ -->
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <!-- 設置maven編譯插件版本,可通過下面網址查看最新的版本-->
        <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->
        <maven.compiler.plugin.version>3.5.1</maven.compiler.plugin.version>
        <!-- 項目所使用第三方依賴jar包的版本,建議以後都使用這種方式,方便今後維護和升級 -->
        <lombok.version>1.18.10</lombok.version>
        <spring.boot.version>2.1.6.RELEASE</spring.boot.version>
        <netty.version>4.1.47.Final</netty.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>${spring.boot.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
            <version>${netty.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--該插件限定Maven打包時所使用的版本,避免出現版本不匹配問題-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.3.2.2 創建 DiscardServerHandler

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

/**
 * 1.DiscardServerHandler擴展ChannelInboundHandlerAdapter,它是ChannelInboundHandler的實現。
 * ChannelInboundHandler提供了可以覆蓋的各種事件處理程序方法。 目前,僅擴展ChannelInboundHandlerAdapter即可,而不是自己實現處理程序接口。
 * 繼承關係如下所示: DiscardServerHandler extends ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler
 * @author qing-feng.zhao
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {//1
    /**
     * 2.我們在這裏重寫channelRead()事件處理程序方法。
     * 每當從客戶端接收到新數據時,就會使用接收到的消息來調用此方法。 在此示例中,接收到的消息的類型爲ByteBuf。
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {//2
        // Discard the received data silently.
        // 3. 爲了實現DISCARD協議,處理程序必須忽略收到的消息。
        //  ByteBuf是一個引用計數的對象,必須通過release()方法顯式釋放它。
        // 請記住,釋放任何傳遞給處理程序的引用計數對象是處理程序的責任。 通常,channelRead()處理程序方法的實現方式如下:
        //@Override
        //public void channelRead(ChannelHandlerContext ctx, Object msg) {
        //    try {
        //        // Do something with msg
        //    } finally {
        //        ReferenceCountUtil.release(msg);
        //    }
        //}
        ((ByteBuf) msg).release(); // (3)
    }

    /**
     * 4. 當Netty因I/O錯誤而引發異常時,或者由於處理事件時引發異常而由處理程序實現引發異常時,將使用Throwable調用exceptionCaught()事件處理程序方法。
     *    在大多數情況下,應該記錄捕獲到的異常,並在此處關閉其關聯的通道,儘管此方法的實現可能會有所不同,具體取決於您要處理特殊情況時要採取的措施。
     *    例如,您可能想在關閉連接之前發送帶有錯誤代碼的響應消息。
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

3.3.2.3 編寫main方法

到目前爲止,一切都很好。 我們已經實現了DISCARD服務器的前半部分。 現在剩下的是編寫main()方法,該方法使用DiscardServerHandler啓動服務器。

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;

/**
 * @author qing-feng.zhao
 */
public class DiscardServer {
    /**
     * 端口
     */
    private int port;

    /**
     * 構造方法
     * @param port
     */
    public DiscardServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        //默認端口8080
        int port = 8080;
        //如果傳入了參數
        if (args.length > 0) {
            //使用傳入的參數端口
            port = Integer.parseInt(args[0]);
        }
        //啓動服務器
        new DiscardServer(port).run();
    }

    /**
     * @throws Exception
     */
    public void run() throws Exception {
        // 1. NioEventLoopGroup是處理I/O操作的多線程事件循環。
        // Netty爲不同類型的傳輸提供了各種EventLoopGroup實現。
        // 在此示例中,我們正在實現服務器端應用程序,因此將使用兩個NioEventLoopGroup。

        //第一個通常稱爲“boss”,接受傳入的連接
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        //第二個通常稱爲“worker”,一旦boss接受連接並將註冊的連接註冊給worker,便處理已接受連接的流量。
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        //使用多少個線程以及如何將它們映射到創建的通道取決於EventLoopGroup實現,甚至可以通過構造函數進行配置。
        try {

            //ServerBootstrap是設置服務器的幫助程序類。您可以直接使用Channel設置服務器。
            // 但是,請注意,這是一個繁瑣的過程,在大多數情況下您無需這樣做。
            ServerBootstrap b = new ServerBootstrap(); // (2)

            b.group(bossGroup, workerGroup)
                    //在這裏,我們指定使用NioServerSocketChannel類,該類用於實例化新的Channel來接受傳入的連接。
                    .channel(NioServerSocketChannel.class) // (3)
                    // 此處指定的處理程序將始終由新接受的Channel評估。 ChannelInitializer是一個特殊的處理程序,旨在幫助用戶配置新的Channel。
                    // 您很可能希望通過添加一些處理程序(例如DiscardServerHandler)來實現新的Channel的ChannelPipeline,以實現您的網絡應用程序。
                    //隨着應用程序變得複雜,您可能會向管道添加更多處理程序,並最終將此匿名類提取到頂級類中。
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })
                    //您還可以設置特定於Channel實現的參數。我們正在編寫一個TCP / IP服務器,因此我們可以設置套接字選項,例如tcpNoDelay和keepAlive。
                    // 請參考ChannelOption的apidocs和特定的ChannelConfig實現,以獲取有關受支持的ChannelOptions的概述。
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                    //您是否注意到option()和childOption()
                    //   option()用於接受傳入連接的NioServerSocketChannel。
                    //   childOption()用於父級ServerChannel接受的通道,在這種情況下爲NioServerSocketChannel。
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // 剩下的就是綁定到端口並啓動服務器。
            // 在這裏,我們綁定到計算機中所有NIC(網絡接口卡)的端口8080。
            // 現在,您可以根據需要多次調用bind()方法(使用不同的綁定地址)。
            // 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();
        }
        //恭喜你!您剛剛在Netty上完成了第一臺服務器。
    }
}

使用方法:

  • 運行DiscardServer.main() 方法

  • 另外新開一個窗口輸入連接命令:

telnet 127.0.0.1 8080

演示如下所示:
在這裏插入圖片描述
源碼放這裏了,需要的拿走。

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