Taste of Tech Topics

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

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


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

最近「xx in Action」シリーズを見ていると、JavaScript関係の本が随分増えているように思います。このブログでも、JavaScript関係の紹介をしていますが、このシリーズで刊行されると「ある程度使われてきているんだなぁ」と感じます。
実は数日前、NettyコミッタのNormanさんが「Netty in Action」を出すと発表していました。それだけ、Nettyも広がってきているんですね。

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

さて、前回は「導入編」として、サーバサイドのサンプルを紹介しました。今回はその続き「導入編2」として、クライアントサイドのサンプルを紹介します。

具体例を見てみましょう(クライアントサイド)

前回、サンプルとしてクライアントから"Hello, World!"という文字列を受信してそのまま返すエコーサーバを紹介しました。
具体的なプロトコルとしては「データの先頭に電文長があり、その後に実際の電文内容が続く」というものを想定したものでした。

それでは、具体的にクライアントサイドのコードを見てみましょう。EchoClientがメインクラスです。アプリケーションロジックを実装しているのが、EchoClientHandlerです。

  • EchoClient
package netty_sample;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.LengthFieldPrepender;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

/**
 * クライアント側メインクラス
 */
public class EchoClient {

    public static void main(String[] args) {
        ChannelFactory factory =
            new NioClientSocketChannelFactory( // client
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool()
                    );
        
        ClientBootstrap bootstrap = new ClientBootstrap(factory);
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                ChannelPipeline pipeline = Channels.pipeline();
                // Downstream(送信)
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                pipeline.addLast("stringEncoder", new StringEncoder());
                // Upstream(受信)
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(8192, 0, 4, 0, 4));
                pipeline.addLast("stringDecoder", new StringDecoder());
                // Application Logic Handler
                pipeline.addLast("handler", new EchoClientHandler()); // client

                return pipeline;
            }
        });
        
        ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 9999)); // 9999番ポートにconnect
        future.getChannel().getCloseFuture().awaitUninterruptibly();
        bootstrap.releaseExternalResources();
    }
}
  • EchoClientHandler
package netty_sample;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

/**
 * クライアント側アプリケーションロジック
 */
public class EchoClientHandler extends SimpleChannelHandler {
    /**
     * サーバに接続した際に呼び出されるメソッド
     */
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent event) {
        ctx.getChannel().write("Hello, World!");
    }
    
    /**
     * サーバから電文を受信した際に呼び出されるメソッド
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) {
        String msg = (String) event.getMessage();
        System.out.println(msg);
    }
}

メインクラス(EchoClient)の方は、前回紹介したサーバサイドのメインクラス(EchoServer)似ていますよね。この部分は、通信するために必要な構成要素を組み立てる部分です。
ロジックがあるのは、EchoClientHandlerクラスです。このくらいシンプルな記述で「オリジナルプロトコルを実装できる」のがNettyの凄いところです。

動作させてみましょう

それでは、実際に動作させてましょう。最初にサーバのメインクラスを実行し、次にクライアントのメインクラスを実行してください。すると、以下の流れで動作します(デバッガを利用してステップ実行してみると、良く分かると思います)。

  1. EchoServerを起動してください。9999番ポートでクライアントからの接続を待ちます。
  2. サーバが起動したら、EchoClientを起動してください。EchoClientは9999番ポートに接続します。
  3. クライアントがサーバに接続すると、EchoClientHandler#channelConnectedがNettyから呼び出されます。この中で、サーバに対して文字列"Hello, World!"を送信します。
  4. サーバがクライアントからの電文を受信すると、EchoServerHandler#messageReceivedがNettyから呼び出されます。クライアントから受信した文字列を、クライアントに送信します。
  5. クライアントがサーバからの電文を受信すると、EchoClientHandler#messageReceivedがNettyから呼び出されます。サーバから受信した文字列を標準出力します。
Hello, World!

うん、簡単ですね。
重要なことなので、もう一度良いますがこれでオリジナルプロトコルが実装できました。OSS側で決められたプロトコルではなく、自分で作ったプロトコルを簡単に実装できるのがNettyの良いところです。

段々と、Nettyの良さが伝わってきたでしょうか。
それではまた、次回をお楽しみに。