1. 程式人生 > >Netty 入門示例詳解

Netty 入門示例詳解

本文導讀

開發包獲取

二進位制 jar 包

  • Netty 本身就是人家寫好的一個 Java 的 Jar 包(庫),所以開發的第一步便是要下載它,Maven 方式後面講解。
  • 如下所示,進入 Netty 官網,然後滑鼠懸停在 "Downloads" 上,點選下載對應的版本,本文以最新的 4.1.30.Final 為例。

  • 下載後壓縮包大小為 20.1M,可以使用 "WinRar" 等壓縮工具進行解壓。

  • jar 開發包目錄中提供了各個模組單獨的開發包,同時也提供了對應的原始碼,其中有一個 netty-example-4.1.30.Final.jar 、netty-example-4.1.30.Final-sources.jar 提供了大量的示例,非常適合學習。

  • 以匯入二進位制類庫的方式開發 Netty 時,則只需要匯入如下所示的整合包即可,3.7M 包含了所有的模組,同時也提供了原始碼。

Maven 依賴

  • 如果使用 Maven 進行專案開發管理,則 Netty 也提供了 Maven 依賴。
  • Maven 依賴可以從 Netty 官網下載頁中獲取:https://netty.io/downloads.html,如下所示:

<dependencies>
  ...
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
    <version>X.Y.Z.Q</version>
    <scope>compile</scope>
  </dependency>
  ...
</dependencies>

<artifactId>netty</artifactId>:如果 Netty 是 4.0 以下版本,則 artifactId值寫 netty,如果 Netty 是 4.0 及以上版本,則 寫 netty-all。

<version>X.Y.Z.Q</version>:netty 版本號自己填寫具體版本即可。

  • 這裡同樣可以選擇上面最新版的 4.1.30.Final ,點選進入即可獲取依賴:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.30.Final</version>
</dependency>

Hello World

  • 出於入門階段學習的目的,本文將新建 Java SE 專案,採用匯入二進位制開發包的方式,暫時不使用 Maven 管理。
  • 環境:IDEA 14 + JDK 8 + Netty4.1.30。

  • 專案建好之後,新建 lib 目錄用於存放第三方開發包,注意要新增到類路徑中,同時新建 classes 目錄作為專案統一的編譯輸出目錄。
  • 以一個簡單的例子來對 Netty 網路程式設計有一個初步的瞭解,其中的細節可以以後慢慢消化:先開啟伺服器等待客戶端連線,然後開啟客戶端,同時給伺服器傳送一條訊息,伺服器接收到訊息後,回發一條訊息。

服務端

·TimeServerHandler·

package com.lct.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/**
 * Created by Administrator on 2017/5/16.
 * 用於對網路事件進行讀寫操作
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        /**獲取緩衝區可讀位元組數,並建立陣列*/
        byte[] reg = new byte[buf.readableBytes()];
        /**將緩衝區位元組陣列複製到新建的byte陣列中*/
        buf.readBytes(reg);
        /**獲取請求訊息*/
        String body = new String(reg, "UTF-8");
        System.out.println("The server receive  order : " + body);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        /**給客戶端傳送應答訊息,實際是將訊息放到傳送緩衝陣列中*/
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        /**將傳送緩衝區中的訊息全部寫到SocketChannel中*/
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**當發生異常時,關閉ChannelHandlerContext,釋放和它相關聯的控制代碼等資源*/
        ctx.close();
    }
}

·TimeServer·

package com.lct.server;

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

import java.beans.beancontext.BeanContextChildComponentProxy;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeServer {

    public void bind(int port){
        /**配置服務端的NIO執行緒組,專門用於網路事件處理
         * 一個用於服務端結束客戶端連線
         * 一個用於用於進行SocketChannel讀寫*/
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /**ServerBootstrap是Netty用於啟動NIO服務端的輔助啟動類*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());
            /**繫結埠,同步等待成功*/
            ChannelFuture f = b.bind(port).sync();
            /**等待伺服器監聽埠關閉*/
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            /**優雅退出,釋放執行緒池資源*/
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            arg0.pipeline().addLast(new TimeServerHandler());
        }
    }
    public static void main(String[] args) {
        int port = 8080;
        if (args!=null && args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                /**採用預設值*/
            }
        }
        new TimeServer().bind(port);
    }
}

客戶端

·TimeClientHandler·

package com.lct.client;

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

import java.util.logging.Logger;

/**
 * Created by Administrator on 2017/5/17.
 * 用於對網路事件進行讀寫操作
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
    private  final ByteBuf firstMessage;

    public TimeClientHandler() {
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    /**當客戶端和服務端TCP鏈路建立成功之後,Netty的NIO執行緒會呼叫channelActive方法
     * 同時傳送查詢時間的指令給服務端,呼叫ChannelHandlerContext的writeAndFlush方法
     * 將 請求訊息傳送給服務端*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    /**當服務端返回應答訊息時,channelRead方法被呼叫,從Netty的ByteBuf中讀取並列印應答訊息*/
    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 return Message:"+body);
    }
    /**當發生異常時,列印異常 日誌,釋放客戶端資源*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**釋放資源*/
        logger.warning("Unexpected exception from downstream : "+cause.getMessage());
        ctx.close();
    }
}

·TimeClient·

package com.lct.client;

import io.netty.bootstrap.Bootstrap;
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;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeClient {
    public void connect(int port,String host){
        /**配置客戶端NIO執行緒組*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /**建立客戶端輔助啟動類,並對其配置
             * 與伺服器稍微不同,這裡的Channel設定為NioSocketChannel
             * 然後為其新增Handler,這裡直接使用匿名內部類,實現initChannel方法
             * 作用是當建立NioSocketChannel成功後,在進行初始化時
             * 將它的ChannelHandler設定到ChannelPipeline中,用於處理網路I/O事件*/
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            /**發起非同步連線操作*/
            ChannelFuture f = b.connect(host, port);
            /**等待客戶端鏈路關閉*/
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /**優雅退出,釋放NIO執行緒組*/
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        int port = 8080;
        if (args!=null && args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                /**採用預設值*/
            }
        }
        new TimeClient().connect(port,"127.0.0.1");
    }
}

測試執行

  • 先執行服務端,再執行客戶端:

自學建議

  • 對於和我一樣自學的兄弟姐妹來說,除了百度以外,還可以參考下載包中更多官方示例,如下所示:

············下一篇《傳統 BIO 程式設計