Netty學習筆記(10)——Netty應用示例(1)

 

本來想先了解Netty組件,然後再學習組件應用的,然後越學越感覺怪異,總感覺少了啥,組件學起來不知道咋用的,想想還是先從Netty應用開始學算了。

自己的技術學習方法:先學習技術的應用,在應用中逐步拋出問題,比如說這個功能是怎麼實現的,帶着問題去接觸底層原理,然後解決問題。

1. 最基礎的Netty應用實現——實現請求與響應

1. 首先是環境配置(jdk)要保證沒問題,其次,要引入Netty的jar,使用netty-5.0版本的jar。

2. 在使用Netty開始開發之前,先想一想使用jdk中的NIO原生類庫開發服務端時所需要的主要步驟:

  1. 首先,創建ServerSocketChannel,設置爲非阻塞模式。
  2. 綁定監聽端口,設置TCP連接參數。
  3. 創建一個獨立IO線程,用於輪詢多路複用器Selector。
  4. 創建Selector,將創建的ServerSocketChannel註冊到該Selector中,並且監聽ServerSocketChannel上的SelectionKey.OP_ACCEPT事件。
  5. 啓動獨立IO線程,在循環體中執行Selector.select()方法,獲取到就緒的Channel。
  6. 每當獲取到Channel時,就需要判斷Channel的狀態,
    1. 如果是OP_ACCEPT,那麼就說明是新的客戶端接入,調用ServerSocketChannel的accept()方法,創建新的連接,即SocketChannel對象。創建SocketChannel對象後,可以設置該TCP連接參數,並且要設置爲非阻塞模式,設置完畢後,將該SocketChannel註冊到Selector中,並且監聽OP_READ事件。
    2. 如果是OP_READ,那麼就說明SocketChannel中有已經就緒的數據可以進行讀取,此時就需要構造ByteBuffer對象進行讀取。
    3. 如果是OP_WRITE,那麼說明SocketChannel還有數據沒有發送,需要繼續進行發送。

3. 僅僅是一個簡單的數據收發,都已經如此複雜,而Netty真的就簡單很多了。以一個服務端時間信息查詢爲例,客戶端向服務端發送一條字符串信息,服務端返回當前時間數據作爲響應,代碼如下

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @ClassName TimeServer
 * @Description: Netty實現的時間服務器服務端示例
 * @Author 
 * @Date 2019/11/19 14:41
 * @Modified By:
 * @Version V1.0
 */
public class TimeServer {

    public static void main(String[] args) {
        try {
            new TimeServer().bind(8080);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //綁定端口
    public void bind(int port) throws InterruptedException {
        //想想Netty的線程模型,我們需要首先創建Reactor模式的線程組,有兩個線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();//主線程組,用於接收連接請求,並建立連接
        EventLoopGroup workGroup = new NioEventLoopGroup();//工作線程組,該線程組通過SocketChannel進行IO操作
        try {
            //Netty服務端啓動類配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();//創建服務端啓動類,該類是輔助服務端啓動的類,主要目的是降低開發複雜度,我們只需要配置相關參數即可
            serverBootstrap.group(bossGroup, workGroup);//配置線程池
            //配置服務端的NioServerSocketChannel,既然是作爲服務端,肯定是需要一個ServerSocketChannel來接收客戶端連接請求並建立連接
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置NioServerSocketChannel的參數,或者說NioServerSocketChannel對應的TCP連接的參數,比如該參數爲連接超時參數,爲10000毫秒
            serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            //和上面哪行代碼一樣,配置TCP連接的backlog參數爲1024
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //最後,綁定IO事件的處理類ChildChannelHandler,該類就是Reactor模型中的Handler,用於處理IO事件,不過這裏的Handler是專門處理ACCEPT事件的,也就是建立連接創建SocketChannel
            serverBootstrap.childHandler(new ChildChannelHandler());
            //服務端輔助啓動類配置完成後,就可以綁定監聽端口了,綁定結果會通過ChannelFuture來獲取
            ChannelFuture future1 = serverBootstrap.bind(port);
            //這行代碼表示阻塞當前線程,直到future1中能夠獲取綁定操作結果,綁定完成後服務端就已經開始運行了
            future1.sync();
            //closeFuture表示Channel連接被關閉時(比如客戶端斷開連接),會返回一個ChannelFuture對象
            ChannelFuture future2 = future1.channel().closeFuture();
            //阻塞當前線程,直到future2總可以獲取到關閉連接操作結果
            future2.sync();
        } finally {
            //釋放線程池資源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    //這個類主要用於監聽ServerSocketChannel接收連接請求並建立連接事件,也就是相當於NIO中的ACCEPT事件
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            //建立連接後創建的的SocketChannel,會在該方法中進行處理,添加響應的handler處理器
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    //針對於SocketChannel的處理器,處理SocketChannel中的IO操作
    private class TimeServerHandler extends ChannelHandlerAdapter {

        //讀取數據,並對數據進行處理
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //將讀取到的數據全部取出
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            //編碼解析
            String body = new String(req, "utf-8");
            System.out.println("server received:" + body);//輸出

            //響應數據
            String res = "server current time:" + System.currentTimeMillis();
            ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
            ctx.write(data);//注意,這裏的寫操作不是立即發送的,而是會先保存在一個緩衝區中

        }

        //該方法表示讀取數據操作完畢,讀取完畢後進行的操作
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();//調用該方法才能將緩衝區中所有的數據發送給客戶端
        }

        //數據讀取過程中發生異常情況,要進行的後續處理都在該方法中
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }
}
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.Buffer;

/**
 * @ClassName TimeClient
 * @Description: 客戶端
 * @Author 
 * @Date 2019/11/19 16:39
 * @Modified By:
 * @Version V1.0
 */
public class TimeClient {

    public void connect(int port, String host) {
        //創建客戶端的NIO線程組
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();//創建客戶端輔助啓動類
        try {
            bootstrap.group(workGroup);//綁定NIO線程組
            bootstrap.channel(NioSocketChannel.class);//初始創建的Channel類型
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeChannelHandler());
                }
            });//設置Handler
            //bootstrap.bind("8081");
            ChannelFuture future = bootstrap.connect(host, port);//與服務端建立連接
            future.sync();//同步阻塞等待連接建立成功
            ChannelFuture f = future.channel().closeFuture();//等待連接被關閉斷開
            f.sync();//同步阻塞等待連接關閉完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();//清理資源
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(8080, "localhost");
    }

    private class TimeChannelHandler extends ChannelHandlerAdapter{

        //該方法會在建立連接後執行,表示觸發了連接建立事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            String message = "query server time";
            ByteBuf req = Unpooled.copiedBuffer(message.getBytes());
            ctx.writeAndFlush(req);//向服務端發送數據
        }

        //讀取服務端發送的數據,如果服務端發送了數據,Channel觸發了可讀事件,那麼該方法就會執行,讀取數據
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] buffer = new byte[buf.readableBytes()];
            buf.readBytes(buffer);
            String message = new String(buffer, "utf-8");
            System.out.println(message);//讀取服務端發送過來的數據,並編碼輸出
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}

4. Netty開發主要步驟:可以參考Netty的線程模型,幫助理解

  1. 服務端:
  • 首先創建兩個處理線程組,也就是Reactor模型中的主從多線程模型。
  • 其次,創建輔助啓動類ServerBootstrap,該類的主要作用就是輔助創建並啓動Netty服務端的類,通過該類我們可以設置一系列的配置並啓動服務端,降低開發複雜度。
  • 配置綁定線程組,主線程組用來接收並創建新的連接,工作線程組主要進行IO操作。
  • 設置初始創建的channel類型爲NioServerSocketChannel,該類對應着NIO中的ServerSocketChannel,用於接收客戶端的連接請求,並創建連接(SocketChannel)。
  • 設置NioServerSocketChannel的TCP連接參數,比如CONNECT_TIMEOUT_MILLIS連接超時判斷等。
  • 設置對於與客戶端建立連接後創建的NIOSocketChannel對象(所以是childHandler方法,因爲是NioServerSocketChannel“創建”的),綁定自定義的Handler處理鏈,Netty中對於SocketChannel上的數據IO操作採用了類似於Servlet中的Filter機制,使用了責任鏈設計模式的變形。
  • 綁定監聽的端口,啓動服務端。
  • 如果連接被關閉,那麼就清理資源,退出

    2.客戶端:

  • 首先,創建一個工作線程組。
  • 其次,創建輔助啓動類Bootstrap。
  • 指定初始建立連接創建的Channel類型爲NioSocketChannel
  • 調用handler方法綁定NioSocketChannel的處理鏈,因爲是爲NioSocketChannel綁定,所以是handler()方法,而不是childHandler()方法。
  • 調用connect()方法與服務端建立連接。
  • 如果連接被關閉,那麼就清理資源,退出

2. 使用Netty解決半包讀取(粘包與拆包)的問題

1. TCP中的粘包與拆包問題

    TCP在網絡模型中,屬於傳輸層協議,即其只負責數據的傳輸,會將要傳輸的數據以流的形式在網絡間進行傳輸,就像一條河流一樣是連續的,對於TCP來說,並不瞭解來自應用層的數據含義,再加上TCP的窗口滑動機制,TCP可能會把一條完整的數據流在讀取時讀到部分數據,也有可能在讀取時一次性讀取到多段數據流(比較難懂的話可以去簡單瞭解一下NIO中設計的零拷貝實現原理,設計到內核緩衝區和應用緩衝區,以及TCP的部分知識)。

    形象一點來說,水龍頭就相當於建立的網絡連接,在系統內核底層會有一個緩衝區,也就是相當於有一個“缸”,這個缸直接放在水龍頭下面的,水龍頭打開後(建立連接),水流出來(接收數據)會直接先到缸內暫存(內核緩衝區接收),然後是用戶空間,也就是應用程序會用一個“盆”(用戶空間的緩衝區)去這個缸裏取水(內核緩衝區數據複製到用戶緩衝區),然後在代碼中對這部分數據進行處理(編解碼等)。但是問題在於,數據發送方發送的數據通常都會是一個一個整體的進行發送,對應着數據接收方也要保證接收一個一個完整的整體,這樣才能保證正確的數據編解碼,但是對於TCP協議來說,由於處於傳輸層,無法理解這些數據在上層應用層中的含義,無法進行分割處理,只負責數據的傳輸,在數據接收方,你能保證每次拿“盆”取出的數據是發送方發送的單條完整消息數據嗎?

    服務端接收讀取客戶端發送的數據時,有可能會發生以下幾種情況:

(1)服務端接收並讀取了客戶端發送的一條完整消息數據。比如客戶端發送了一條“hello”的字符串數據,那麼服務端也接受到了一條“hello”的消息數據。

(2)客戶端發送了兩次消息數據,兩條消息數據都是“hello”,但是服務端只接收到了一條消息,消息內容爲”hellohello”。(粘包)

(3)客戶端發送了兩次消息數據,兩條消息數據都是“hello”,服務端也接受到了兩條消息,但是第一次接受到的消息數據是“helloh”,第二次接收到了“ello”。(拆包)

    如果是發送的不包含漢字字符的字符串,那麼半包讀取還算能夠正常進行,但是一旦包含漢字字符,或者是某類文件的字節碼數據,發生半包讀取後,對數據進行編解碼肯定是會發生異常的。

    粘包拆包問題的演示代碼:正常情況下(或者說我們希望不發生拆包粘包的情況下),客戶端發送100條信息,請求服務端時間;服務端接收100條信息,每接收一條就響應一次時間信息,但因爲拆包和粘包,結果顯然不是這樣。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @ClassName TimeServer
 * @Description: TCP粘包拆包引起問題的演示
 * @Author 
 * @Date 2019/11/20 13:59
 * @Modified By:
 * @Version V1.0
 */
public class TimeServer {

    public static void main(String[] args) {
        try {
            new netty_demo.demo1.TimeServer().bind(8080);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //綁定端口
    public void bind(int port) throws InterruptedException {
        //想想Netty的線程模型,我們需要首先創建Reactor模式的線程組,有兩個線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();//主線程組,用於接收連接請求,並建立連接
        EventLoopGroup workGroup = new NioEventLoopGroup();//工作線程組,該線程組通過SocketChannel進行IO操作
        try {
            //Netty服務端啓動類配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();//創建服務端啓動類,該類是輔助服務端啓動的類,主要目的是降低開發複雜度,我們只需要配置相關參數即可
            serverBootstrap.group(bossGroup, workGroup);//配置線程池
            //配置服務端的NioServerSocketChannel,既然是作爲服務端,肯定是需要一個ServerSocketChannel來接收客戶端連接請求並建立連接
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置NioServerSocketChannel的參數,或者說NioServerSocketChannel對應的TCP連接的參數,比如該參數爲連接超時參數,爲10000毫秒
            serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            //和上面哪行代碼一樣,配置TCP連接的backlog參數爲1024
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //最後,綁定IO事件的處理類ChildChannelHandler,該類就是Reactor模型中的Handler,用於處理IO事件,不過這裏的Handler是專門處理ACCEPT事件的,也就是建立連接創建SocketChannel
            serverBootstrap.childHandler(new ChildChannelHandler());
            //服務端輔助啓動類配置完成後,就可以綁定監聽端口了,綁定結果會通過ChannelFuture來獲取
            ChannelFuture future1 = serverBootstrap.bind(port);
            //這行代碼表示阻塞當前線程,直到future1中能夠獲取綁定操作結果
            future1.sync();
            //closeFuture表示關閉連接,該方法會返回一個ChannelFuture,關閉連接操作結果會存儲在該對象中
            ChannelFuture future2 = future1.channel().closeFuture();
            //阻塞當前線程,直到future2總可以獲取到關閉連接操作結果
            future2.sync();
        } finally {
            //釋放線程池資源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    //這個類主要用於監聽ServerSocketChannel接收連接請求並建立連接事件,也就是相當於NIO中的ACCEPT事件
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            //建立連接後創建的的SocketChannel,會在該方法中進行處理,添加響應的handler處理器
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    //針對於SocketChannel的處理器,處理SocketChannel中的IO操作
    private class TimeServerHandler extends ChannelHandlerAdapter {

        private int count = 0;
        //讀取數據,並對數據進行處理
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //將讀取到的數據全部取出
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            //編碼解析
            String body = new String(req, "utf-8");
            System.out.println("server received:" + body + ";count="+ ++count);//輸出

            //響應數據
            if ("query server time".equals(body)) {
                String res = "server current time:" + System.currentTimeMillis();
                ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
                ctx.writeAndFlush(data);//注意,這裏的寫操作不是立即發送的,而是會先保存在一個緩衝區中
            } else {
                String res = "bad req:" + System.currentTimeMillis();
                ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
                ctx.writeAndFlush(data);//注意,這裏的寫操作不是立即發送的,而是會先保存在一個緩衝區中
            }


        }


        //數據讀取過程中發生異常情況,要進行的後續處理都在該方法中
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }
}
public class TimeClient {
    public void connect(int port, String host) {
        //創建客戶端的NIO線程組
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();//創建客戶輔助啓動類
        try {
            bootstrap.group(workGroup);//綁定NIO線程組
            bootstrap.channel(NioSocketChannel.class);//初始創建的Channel類型
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeChannelHandler());
                }
            });//設置Handler
            //bootstrap.bind("8081");
            ChannelFuture future = bootstrap.connect(host, port);//與服務端建立連接
            future.sync();//同步阻塞等待連接建立成功
            ChannelFuture f = future.channel().closeFuture();//等待連接關閉斷開
            f.sync();//同步阻塞等待連接關閉完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();//清理資源
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(8080, "localhost");
    }

    private class TimeChannelHandler extends ChannelHandlerAdapter {

        private int count=0;
        //該方法會在建立連接後執行,表示觸發了連接建立事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for(int i=1;i<=100;i++){
                String message = "query server time";
                ByteBuf req = Unpooled.copiedBuffer(message.getBytes());
                ctx.writeAndFlush(req);//向服務端發送數據
            }
        }

        //讀取服務端發送的數據,如果服務端發送了數據,Channel觸發了可讀事件,那麼該方法就會執行,讀取數據
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] buffer = new byte[buf.readableBytes()];
            buf.readBytes(buffer);
            String message = new String(buffer, "utf-8");
            System.out.println(message+";count="+ ++count);//讀取服務端發送過來的數據,並編碼輸出
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}

2. TCP中的粘包與拆包問題的解決方式

粘包與拆包問題的解決方式有以下幾種:

(1)消息定長,即消息數據長度固定。

(2)以特定字符作爲分隔符或者說結束標誌,比如換行符。

(3)將消息分爲消息頭和消息體,消息頭中包含對消息體的描述,比如對於消息體的數據長度等信息。(比如HTTP協議、TCP協議)

(4)定義應用層協議。

    1. 指定消息結束標誌字符解決文本消息數據的半包讀取

    對於文本消息數據,也就是字符串數據來說,比較容易解決,可以使用上述的第二種方法,指定一個消息結束標誌字符,比如常見的換行符\n,Netty中正好提供了以換行符作爲消息結束標誌的Handler類,通過io.netty.handler.codec.LineBasedFrameDecoder類來解決拆包粘板問題。代碼如下所示

public class TimeClient {

    public void connect(int port, String host) {
        //創建客戶端的NIO線程組
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();//創建客戶輔助啓動類
        try {
            bootstrap.group(workGroup);//綁定NIO線程組
            bootstrap.channel(NioSocketChannel.class);//初始創建的Channel類型
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    ch.pipeline().addLast(new StringDecoder());
                    ch.pipeline().addLast(new TimeChannelHandler());
                }
            });//設置Handler
            //bootstrap.bind("8081");
            ChannelFuture future = bootstrap.connect(host, port);//與服務端建立連接
            future.sync();//同步阻塞等待連接建立成功
            ChannelFuture f = future.channel().closeFuture();//等待連接關閉斷開
            f.sync();//同步阻塞等待連接關閉完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();//清理資源
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(8080, "localhost");
    }

    private class TimeChannelHandler extends ChannelHandlerAdapter{
        private int count=0;

        //該方法會在建立連接後執行,表示觸發了連接建立事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for(int i=1;i<=100;i++){
                String message = "query server time"+i+System.getProperty("line.separator");
                ByteBuf req = Unpooled.copiedBuffer(message.getBytes());
                ctx.writeAndFlush(req);//向服務端發送數據
            }

        }

        //讀取服務端發送的數據,如果服務端發送了數據,Channel觸發了可讀事件,那麼該方法就會執行,讀取數據
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String message = (String) msg;
            System.out.println(message+";count="+ ++count);//讀取服務端發送過來的數據,
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}
/**
 * Netty實現的時間服務器服務端粘包拆包問題修正版
 */
public class TimeServer {

    public static void main(String[] args) {
        try {
            new TimeServer().bind(8080);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //綁定端口
    public void bind(int port) throws InterruptedException {
        //想想Netty的線程模型,我們需要首先創建Reactor模式的線程組,有兩個線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();//主線程組,用於接收連接請求,並建立連接
        EventLoopGroup workGroup = new NioEventLoopGroup();//工作線程組,該線程組通過SocketChannel進行IO操作
        try {
            //Netty服務端啓動類配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();//創建服務端啓動類,該類是輔助服務端啓動的類,主要目的是降低開發複雜度,我們只需要配置相關參數即可
            serverBootstrap.group(bossGroup, workGroup);//配置線程池
            //配置服務端的NioServerSocketChannel,既然是作爲服務端,肯定是需要一個ServerSocketChannel來接收客戶端連接請求並建立連接
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置NioServerSocketChannel的參數,或者說NioServerSocketChannel對應的TCP連接的參數,比如該參數爲連接超時參數,爲10000毫秒
            serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            //和上面哪行代碼一樣,配置TCP連接的backlog參數爲1024
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //最後,綁定IO事件的處理類ChildChannelHandler,該類就是Reactor模型中的Handler,用於處理IO事件,不過這裏的Handler是專門處理ACCEPT事件的,也就是建立連接創建SocketChannel
            serverBootstrap.childHandler(new ChildChannelHandler());
            //服務端輔助啓動類配置完成後,就可以綁定監聽端口了,綁定結果會通過ChannelFuture來獲取
            ChannelFuture future1 = serverBootstrap.bind(port);
            //這行代碼表示阻塞當前線程,直到future1中能夠獲取綁定操作結果
            future1.sync();
            //closeFuture表示關閉連接,該方法會返回一個ChannelFuture,關閉連接操作結果會存儲在該對象中
            ChannelFuture future2 = future1.channel().closeFuture();
            //阻塞當前線程,直到future2總可以獲取到關閉連接操作結果
            future2.sync();
        } finally {
            //釋放線程池資源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    //這個類主要用於監聽ServerSocketChannel接收連接請求並建立連接事件,也就是相當於NIO中的ACCEPT事件
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
            ch.pipeline().addLast(new StringDecoder());

            //建立連接後創建的的SocketChannel,會在該方法中進行處理,添加響應的handler處理器
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    //針對於SocketChannel的處理器,處理SocketChannel中的IO操作
    private class TimeServerHandler extends ChannelHandlerAdapter {

        //讀取數據,並對數據進行處理
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String body = (String) msg;
            System.out.println("server received:" + body);//輸出

            //響應數據
            String res = "server current time:" + System.currentTimeMillis()+System.getProperty("line.separator");
            ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
            ctx.writeAndFlush(data);

        }

        //數據讀取過程中發生異常情況,要進行的後續處理都在該方法中
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }
}

    該類LineBasedFrameDecoder是已經默認換行符爲消息結束標誌,無法修改;Netty還提供了另外一個類,可以自由指定結束標誌字符,這個類就是DelimiterBasedFrameDecoder,可以通過該類指定字符$爲消息結束標誌字符。代碼如下

public class TimeClient {

    public void connect(int port, String host) {

        //創建客戶端的NIO線程組
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();//創建客戶輔助啓動類
        try {
            bootstrap.group(workGroup);//綁定NIO線程組
            bootstrap.channel(NioSocketChannel.class);//初始創建的Channel類型
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ByteBuf buf = Unpooled.copiedBuffer("$".getBytes());
                    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
                    ch.pipeline().addLast(new StringDecoder());
                    ch.pipeline().addLast(new TimeChannelHandler());
                }
            });//設置Handler
            //bootstrap.bind("8081");
            ChannelFuture future = bootstrap.connect(host, port);//與服務端建立連接
            future.sync();//同步阻塞等待連接建立成功
            ChannelFuture f = future.channel().closeFuture();//等待連接關閉斷開
            f.sync();//同步阻塞等待連接關閉完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();//清理資源
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(8080, "localhost");
    }

    private class TimeChannelHandler extends ChannelHandlerAdapter{
        private int count=0;

        //該方法會在建立連接後執行,表示觸發了連接建立事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for(int i=1;i<=100;i++){
                String message = "query server time"+i+"$";
                ByteBuf req = Unpooled.copiedBuffer(message.getBytes());
                ctx.writeAndFlush(req);//向服務端發送數據
            }

        }

        //讀取服務端發送的數據,如果服務端發送了數據,Channel觸發了可讀事件,那麼該方法就會執行,讀取數據
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String message = (String) msg;
            System.out.println(message+";count="+ ++count);//讀取服務端發送過來的數據,
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}
public class TimeServer {

    public static void main(String[] args) {
        try {
            new TimeServer().bind(8080);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //綁定端口
    public void bind(int port) throws InterruptedException {
        //想想Netty的線程模型,我們需要首先創建Reactor模式的線程組,有兩個線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();//主線程組,用於接收連接請求,並建立連接
        EventLoopGroup workGroup = new NioEventLoopGroup();//工作線程組,該線程組通過SocketChannel進行IO操作
        try {
            //Netty服務端啓動類配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();//創建服務端啓動類,該類是輔助服務端啓動的類,主要目的是降低開發複雜度,我們只需要配置相關參數即可
            serverBootstrap.group(bossGroup, workGroup);//配置線程池
            //配置服務端的NioServerSocketChannel,既然是作爲服務端,肯定是需要一個ServerSocketChannel來接收客戶端連接請求並建立連接
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置NioServerSocketChannel的參數,或者說NioServerSocketChannel對應的TCP連接的參數,比如該參數爲連接超時參數,爲10000毫秒
            serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            //和上面哪行代碼一樣,配置TCP連接的backlog參數爲1024
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //最後,綁定IO事件的處理類ChildChannelHandler,該類就是Reactor模型中的Handler,用於處理IO事件,不過這裏的Handler是專門處理ACCEPT事件的,也就是建立連接創建SocketChannel
            serverBootstrap.childHandler(new ChildChannelHandler());
            //服務端輔助啓動類配置完成後,就可以綁定監聽端口了,綁定結果會通過ChannelFuture來獲取
            ChannelFuture future1 = serverBootstrap.bind(port);
            //這行代碼表示阻塞當前線程,直到future1中能夠獲取綁定操作結果
            future1.sync();
            //closeFuture表示關閉連接,該方法會返回一個ChannelFuture,關閉連接操作結果會存儲在該對象中
            ChannelFuture future2 = future1.channel().closeFuture();
            //阻塞當前線程,直到future2總可以獲取到關閉連接操作結果
            future2.sync();
        } finally {
            //釋放線程池資源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    //這個類主要用於監聽ServerSocketChannel接收連接請求並建立連接事件,也就是相當於NIO中的ACCEPT事件
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ByteBuf buf = Unpooled.copiedBuffer("$".getBytes());
            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
            ch.pipeline().addLast(new StringDecoder());

            //建立連接後創建的的SocketChannel,會在該方法中進行處理,添加響應的handler處理器
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    //針對於SocketChannel的處理器,處理SocketChannel中的IO操作
    private class TimeServerHandler extends ChannelHandlerAdapter {

        //讀取數據,並對數據進行處理
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String body = (String) msg;
            System.out.println("server received:" + body);//輸出

            //響應數據
            String res = "server current time:" + System.currentTimeMillis()+"$";
            ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
            ctx.writeAndFlush(data);

        }

        //數據讀取過程中發生異常情況,要進行的後續處理都在該方法中
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }
}

    2. 消息定長解決消息數據的半包讀取問題

    實際上,如果你可以確定每次發送的最大消息長度的話,那麼可以採用消息定長的方式來解決,當然該方式必須保證消息定長的長度不會很大,可以使用Netty提供的FixedLengthFrameDecoder類實現消息定長。代碼如下

public class TimeClient {

    public void connect(int port, String host) {

        //創建客戶端的NIO線程組
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();//創建客戶輔助啓動類
        try {
            bootstrap.group(workGroup);//綁定NIO線程組
            bootstrap.channel(NioSocketChannel.class);//初始創建的Channel類型
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {

                    ch.pipeline().addLast(new FixedLengthFrameDecoder(1024));
                    ch.pipeline().addLast(new StringDecoder());
                    ch.pipeline().addLast(new TimeChannelHandler());
                }
            });//設置Handler
            //bootstrap.bind("8081");
            ChannelFuture future = bootstrap.connect(host, port);//與服務端建立連接
            future.sync();//同步阻塞等待連接建立成功
            ChannelFuture f = future.channel().closeFuture();//等待連接關閉斷開
            f.sync();//同步阻塞等待連接關閉完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            workGroup.shutdownGracefully();//清理資源
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(8080, "localhost");
    }

    private class TimeChannelHandler extends ChannelHandlerAdapter{
        private int count=0;

        //該方法會在建立連接後執行,表示觸發了連接建立事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for(int i=1;i<=100;i++){
                String message = "query server time"+i+"$";
                ByteBuf req = Unpooled.copiedBuffer(message.getBytes());
                ctx.writeAndFlush(req);//向服務端發送數據
            }

        }

        //讀取服務端發送的數據,如果服務端發送了數據,Channel觸發了可讀事件,那麼該方法就會執行,讀取數據
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String message = (String) msg;
            System.out.println(message+";count="+ ++count);//讀取服務端發送過來的數據,
        }


        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}
public class TimeServer {

    public static void main(String[] args) {
        try {
            new TimeServer().bind(8080);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //綁定端口
    public void bind(int port) throws InterruptedException {
        //想想Netty的線程模型,我們需要首先創建Reactor模式的線程組,有兩個線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();//主線程組,用於接收連接請求,並建立連接
        EventLoopGroup workGroup = new NioEventLoopGroup();//工作線程組,該線程組通過SocketChannel進行IO操作
        try {
            //Netty服務端啓動類配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();//創建服務端啓動類,該類是輔助服務端啓動的類,主要目的是降低開發複雜度,我們只需要配置相關參數即可
            serverBootstrap.group(bossGroup, workGroup);//配置線程池
            //配置服務端的NioServerSocketChannel,既然是作爲服務端,肯定是需要一個ServerSocketChannel來接收客戶端連接請求並建立連接
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置NioServerSocketChannel的參數,或者說NioServerSocketChannel對應的TCP連接的參數,比如該參數爲連接超時參數,爲10000毫秒
            serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            //和上面哪行代碼一樣,配置TCP連接的backlog參數爲1024
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //最後,綁定IO事件的處理類ChildChannelHandler,該類就是Reactor模型中的Handler,用於處理IO事件,不過這裏的Handler是專門處理ACCEPT事件的,也就是建立連接創建SocketChannel
            serverBootstrap.childHandler(new ChildChannelHandler());
            //服務端輔助啓動類配置完成後,就可以綁定監聽端口了,綁定結果會通過ChannelFuture來獲取
            ChannelFuture future1 = serverBootstrap.bind(port);
            //這行代碼表示阻塞當前線程,直到future1中能夠獲取綁定操作結果
            future1.sync();
            //closeFuture表示關閉連接,該方法會返回一個ChannelFuture,關閉連接操作結果會存儲在該對象中
            ChannelFuture future2 = future1.channel().closeFuture();
            //阻塞當前線程,直到future2總可以獲取到關閉連接操作結果
            future2.sync();
        } finally {
            //釋放線程池資源
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    //這個類主要用於監聽ServerSocketChannel接收連接請求並建立連接事件,也就是相當於NIO中的ACCEPT事件
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {

            ch.pipeline().addLast(new FixedLengthFrameDecoder(1024));
            ch.pipeline().addLast(new StringDecoder());

            //建立連接後創建的的SocketChannel,會在該方法中進行處理,添加響應的handler處理器
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    //針對於SocketChannel的處理器,處理SocketChannel中的IO操作
    private class TimeServerHandler extends ChannelHandlerAdapter {

        //讀取數據,並對數據進行處理
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            String body = (String) msg;
            System.out.println("server received:" + body);//輸出

            //響應數據
            String res = "server current time:" + System.currentTimeMillis()+"$";
            ByteBuf data = Unpooled.copiedBuffer(res.getBytes());
            ctx.writeAndFlush(data);

        }

        //數據讀取過程中發生異常情況,要進行的後續處理都在該方法中
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }

    }
}

    3. 定義應用層協議,規定消息結構

    上面兩種方式基本都適用於比較小的文本消息數據傳輸,但如果針對於複雜的情況,比如既能傳輸文本數據,也能傳輸各種文件,那麼就比較複雜了,因爲TCP協議屬於傳輸層協議,其不會理解傳輸的二進制數據代表着什麼,所以就必須要定義一個上層應用層的協議,來對TCP接收的數據進行解析。

    協議就不細說了,簡單來說就是客戶端服務端雙方的約定,協議規定了客戶端發送數據的格式,也規定了服務端解析數據的方式。常見的HTTP協議就是一個很好的例子,其採用了多種方式解決半包讀取的問題,首先就是指定了消息的結構(比如請求頭、請求行、請求體),並且對於其中的請求頭、請求頭和請求行肯定也需要一個結束符表示,消息子結構之間的間隔會添加一個換行符來表示結束。可以去詳細瞭解一下Http協議的格式。

    Http協議相應的處理Handler肯定不用說,Netty中已經準備好了,只需直接調用就可以實現一個簡單的Http協議的文件服務器。代碼如下

public class HttpFileServer {
    private static final String DEFAULT_URL = "/src/netty_demo/";

    public static void main(String[] args) {
        int port = 8080;
        try {
            new HttpFileServer().run(port, DEFAULT_URL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //服務器啓動方法
    public void run(final int port,final String url) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.group(bossGroup, workGroup);
            bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {

                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast("http-decoder",
                            new HttpRequestDecoder());//添加HTTP請求消息解碼器
                    ch.pipeline().addLast("http-aggregator",
                            new HttpObjectAggregator(65536));//該解碼器用於將多個Http消息合併爲一個完整的HTTP消息,
                    ch.pipeline().addLast("http-encoder",
                            new HttpResponseEncoder());//HTTP響應數據編碼器
                    ch.pipeline().addLast("http-chunked",
                            new ChunkedWriteHandler());//該handler用於解決異步傳輸大碼流(如文件傳輸),但不會佔用太多內存,防止內存溢出
                    ch.pipeline().addLast("fileServerHandler",
                            new HttpFileServerHandler(url));
                }
            });
            ChannelFuture cf = bootstrap.bind(port).sync();
            System.out.println("HTTP文件服務器啓動,地址爲:http://localhost:8080"+url);
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
    //返回HTTP響應,表示該請求錯誤
    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status, Unpooled.copiedBuffer(status.toString()+"\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
    //設置HTTP響應的響應頭
    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(file.getPath()));
    }
    //響應一個重定向消息
    private static void sendRedirect(ChannelHandlerContext ctx, String uri) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
        response.headers().set(HttpHeaderNames.LOCATION, uri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    //返回文件列表的html頁面
    private static void sendFileList(ChannelHandlerContext ctx, File dir) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
        StringBuilder html = new StringBuilder();
        String dirPath = dir.getPath();
        html.append("<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>");
        html.append("<title>"+dirPath+"目錄:"+"</title>");
        html.append("</head>");
        html.append("<body>\n");
        html.append("<h3>").append(dirPath).append("目錄:").append("</h3>\n");
        html.append("<ul>");
        html.append("<li>鏈接:<a href=\"../\">..</a></li>\n");
        for (File f:dir.listFiles()) {
            if (f.isHidden() || !f.canRead()) {
                continue;
            }
            String name = f.getName();
            html.append("<li>鏈接:<a href=\""+name+"\">"+name+"</a></li>\n");

        }
        html.append("</ul>");
        html.append("</body>\n");
        html.append("</html>");
        ByteBuf buf = Unpooled.copiedBuffer(html, CharsetUtil.UTF_8);
        response.content().writeBytes(buf);
        buf.release();
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    //HTTP請求入棧後進行的一系列操作,包括進行響應消息
    private class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        private String url;
        public HttpFileServerHandler(String url) {
            this.url = url;
        }

        //解碼uri,並轉爲本地文件目錄
        private String decodeUri(String uri) {
            try {
                uri = URLDecoder.decode(uri, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                try {
                    uri = URLDecoder.decode(uri, "ISO-8859-1");
                } catch (UnsupportedEncodingException ex) {
                    throw new RuntimeException("請求路徑異常");
                }
            }
            if (!uri.startsWith(DEFAULT_URL)) {
                return null;
            }
            if (!uri.startsWith("/")) {
                return null;
            }
            uri = uri.replace('/', File.separatorChar);

            String path = System.getProperty("user.dir")+uri;
            return path;
        }


        //接收到請求消息後進行的處理
        @Override
        protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
            //首先判斷請求路徑正確性
            if(request.decoderResult().isFailure()){
                sendError(ctx, HttpResponseStatus.BAD_REQUEST);
                return;
            }
            if(request.method()!=HttpMethod.GET) {
                sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
                return;
            }
            String uri = request.uri();
            String path = decodeUri(uri);
            if (path == null) {
                sendError(ctx, HttpResponseStatus.FORBIDDEN);
                return;
            }
            File file = new File(path);
            if (file.isHidden() || !file.exists()) {
                sendError(ctx, HttpResponseStatus.NOT_FOUND);
                return;
            }
            //如果請求路徑對應的本地文件是一個文件夾(目錄)那麼就將該目錄下的文件變成一個列表展示在HTML頁面中,
            //並將該頁面作爲響應消息返回給客戶端
            if (file.isDirectory()) {
                if(uri.endsWith("/")) {
                    sendFileList(ctx, file);
                } else {
                    sendRedirect(ctx, uri+"/");
                }
                return;
            }
            if (!file.isFile()) {
                sendError(ctx, HttpResponseStatus.FORBIDDEN);
                return;
            }

            //如果請求路徑對應的目標是文件,那麼就開啓文件傳輸
            RandomAccessFile accessFile = null;
            try {
                accessFile = new RandomAccessFile(file, "r");//以只讀方式打開文件

            }catch (FileNotFoundException e) {
                sendError(ctx, HttpResponseStatus.NOT_FOUND);
                return;
            }
            long fileLength = accessFile.length();

            //創建並設置響應頭和響應行
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            setContentTypeHeader(response, file);
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength+"");
            String connection = request.headers().get(HttpHeaderNames.CONNECTION).toString();
            if(connection.contentEquals(HttpHeaderValues.KEEP_ALIVE)) {
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            }
            ctx.write(response);

            //創建並設置響應體,即爲文件數據
            ChannelFuture sendFileFuture;
            sendFileFuture = ctx.write(new ChunkedFile(accessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
            sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
                @Override
                public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
                    if (total < 0) {
                        System.out.println("文件傳輸中,已發送:"+progress+"byte");
                    }else {
                        System.out.println("文件傳輸中,已發送:"+progress+"/"+total+"byte");
                    }
                }

                @Override
                public void operationComplete(ChannelProgressiveFuture future) throws Exception {
                    System.out.println("文件傳輸完成");
                }
            });
            ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            if(connection.contentEquals(HttpHeaderValues.KEEP_ALIVE)) {
                lastContentFuture.addListener(ChannelFutureListener.CLOSE);
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            if (ctx.channel().isActive()) {
                sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }


}

    如果看完了Http協議服務器代碼,你會覺得似乎nginx中對於反向代理的實現代碼似乎就是這樣的,當然,其中的路徑等參數並不是寫死了,而是通過nginx的配置文件定義,並且定義一系列的負載均衡規則等,實際代碼肯定要複雜的多,但是原理是一樣的。

3. 總結

    在對Netty進行使用之後,相信10個人裏10個人都不會願意再去使用NIO的原生類庫了,Netty相較於NIO原生類庫的最大優勢就在這一點上,開發複雜度低,而且類庫齊全,功能強大。

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