Help us understand the problem. What is going on with this article?

Nettyを使ってサーバーを実装してみた

More than 1 year has passed since last update.

初めに

前回はwebSocketやSocketChannelなどのライブラリを使ってTCP/IP+NIO/BIOの転送路を作ってみましたが、かなりコードが複雑になることが分かりました。 今回は改めてNettyを使ってサーバーを実現してみます。

Nettyとは

簡単に言うと、Nettyとは、非同期通信を行うアプリケーションを開発するためのフレームワークです。SocketChannelで実現したNIO処理に比べると、低レイヤーのAPI(select(),read()など)を直接操作する必要がなくなり、Netty側で隠蔽されました。

実装

ServerSocketChannelでNIO実装

まず、前回のServerSocketChannelでNIO実装を再掲させていただきます。

➀ServerSocketChannelを生成し、クライアント側のアクセスを待ち受けます

public class ChannelServer {

    public static void main(String[] args) throws IOException {
        // I/Oリクエストを処理するスレッドプールを用意
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 1000, TimeUnit.MILLISECONDS, 
                new ArrayBlockingQueue<Runnable>(100));

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(1234));

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();

            if (socketChannel != null) {
                // リクエストをスレッドプールにコミット
                executor.submit(new ChannelServerThread(socketChannel));
            }
        }
    }
}

②リクエストを処理するスレッド

public class ChannelServerThread implements Runnable {

    private SocketChannel socketChannel;
    private String remoteName;

    public ChannelServerThread(SocketChannel socketChannel) throws IOException {
        this.socketChannel = socketChannel;
        this.remoteName = socketChannel.getRemoteAddress().toString();
        System.out.println("client:" + remoteName + " access successfully!");
    }

    // I/Oリクエストを処理
    @Override
    public void run() {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
        StringBuilder sb = new StringBuilder();
        byte b[];
        // socketChannelからデータと長さを読み込んで、標準出力に出力する
        while(true) {
            try {
                sizeBuffer.clear();
                int read = socketChannel.read(sizeBuffer);
                if (read != -1) {
                    sb.setLength(0);
                    sizeBuffer.flip();
                    int size = sizeBuffer.getInt();
                    int readCount = 0;
                    b = new byte[1024];
                    while (readCount < size) {
                        buffer.clear();
                        read = socketChannel.read(buffer);
                        if (read != -1) {
                            readCount += read;
                            buffer.flip();
                            int index = 0 ;
                            while(buffer.hasRemaining()) {
                                b[index++] = buffer.get();
                                if (index >= b.length) {
                                    index = 0;
                                    sb.append(new String(b,"UTF-8"));
                                }
                            }
                            if (index > 0) {
                                sb.append(new String(b,"UTF-8"));
                            }
                        }
                    }
                    System.out.println(remoteName +  ":" + sb.toString());
                }
            } catch (Exception e) {
                System.out.println(remoteName + "access colsed");
                try {
                    socketChannel.close();
                } catch (IOException ex) {
                }
                break;
            }
        }
    }
}

NettyでNIO実装

➀クライアント側のアクセスを待ち受ける処理

public class EchoServer {
    private final int port;

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

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap sb = new ServerBootstrap();
            // スレッド プールバインディング
            sb.group(group)
                    .channel(NioServerSocketChannel.class)
                     // ポート番号バインディング
                    .localAddress(this.port)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("connected...; Client:" + ch.remoteAddress());
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            // サーバーの非同期バインディング
            ChannelFuture cf = sb.bind().sync();
            System.out.println(EchoServer.class + " started and listen on " + cf.channel().localAddress());
            // サーバーチャンネルを閉じる
            cf.channel().closeFuture().sync();
        } finally {
            // スレッド プールのバインディングを解き消し
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception {
        // 入口
        new EchoServer(66666).start();
    }
}

②リクエストを処理するやつ

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server channelRead...; received:" + msg);
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server channelReadComplete..");
        // 空バッファーにWriteAndFlush,接続をクローズする
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("server occur exception:" + cause.getMessage());
        cause.printStackTrace();
        ctx.close();
    }
}

変更点

コード量が大幅減りました。
それはread()、Buffer操作などのAPIをNetty側から隠蔽し、便利な高レイヤーのAPIを提供するためです。例えば、EchoServerHandler クラスのchannelRead()メソッド:クライアント側からのデータを受け付けると、内部的にでStringに変換してくれるため、このメソッドが呼び出されます。

まとめ

今回はNettyを使ってサーバーを実装してみました。クライアント側との連携は次回に紹介します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした