この記事ではWIN10にインストールしたJava 10.0.1を使っています。
#初めに
前回はBIO転送路を作りました、今日は引き続きNIOを実装してみます。
#NIOについて
##NIOとは
簡単に言うと、I/Oリクエストをブロッキングさせないものです。既存のI/O処理(BIO)を改善できます。
##BIOの良くない点
まずクライアント側からリクエストに対して、接続リクエストとI/Oリクエスト二つ部分が含まれています。
接続リクエストが必ず行いますが、I/Oリクエストは時と場合によって行います。
ですので、BIOのようなI/O処理があるかどうかを問わずに1リクエストに対して関わらず1個処理スレッドを用意する方式がサーバーの性能を無駄に利用しています。
##NIOの良い点
NIOはクライアント側からの全てリクエストに対して、1個の処理スレッドしか用意していません。I/Oリクエストがある場合に、別のスレッドにI/O処理を頼みます。
#実装
Java言語からSocketChannel、ServerSocketChannelを提供していので、今回はこれらを使ってTCP/IP+NIOを実装してみます。
(公式APIはここ:SocketChannel、ServerSocketChannel
サーバ側のファイル
ChannelServer.java の内容
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));
}
}
}
}
ChannelServer.java の内容
リクエストを処理するスレッド
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;
}
}
}
}
クライアント側のファイル
socketChannelを生成し、指定されたサーバとポートをアクセスします
public class ChannelClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(1234));
while (true) {
Scanner sc = new Scanner(System.in);
String next = sc.next();
sendMessage(socketChannel, next);
}
}
// ChannleがIOするための容器を用意
public static void sendMessage(SocketChannel socketChannel, String mes) throws IOException {
if (mes == null || mes.isEmpty()) {
return;
}
byte[] bytes = mes.getBytes("UTF-8");
int size = bytes.length;
ByteBuffer buffer = ByteBuffer.allocate(size);
ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
sizeBuffer.putInt(size);
buffer.put(bytes);
buffer.flip();
sizeBuffer.flip();
ByteBuffer dest[] = {sizeBuffer,buffer};
while (sizeBuffer.hasRemaining() || buffer.hasRemaining()) {
socketChannel.write(dest);
}
}
}
#結果認証
サーバーを起動し、接続を待ち受け中です:
サーバー
PS C:\Users\ma\Documents\work\socket\nio_tcp> java ChannelServer
クライアント1,2それぞれを起動し、何かを入力します:
クライアント1
PS C:\Users\ma\Documents\work\socket\nio_tcp> java ChannelClient
iamfirstuser!
byebyefirstone!
Exception in thread "main"
PS C:\Users\ma\Documents\work\socket\nio_tcp>
クライアント2
java ChannelClient
iamseconduser
byebyesecondone!
Exception in thread "main"
PS C:\Users\ma\Documents\work\socket\nio_tcp>
サーバー
PS C:\Users\ma\Documents\work\socket\nio_tcp> java ChannelServer
client:/192.168.56.1:50138 access successfully!
client:/192.168.56.1:50139 access successfully!
/192.168.56.1:50138:iamfirstuser!
/192.168.56.1:50139:iamseconduser
/192.168.56.1:50138:byebyefirstone!
/192.168.56.1:50138access colsed
/192.168.56.1:50139:byebyesecondone!
/192.168.56.1:50139access colsed
#まとめ
今回はJavaのSocketChannel、ServerSocketChannelライブラリを使ってTCP/IP+NIOの転送路を作ってみました。
1サーバーが同時に複数のリクエストを対応できることも認証出来ました。