BARぎこを作りたい(2)

広告

Javaでサーバを作る

Javaでサーバを作るには、典型的にはjava.net.ServerSocketを利用します。

public Server() throws IOException
{
	serverSocket = new ServerSocket();
	serverSocket.bind(new InetSocketAddress(ポート番号));
	Socket clientSocket = serverSocket.accept();
}

大雑把にはこのようにServerSocketを作って、bindしてacceptで接続を待ちます。accept()は接続を受け付ける度にSocketを返しますので、あとは好きにやって下さいというものです。

問題は、accept()もそうだけど、接続があるまでそこで止まってしまいます。これをブロッキングというのですが、それを回避するためにはマルチスレッドを使うことが一般的です。JavaのスレッドはThreadクラスを継承するか、Runnableインターフェースを継承したクラスを使うため、1つの接続の1つのスレッドを割り当てると、自ずと1つの接続に1つのインスタンスが対応します。接続とインスタンスが1:1だから、BARぎこのように少々複雑なこと(キャラクターの移動、あぼーんなど)をしていても、そのクラス内の状態変化ですみます。

ところが、最近(と言ってもずいぶん経つけど)NIOという新しいインターフェースが登場し、これを使うとスレッドを使わなくてもノンブロッキングで上手くさばけます。

ところが、そうすると複雑なサーバプログラムをどう書いていいかよくわからない。ネットのNIOのサンプルもEchoという送られてきたメッセージをそのまま返すだけのシンプルなものが多いため、あんまり参考にならないなーと思っていました。

NIOでサーバを作る

NIOではSelectorというものをつかって、スレッドを使わなくても複数の接続をブロッキングなしでうまく扱うことができます。

_selector = Selector.open();
_serverChannel = ServerSocketChannel.open();
_serverChannel.configureBlocking(false);
_serverChannel.socket().bind(new InetSocketAddress(ポート)); // bindする
_serverChannel.register(_selector, SelectionKey.OP_ACCEPT); // acceptする

準備はServerSocketとあまりかわらず、bindしてacceptするだけです。途中でconfigureBlockingにfalseを渡してブロッキングしません宣言をしておきます。さて、これでどうやって複数の入力をさばくのでしょうか。

selector

Selectorというのは、たとえて言うなら矢印です。たとえいくつの接続があっても、必要に応じて矢印をくるくる回せばOKです。selectするということは、必要があれば矢印を回してくれるということと考えるとよいでしょう。

while( _selector.select() > 0 )
{
	Iterator it = _selector.selectedKeys().iterator();

	while( it.hasNext() )
	{
		SelectionKey key = it.next();
		it.remove();
					
		if( key.isAcceptable() )
			accept( (ServerSocketChannel) key.channel() );
		if( key.isReadable() )
			read( (SocketChannel) key.channel() );
		if( key.isWritable() )
			write( (SocketChannel) key.channel() );
	}			
}

Selector.select()ではブロックが発生します。誰も用がないときはここで足踏みをしますが、誰かに用事があればIteratorに情報をつめて先に進みます。あとは、Iteratorから1つずつ用件と向いている矢印の方向を確認して処理をします。

問題はSelectionKeyはchannelを返すということです。ですから、少し複雑なことをするときはchannelと何らかのクラスの対応を覚えておく必要があります。

private List<SocketChannel> _channelList = new LinkedList<SocketChannel>();
private Map<SocketChannel, ChatClientInfo> _clientMap = new HashMap<SocketChannel, ChatClientInfo>();

こんな感じのコンテナを使ってうまく整理すればOKです。