Netty実践(一):簡単な入門

6682 ワード

前言
Nettyは現在世界で最も流行しているNIOフレームワークの一つとして、機能、性能、丈夫性の面で屈指であり、メッセージミドルウェアRocketMQ、分散型通信フレームワークDubboxなどの多くのプロジェクトで検証されている.Nettyの内部実装は複雑ですが、外部に提供されるAPIは非常に簡単で、私たちのネットワーク処理コードとビジネスロジック処理コードを簡単に分離し、ネットワークアプリケーションを迅速に開発します.
JAVA NIO、JAVA SOCKETをまだ知らない場合は、まずブロガーの以前のブログ「Java NIOの世界に入る」、「Java NIOサーバーとクライアントがファイルダウンロードを実現する」、「Java通信実戦:カスタム通信プロトコルを作成してFTPサービスを実現する」を参照してください.
コードインスタンスからNettyを分析する
サーバ側起動コード
public class Main {

    public static void main(String[] args) {

        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // (2)
        int port = 8867;
        try {
            ServerBootstrap b = new ServerBootstrap(); // (3)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (4)
                    .childHandler(new ChannelInitializer() { // (5)
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ServerHandler());
                }
            })
            .option(ChannelOption.SO_BACKLOG, 128)          // (6)
            .childOption(ChannelOption.SO_KEEPALIVE, true); // (7)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (8)

            // 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.
            System.out.println("start server....");
            f.channel().closeFuture().sync();
            System.out.println("stop server....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("exit server....");
        }

    }
}

Nettyサービス側が作成を開始するコード手順を大まかに示しました.次に、具体的に分析します.
元のJAVA SOCKET通信では、IO/NiOベースにかかわらず、実質的にサービス側は2つの作業を処理する必要がある:第一に、クライアントの接続要求を受け入れる;第二に、クライアントの要求を処理して通信する.上記のコードでは、Nettyはすでに2つのEventLoopGroupスレッドグループ(bossGroup/workerGroup)を抽象化してこの2つのタスクを完了しています.
サービス側が起動する前に、明らかにいくつかの構成(Channelのタイプ、サービス側が業務処理を行うHandler、いくつかのTCP/IPプロトコルの構成など)が必要であり、NettyはサーバBootstrap/Bootstrapを利用してそれぞれサーバ/Clientを構成することができる.
ポートをバインドし、サービスを開始し、返されるオブジェクトChannelFutureに注意します.Futureと呼ばれている以上、推測は非同期の動作です.チャンネルFutureによってチャンネルを取得することができ、チャンネル上でチャンネルの読み取り、書き込み、閉じるなどの操作を行うことに注意してください.
bind法により,複数のポートをバインドし,N個のClientsがサーバ側のM個のポートでデータ通信を行うことができる.
次に、サービス側が業務処理を行うHandlerについて見てみましょう.
public class ServerHandler  extends ChannelHandlerAdapter {

    //             ,              
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf in = (ByteBuf) msg;
        try {
            // Do something with msg
            System.out.println("server get :" + in.toString(CharsetUtil.UTF_8));

            ChannelFuture channelFuture = ctx.writeAndFlush(Unpooled.copiedBuffer(("server send time: " + new Date()).getBytes()));

            //          ,    
            channelFuture.addListener(ChannelFutureListener.CLOSE);

        } finally {
            //ByteBuf         ,           release()     
            //or ((ByteBuf)msg).release();
            ReferenceCountUtil.release(msg);
        }

    }

    //exceptionCaught()          Throwable       
    // Netty  IO                   
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();

    }

}

まず、サービス側のHandlerがChannelHandler Adapterを継承していることを見ると、実際にはアダプタ設計モードが体現されています.ChannelHandlerAdapter implements ChannelHandlerは,我々のサービス側Handlerが直接implements ChannelHandlerであればoverrideが非常に多くのAPIを必要とし,まずChannelHandler Adapterを介して汎用的なChannelHandlerを実現し,その後サービス側Handlerに特定のAPIを複写させる.
チャネル上の特定のイベントが発生すると,特定のメソッドを呼び出して処理し,明瞭に見える.
Nettyがネットワーク通信を行うデータ型は、ByteBufなどのバッファデータ型である.以前NIOでByteBufferを利用して通信していたときは、positionの位置変化に注意する必要がありましたが、Nettyでは、これらに関心を持つ必要はありません.
Client/Server側にはバッファが存在するため、バッファのメッセージの解放とリフレッシュに注意する必要があります.読むならreleaseが必要で、書くならflush(flushの時にreleaseした)だけで相手に送信します.
NettyはNIOフレームワークであり、操作が非同期であるため、上のwriteAndFlush操作はFutureオブジェクトに戻り、操作が完了してチャネルを閉じるなど、このFuture上で傍受することができます.
クライアント起動コード
public class Client {

    public static void main(String[] args) {

        EventLoopGroup group = new NioEventLoopGroup();

        try {
              Bootstrap b = new Bootstrap();
              b.group(group)
               .channel(NioSocketChannel.class)
               .handler(new ChannelInitializer() {
                   @Override
                   public void initChannel(SocketChannel ch) throws Exception {
                       ChannelPipeline p = ch.pipeline();
                       p.addLast(new ClientHandler());
                   }
               });

              // Start the client.
              ChannelFuture f = b.connect("127.0.0.1", 8867).sync();

              // Wait until the connection is closed.
              f.channel().closeFuture().sync();

          } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
              // Shut down the event loop to terminate all threads.
              group.shutdownGracefully();
          }

    }

}

ここでは、クライアントとサービス・エンドの起動の違いについて説明します.
第一に、クライアントは1つのスレッドグループしか必要とせず、サービス側は2つ必要とする.
第二に、サービス起動補助クラス、クライアントはBootstrap、サービス側はServer Bootstrap
第三に、サービス側チャネル構成はNioServerSocketChannelであり、クライアントはNioSocketChannelである.
クライアント業務処理Handler
public class ClientHandler extends ChannelHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        ctx.writeAndFlush(Unpooled.copiedBuffer(("client send hello ").getBytes()));

    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf in = (ByteBuf) msg;
        try {
            // Do something with msg
            System.out.println("client get :" + in.toString(CharsetUtil.UTF_8));

            ctx.close();
        } finally {
            //ByteBuf         ,           release()     
            //or ((ByteBuf)msg).release();
            ReferenceCountUtil.release(msg);
        }
    }
}

実行
ここまで、Nettyのシンプルさ、強さを実感しましたか?
次のブログで会いましょう.