本記事は、Javaによるネットワークプログラミングについての学習メモとなります。
その中でNIOフレームワークであるNettyを使った実装方法について学んでいきます。
今回は、シンプルなサーバプログラムを作成してみます。
ネットワークプログラミングについて
ネットワークプログラミングの目的はデータの送受信をすることで、そのためにソケットというインターフェースを利用します。
このソケットは、TCP/IPの誕生時にBSD Uinux上に実装されたものですが、非常に使い勝手が良かったため、WindowsなどのUnix系以外のOSでも利用されています。
Nettyについて
Javaには元々java.ioというパッケージが存在しますが、ブロッキングIOやストリーム指向であることから、Java 1.4からjava.nioというパッケージが導入されました。
このNIO(New IO)では、ノンブロッキングIOとバッファ指向で設計され、さらにセレクタを利用し単一スレッドで複数の入力チャネルをモニタ出来るようになり、様々な改良が加えられました。
今回はこのNIOのフレームワークであるNettyを利用していきます。
以下が公式ドキュメントになります。
サーバプログラムの作成
今回はNetcatなどでデータ送受信をするようなシンプルなサーバプログラムを作成します。
内容は単純なエコーサーバとなり、クライアントが送信してきたデータに「[+]received(ip_address:8000): message」を付与して送り返します。
プログラムとしては、以下の3つから構成されます。
- Bootstrap:アドレスとポートのバインド処理などを行う
- ChannelHandler:クライアントの接続を処理する
- ChannelInitializer:チャネル初期化処理(バイト列変換など)を行う
# SimpleTCPChannelHandler.java package tcpserver; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class SimpleTCPChannelHandler extends SimpleChannelInboundHandler<String> { @Override public void channelActive(ChannelHandlerContext ctx) { String[] remoteIp = ctx.channel().remoteAddress().toString().split("/"); System.out.println("[+]got a connection from " + remoteIp[1]); ctx.writeAndFlush("> "); } @Override public void channelInactive(ChannelHandlerContext ctx) { String[] remoteIp = ctx.channel().remoteAddress().toString().split("/"); System.out.println("[-]connection closed from " + remoteIp[1]); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { String[] remoteIp = ctx.channel().remoteAddress().toString().split("/"); System.out.printf("[+]received(%s): %s", remoteIp[1], msg); ctx.writeAndFlush("received: " + msg); ctx.writeAndFlush("> "); } }
# SimpleTCPServerBootstrap.java package tcpserver; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; class SimpleTCPServerBootstrap { void start(int port) throws InterruptedException { System.out.printf("[*]starting tcp server on port %d...\n", port); EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new SimpleTCPChannelInitializer()) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture cf = serverBootstrap.bind(port).sync(); if (cf.isSuccess()) { System.out.println("[*]server started successfully!!"); } System.out.println("[*]waiting for client connection..."); cf.channel().closeFuture().sync(); } finally { System.out.println("[*]stopping TCP Server..."); workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } }
# SimpleTCPChannelInitializer.java package tcpserver; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; public class SimpleTCPChannelInitializer extends ChannelInitializer<SocketChannel> { protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new StringEncoder()); socketChannel.pipeline().addLast(new StringDecoder()); socketChannel.pipeline().addLast(new SimpleTCPChannelHandler()); } }
# Main.java package tcpserver; public class Main { public static void main(String[] args) { try { SimpleTCPServerBootstrap server = new SimpleTCPServerBootstrap(); server.start(8000); } catch (InterruptedException e) { e.printStackTrace(); } } }
動作確認
それでは、上記プログラムを実行してみます。
# server側 [*]starting tcp server on port 8000... [*]server started successfully!! [*]waiting for client connection... [+]got a connection from 0:0:0:0:0:0:0:1:44026 [+]received(0:0:0:0:0:0:0:1:44026): hello [-]connection closed from 0:0:0:0:0:0:0:1:44026
クライアントからncコマンドで接続をします。
# client側 > hello received: hello > ^C
最後に
今回はシンプルなサーバプログラムの作成方法について学びました。
まだJava NIOの設計思想が理解できていない面もありますが、それぞれの処理が分担されているので、慣れれば使いやすいフレームワークであると思います。