Engineering Note

プログラミングなどの技術的なメモ

クライアントプログラムの作成(Javaによるネットワークプログラミング)

tcp_client

本記事は、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を利用していきます。

以下が公式ドキュメントになります。

 

 

クライアントプログラムの作成

今回は前回作成したサーバプログラムと通信可能なTCPクライアントを作成していきます。

 

 

プログラムとしては、サーバプログラムと同様に以下の3つから構成されます。

  • Bootstrap:サーバへのコネクト処理などを行う
  • ChannelHandler:サーバとの接続を処理する
  • ChannelInitializer:チャネル初期化処理(バイト列変換など)を行う

 

# SimpleTCPChannelHandler.java
package tcpclient;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class SimpleTCPChannelHandler extends SimpleChannelInboundHandler<String> { 
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("[*]connect to server successfully!!");
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        System.out.println("[*]server connection closed.");
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // String remoteIp = ctx.channel().remoteAddress().toString().replace("/", "");
        if (msg.equals("> ")) {
            System.out.print(msg);
        } else {
            String[] remoteIp = ctx.channel().remoteAddress().toString().split("/");
            System.out.printf("[+]received(%s): %s", remoteIp[1], msg);
        }
    }
}

 

 

# SimpleTCPClientBootstrap.java
package tcpclient;

import java.net.ConnectException;
import java.util.Scanner;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

class SimpleTCPClientBootstrap {    
    void start(String host, int port) throws InterruptedException, ConnectException {
        System.out.println("[*]connecting to " + host + ":" + port);
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap clientBootstrap = new Bootstrap();
            clientBootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new SimpleTCPChannelInitializer());
            ChannelFuture cf = clientBootstrap.connect(host, port).sync();
            String cmd = "";
            Scanner scanner = new Scanner(System.in);
            Channel channel = cf.sync().channel();
            while (true) {
                cmd = scanner.nextLine();
                if (cmd.equals("quit")) {
                    break;
                }
                channel.writeAndFlush(cmd + "\n");
            }
            channel.close();
            scanner.close();
            cf.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();   
        }        
    }
}

 

 

# SimpleTCPChannelInitializer.java
package tcpclient;

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> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new StringDecoder());
        socketChannel.pipeline().addLast(new StringEncoder());
        socketChannel.pipeline().addLast(new SimpleTCPChannelHandler());
    }
}

 

 

# Main.java
package tcpclient;

import java.net.ConnectException;

public class Main {
    public static void main(String[] args) {
        try {
            SimpleTCPClientBootstrap client = new SimpleTCPClientBootstrap();
            client.start("localhost", 8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ConnectException e) {
            System.out.println("[*]failed to connect server.");
        }
    }
}

 

動作確認

それでは、前回作成したサーバプログラムを起動後に上記プログラムを実行してみます。

 

 # server側
 [*]starting tcp server on port 8000...
 [*]server started successfully!!
 [*]waiting for client connection...
 [+]got a connection from 127.0.0.1:42276
 [+]received(127.0.0.1:42276): hello
 [-]connection closed from 127.0.0.1:42276

 

 # client側
 [*]connecting to localhost:8000
 [*]connect to server successfully!!
 > hello
 [+]received(127.0.0.1:8000): received: hello
 > quit
 [*]server connection closed.

 

最後に

今回はシンプルなクライアントプログラムの作成方法について学びました。

まだJava NIOの設計思想が理解できていない面もありますが、それぞれの処理が分担されているので、慣れれば使いやすいフレームワークであると思います。