Grizzlyの概要 2 : Java New I/Oで実装されたサーバ

2008年5月23日 at 12:05 AM



さてさて、今日も引き続きGrizzlyを説明したいと思いますが、

その前に、やはりJava New I/Oについて理解して頂かなければ

本当の意味でGrizzlyを理解していただけませんので、Grizzlyの

ソースコードを追う前にJava New I/Oについて簡単に復習して

おきたいと思います。

ただし、ここではほんの概要程度の説明ですのでちゃんと

理解されたい方は別途Java NIOについて説明されている記事を

読んで理解してください。


※ 本エントリの一番最後にJava New I/Oを学ぶことができる

  参考URLを示しています。



Java New I/Oでノンブロッキングサーバの実装:



前回、Java New I/Oを使って実装されたサーバは、今までの

マルチスレッド型のBlocking I/Oを使用して実装されたサーバより

新規スレッドを作成する必要がなくスレッド数を少なくすることが

できると説明しましたが、では具体的にどのようにして実装されているかを

説明致します。







まず、Non Blocking I/Oを理解する上で必要なJava New I/OのAPIを紹介します。



チャネル:


チャネルとは、ハードウェアデバイス、ファイル、ネットワークソケットのほか、

個別の入出力操作を実行できる接続を表します。

Non Blockingにできるチャネルは、java.nio.channels.SelectableChannelクラス

継承したサブクラスで、java.nio.channels.SocketChannel

java.nio.channels.ServerSocketChannelクラスがあります。

このクラスのインスタンスメソッドで、configureBlocking(false)を

実行するとチャネルをNon Blockingモードに調整することができます。



例えば、ServerSocketChannelを使ってチャネルをノンブロッキングモードに

する為には下記のようにserverChannel.configureBlocking(false)を記載します。









selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
for (Iterator it = selector.selectedKeys().iterator(); it.hasNext();) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isAcceptable()) {
doAccept((ServerSocketChannel) key.channel());
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
doRead(channel);
}
}
}




serverChannelというチャネルをNon Blockingモードに設定した後、

Selectorのインスタンス selector に対してregister(登録)します。

実はこの行がJava New I/Oの中でとても重要な箇所です。



今までのマルチスレッド型のサーバの場合、java.net.ServerSocket.accept()メソッド

を呼び出してソケットのアクセプト処理を行っていましたが、Java New I/Oでは

java.nio.channels.ServerSocketChannel.accept()メソッドでアクセプト処理を行います。

この際、ServerSocketChannel.accept()メソッドはNon Blockingモードに設定されている場合、

保留されている接続がない場合、直ちにnullを返します。

つまりServerSocket.accept()のように新たなクライアントからの接続がくるまで

待ち続けることはありません。



しかし、クライアントからの接続を待たないということはサーバ側ではどのタイミングで

アクセプト処理を行えばよいのかが分からなくなります。

そこで、利用できるチャネル(接続)を取得する為にセレクタ(Selector.select())を使用します。

チャネルをセレクタに登録しておき、セレクタの中で利用できるようになったチャネルを

SelectionKeyとして取得して入出力操作を行うことができます。

セレクタには、複数のチャネルを登録することもでき、

複数の入出力操作を同時に行うことができるようになります。



例えば、下記にかんたんなエコーサーバをJava NIOを使用して

作成してみましたが、下記ではクライアントからの接続毎に

スレッドを生成していないことが御分かりいただけるかと思います。

また、マルチスレッド型のサーバのようにコネクション毎に新規スレッドを

生成していないことがわかります。



例:Java New I/Oで実装したサンプルエコーサーバ







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.nio.charset.Charset;
import java.util.Iterator;
public class NonBlockingServer {
private static final int SERVER_PORT = 8888;
private static final int BUF_SIZE = 2000;
private Selector selector;
public static void main(String[] args) {
NonBlockingServer nserver = new NonBlockingServer();
nserver.start();
}
public void start(){
ServerSocketChannel serverChannel = null;
try {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
for (Iterator it = selector.selectedKeys().iterator();
it.hasNext();) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isAcceptable()) {
doAccept((ServerSocketChannel) key.channel());
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
doRead(channel);
}
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void doAccept(ServerSocketChannel serverChannel) {
try {
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void doRead(SocketChannel channel) {
ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE);
Charset charset = Charset.forName(“UTF-8”);
try {
if (channel.read(buf) < 0) {
return;
}
buf.flip();
System.out.print(
charset.decode(buf).toString());
buf.flip();
channel.write(buf);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}




このように、Java New I/Oを使用して作成されたサーバは、接続毎に

新規スレッドを作成しなくてもよくなるため、既存のサーバ実装とは

特に大量のアクセス等が発生した際に大きな差がでてきます。

例えば、メモリの消費量も少なくて済むので高負荷時になればなるほど

使用するサーバリソースが大きく変わってくるかと思います。



次回は、Grizzly 1.0.19のソースコードを読む際にどこから見ていけばよいのか、

また、Grizzly 1.0.19の中でどのようなクラスが重要なのか等

ソースコードを見るために必要な情報を紹介したいと思います。



※  Grizzlyのソースコードを読む前に、上記のSelector,SocketChannel

  ServerSocketChannel,SelectionKey等のクラスを理解しておいてください。



Java New IOの参考記事へのURL:



● 横河電機の櫻庭さんによるIT Proの記事「New I/Oで高速な入出力」

● TECHSCORE (5. ノンブロッキングチャネル)


広告

Entry filed under: Application Server/GlassFish. Tags: , , .

SDC 連載記事 第3回:クラスタと負荷分散 (1) JavaOne 2008 報告会開催


Java Champion & Evangelist

ご注意

このエントリは個人の見解であり、所属する会社の公式見解ではありません

カレンダー

2008年5月
« 4月   6月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

カテゴリー

Twitter

clustermap

ブログ統計情報

  • 986,051 hits

Feeds


%d人のブロガーが「いいね」をつけました。