Non blocking IOでecho server

まぁクリスマスなんですがJavaのNon blocking IO周りについて少し調べてました。非リアですね。

参考にしたのはうっすらと全体を網羅していたっぽいこちら => http://tutorials.jenkov.com/java-nio/index.html

で、手を動かさないと理解できない人種なので簡単にecho serverを書いてみました。

package com.komamitsu;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Main {
  private static final int PORT_NUMBER = 12345;
  private static final int BUFFER_SIZE = 256;

  public static void main(String[] args) throws IOException {
    ServerSocketChannel server = ServerSocketChannel.open();
    server.socket().bind(new InetSocketAddress(PORT_NUMBER));
    server.socket().setReuseAddress(true);
    server.configureBlocking(false);

    Selector selector = Selector.open();
    server.register(selector, SelectionKey.OP_ACCEPT);

    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    while (true) {
      int channelCount = selector.select();
      if (channelCount > 0) {
        Set<SelectionKey> keys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = keys.iterator();
        while (iterator.hasNext()) {
          SelectionKey key = iterator.next();
          iterator.remove();

          if (key.isAcceptable()) {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ, client.socket().getPort());
          } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            p("port: " + key.attachment());
            if (client.read(buffer) < 0) {
              key.cancel();
              client.close();
            } else {
              buffer.flip(); // read from the buffer
              /*
               * byte[] received = new byte[buffer.remaining()];
               * buffer.get(received); buffer.clear(); // write into the buffer
               * buffer.put(received); buffer.flip(); // read from the buffer
               */
              client.write(buffer);
              buffer.clear(); // write into the buffer            }
          }
        }
      }
    }
  }

  private static void p(String s) {
    System.out.println(s);
  }
}

コツは

  • 最初にlisten channelについてacceptのevent監視を登録
  • accept eventが発生したらそのclient channelに対してreadのevent監視を登録
  • read eventが発生したらclient channelから読んで書き返す
  • ByteBufferはallocate後はwrite modeで、読む際はflip()してread modeにしてから。また書き込む際(channel.write()とか)はclear() or compact()してwrite modeにしてから
  • event setのselector.selectedKeys()は外部イテレータからremove()してあげないとeventが残ったままになっちゃう(次回のselect()でも残ってしまう)

とかでしょうか。特にByteBufferでmodeが切り替わるのは個人的には好きくないですねぇ...


娘達へのプレゼントは "どうぶつしょうぎ" を買いました。二人共はまってしまって、やや中毒状態です。ふふふ... 狙いどおり。

二歳になったばかりの息子には救急車のミニカーを買いました。本当は消防車のはしご車が良さげだったのですが、数日でハシゴを折られそうだったので落としどころとして知名度があり耐久性が高そうな救急車になりました。こちらはあまり好評ではありませんね...