Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

イベントドリブンで通信処理を行えるNetty 導入編3


こんにちは!新しい物好きなエンジニアのツカノです。

前回の記事で少しふれた「Netty in Action」が発売されました。ただし、現段階ではEarly Accessということで、PDFで一部の章のみが公開されています。ネットワークプログラミングの基本的な所から丁寧に説明されており、今後、執筆が進むのが楽しみです。
ところで、Nettyは最新安定版がversion3なのですが、version4の開発も進められています。このブログではversion3について紹介していますが、Netty in Actionはversion4のAPIを使って記述しているので、ご注意ください。version3とversion4では、概念的には大きく変わっていませんが、パッケージ名・クラス名・メソッド名等は大幅に変わっています。

それでは、本日もよろしくお願いします。

これまで、「導入編」「導入編2」として、Nettyを使ったエコーサーバのサンプルを見てきました。今回は「導入編3」として、これまでに紹介したコードを使ってNettyの使い方を解説します。

ソースコードの内容説明

EchoServerのサンプルを使って、Nettyのコードで解説します。Client側もほとんど同じですので、Client側の解説は省略します。

  • EchoServer
/**
 * サーバ側メインクラス
 */
public class EchoServer {

    public static void main(String[] args) {
        ChannelFactory factory = 
            new NioServerSocketChannelFactory( // server ★1 
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool()
                    );
        
        ServerBootstrap bootstrap = new ServerBootstrap(factory);
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() { // ★2
            public ChannelPipeline getPipeline() {
                ChannelPipeline pipeline = Channels.pipeline();
                // Downstream(送信)
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); // ★3
                pipeline.addLast("stringEncoder", new StringEncoder()); // ★4
                // Upstream(受信)
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(8192, 0, 4, 0, 4)); // ★5
                pipeline.addLast("stringDecoder", new StringDecoder()); // ★6
                // Application Logic Handler
                pipeline.addLast("handler", new EchoServerHandler()); // server ★7
                
                return pipeline;
            }
        });
        
        bootstrap.bind(new InetSocketAddress(9999)); // 9999番ポートでlisten
    }
}

★1 ServerSocketChannelFactory
Nettyで利用するIO処理の種類を指定します。OioServerSocketChannelFactory、NioServerSocketChannelFactoryが指定できます。
ここに登場するOIO、NIOというのはJavaのコネクション処理の仕組みです。OIOは大量のコネクションを処理しづらい仕組みだったため、NIOという概念が登場し、大量のコネクションを処理できるようになりました。Nettyでは、Factoryの宣言を変更するだけで、どちらも利用可能です。

★2 Pipeline
NettyはPipelineという機能を利用して電文処理を行います。
送信処理はDownstream、受信処理はUpstreamと呼ばれています。Handlerという「電文処理を行うクラス」を組合せてPipelineを作成することにより、電文処理を再利用可能なHandlerを使ってレイヤー化することができます。

この概念をChannelPipelineのJavadocから引用すると、以下の通りです。上の方がアプリケーション側で、下の方がネットワーク側。なので、送信がDownで、受信がUpです。

                                       I/O Request
                                     via Channel or
                                 ChannelHandlerContext
                                           |
  +----------------------------------------+---------------+
  |                  ChannelPipeline       |               |
  |                                       \|/              |
  |  +----------------------+  +-----------+------------+  |
  |  | Upstream Handler  N  |  | Downstream Handler  1  |  |
  |  +----------+-----------+  +-----------+------------+  |
  |            /|\                         |               |
  |             |                         \|/              |
  |  +----------+-----------+  +-----------+------------+  |
  |  | Upstream Handler N-1 |  | Downstream Handler  2  |  |
  |  +----------+-----------+  +-----------+------------+  |
  |            /|\                         .               |
  |             .                          .               |
  |     [ sendUpstream() ]        [ sendDownstream() ]     |
  |     [ + INBOUND data ]        [ + OUTBOUND data  ]     |
  |             .                          .               |
  |             .                         \|/              |
  |  +----------+-----------+  +-----------+------------+  |
  |  | Upstream Handler  2  |  | Downstream Handler M-1 |  |
  |  +----------+-----------+  +-----------+------------+  |
  |            /|\                         |               |
  |             |                         \|/              |
  |  +----------+-----------+  +-----------+------------+  |
  |  | Upstream Handler  1  |  | Downstream Handler  M  |  |
  |  +----------+-----------+  +-----------+------------+  |
  |            /|\                         |               |
  +-------------+--------------------------+---------------+
                |                         \|/
  +-------------+--------------------------+---------------+
  |             |                          |               |
  |     [ Socket.read() ]          [ Socket.write() ]      |
  |                                                        |
  |  Netty Internal I/O Threads (Transport Implementation) |
  +--------------------------------------------------------+

★3〜4
Downstream処理をStringEncoder→LengthFieldPrependerの順で行います。
StringEncoderはStringをバイト配列(電文データ)に変換します。LengthFieldPrependerはバイト配列に電文長を付与します。
この2つのHandlerを組合せて利用することで、Stringを「データの先頭に電文長があり、その後に実際の電文内容が続く」という電文に変換することができます。

★5〜6
Upstream処理をLengthFieldBasedFrameDecoder→StringDecoderの順で行います(DownとUpで順序が逆になります)。LengthFieldBasedFrameDecoderは電文長を解析し、電文長にしたがってバイト配列を取り出します。StringDecoderはバイト配列をStringに変換します。
この2つのHandlerを組合せて利用することで、「データの先頭に電文長があり、その後に実際の電文内容が続く」という電文をStringに変換することができます。

★7
アプリケーションのロジックを記述するクラスを指定します。

レイヤー化、部品化の良いところ

NettyではPipeline、Handlerを利用することで、処理をレイヤー化、部品化することができます。例えば、先ほどのHandlerにSSL処理を行うSslHandlerを加えるだけで、SSL通信が実現できます。
また、Handlerを変更することにより、使用するプロトコルを変えることができます。Nettyでは、http、WebSocket、Protocol Buffers、SPDY等、様々なプロトコルを利用するためのHandlerが用意されています。これは便利ですね。


さて、今回で導入編は終了です。Nettyの紹介を駆け足で行ってきましたが、いかがだったでしょうか。Nettyの力強さが少しでも伝われば、幸いです。


それではまた〜