Netty 框架學習 —— 第一個 Netty 應用


概述

在本文,咱們將編寫一個基於 Netty 實現的客戶端和服務端應用程序,相信經過學習該示例,必定能更全面的理解 Netty APIbootstrap

該圖展現的是多個客戶端同時鏈接到一臺服務器。客戶端創建一個鏈接後,會向服務器發送一個或多個消息,反過來,服務器又會將每一個消息回送給客戶端安全


編寫 Echo 服務器

全部 Netty 服務器都須要如下兩部分:服務器

  • 至少一個 CHannelHandler網絡

    該組件實現了服務器對從客戶端接收的數據的處理,即它的業務邏輯異步

  • 引導maven

    配置服務器的啓動代碼,將服務器綁定到它要監聽鏈接請求的端口上ide

1. ChannelHandler 和業務邏輯

ChannelHandler 是一個接口族的父接口,它的實現負責接收並響應事件通知,即要包含數據的處理邏輯。咱們的 Echo 服務器須要響應傳入的消息,因此須要實現 ChannelHandler 接口,用來定義響應入站事件的方法,又由於只須要用到少許的方法,因此繼承 ChannelHandlerAdapter 類就足夠了,它提供了 ChannelHandler 的默認實現oop

咱們感興趣的方法有:學習

  • channelRead()this

    對於每一個傳入的消息都要調用

  • channelReadComplete()

    通知 ChannelHandler 最後一次對 channelRead() 的調用是當前批量讀取的最後一條消息

  • exceptionCaught

    在讀取操做期間,有異常拋出時會調用

Echo 服務器的 ChannelHandler 實現 EchoServerHandler 以下

@ChannelHandler.Sharable // 標識一個 ChannelHandler 能夠被多個 Channel 安全的共享
public class EchoServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server receiver: " + in.toString(CharsetUtil.UTF_8));
        // 將接收到的消息寫給發送者
        ctx.write(in);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        // 將剩餘的消息所有沖刷到遠程結點,並關閉 CHannel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

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

應用程序經過實現或者擴展 ChannelHandler 來掛鉤到事件的生命週期,而且提供自定義的應用程序邏輯。ChannelHandler 有助於保持業務邏輯與網絡處理代碼的分離,簡化了開發過程

2. 引導服務器

編寫完 EchoServerHandler 實現的核心業務邏輯以後,咱們如今探討引導服務器的過程,具體涉及內容以下:

  • 綁定服務器將在其上監聽並接收傳入鏈接請求的接口
  • 配置 Channel,將入站消息交給 EchoServerHandler 實例

EchoServer 類完整代碼以下

public class EchoServer {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + EchoServer.class.getSimpleName() + "");
            return;
        }
        int port = Integer.parseInt(args[0]);
        new EchoServer(port).start();
    }

    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    // 指定所使用的 NIO 傳輸 Channel
                    .channel(NioServerSocketChannel.class)
                    // 使用指定的端口設置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    // 添加一個 EchoServerHandler 到子 Handler 的 ChannelPipeline
                    .childHandler(new ChannelInitializer<>() {

                        @Override
                        protected void initChannel(Channel ch) {
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            // 異步地綁定服務器,調用 sync() 方法阻塞等待直到綁定完成
            ChannelFuture f = b.bind().sync();
            // 獲取 Channel 的 CloseFuture,而且阻塞當前線程直到它完成
            f.channel().closeFuture().sync();
        } finally {
            // 關閉 EventLoopGroup 釋放全部資源
            group.shutdownGracefully().sync();
        }
    }
}

到此爲止,咱們回顧一下服務器實現中的幾個重要步驟:

  • EchoServerHandler 實現業務邏輯
  • main() 方法引導服務器

引導服務器過程的重要步驟以下:

  • 建立一個 ServerBootstrap 的實例以引導和綁定服務器
  • 建立並分配一個 NioEventLoopGroup 實例以進行事件的處理,如接受新鏈接以及讀寫數據
  • 指定服務器綁定的本地的 InetSocketAddress
  • 使用一個 EchoServerHandler 實例初始化每個新的 Channel
  • 調用 ServerBootstrap.bind() 方法以綁定服務器


編寫 Echo 客戶端

Echo 客戶端的做用:

  • 鏈接到服務器
  • 發送一個或多個消息
  • 對於每一個消息,等待並接收從服務器返回的響應
  • 關閉鏈接

和服務器同樣,編寫客戶端所涉及的主要代碼部分也是業務邏輯和引導

1. ChannelHandler 和客戶端邏輯

客戶端也要有一個用來處理數據的 ChannelHandler,這裏選擇 SimpleChannelInboundHandler 類處理全部必需的任務,要求重寫下面的方法:

  • channelActive()

    當與服務器的鏈接創建以後被調用

  • messageReceived()

    當從服務器接收到一條消息時被調用

  • exceptionCaught()

    在處理過程當中引起異常時被調用

@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler{

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 當一個鏈接創建時被調用,發送一條消息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    @Override
    protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) {
        // 記錄已接收消息的轉儲
        System.out.println("Client received: " + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 發生異常時,記錄錯誤並關閉 Channel
        cause.printStackTrace();
        ctx.close();
    }
}

2. 引導客戶端

引導客戶端相似於服務器,不一樣的是,客戶端是使用主機和端口參數來鏈接遠程地址

public class EchoClient {

    private final String host;
    private final int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + EchoClient.class.getSimpleName() + "");
            return;
        }
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        new EchoClient(host, port).start();
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 建立 Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            // 鏈接到遠程節點,阻塞等待直到鏈接完成
            ChannelFuture future = bootstrap.connect().sync();
            // 阻塞,直到 Channel 關閉
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

到此爲止,咱們回顧一下客戶端實現中的幾個重要步驟:

  • 建立一個 Bootstrap 實例
  • 建立並分配一個 NioEventLoopGroup 實例以進行事件的處理,其中事件處理包括建立新的鏈接以及處理入站和出站數據
  • 爲服務器鏈接建立一個 InetSocketAddress 實例
  • 當鏈接創建時,一個 EchoClientHandler 實例會被安裝到該 Channel 的 ChannelPipeline 中
  • 調用 Bootstrap.connect() 方法鏈接遠程節點


運行客戶端和服務端

本文的項目使用 maven 構建,先啓動服務端並準備好接受鏈接。而後啓動客戶端,一旦客戶端創建鏈接,就會發送消息。服務器接收消息,控制檯會打印以下信息:

Server receiver: Netty rocks!

同時將其回送給客戶端,客戶端的控制檯也會打印以下消息,隨後退出:

Client received: Netty rocks!
相關文章
相關標籤/搜索