Java Tips/Socket Channel
J2SE の 1.4 で導入された New I/O API 。これに含まれるソケット チャネルを使用すると、ノンブロッキング接続を行うことができます。また、ソケット チャネルは選択可能チャネルなので、セレクタを使用して複数のチャネルを効率的に扱うことができます。
ストリーム型接続ソケット用の選択可能チャネルです。つまり、クライアント側に使用することができます。
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。 SocketChannel.open メソッドを呼び出します。
SocketChannel.open メソッドを呼び出すと SocketChannel インスタンスを取得することができます。 SocketChannel.open メソッドにリモートアドレスを指定すると、 SocketChannel インスタンスを取得したときに既に接続されています。
SocketChannel のスーパークラスである AbstractSelectableChannel クラスに用意されている AbstractSelectableChannel#configureBlocking メソッドを呼び出すと、チャネルのブロッキング モードを変更することができます。
block 引数には、ブロックモードにする場合は true 、ノンブロックモードにする場合は false を指定します。
チャネルは、既定ではブロック モードになっています。
SocketChannel.open メソッドにリモートアドレスを指定して接続した場合は、インスタンスを取得できたときに接続が完了しています。
SocketChannel.open メソッドを引数なしで呼び出した場合、 SocketChannel#connect メソッドを呼び出して明示的に接続する必要があります。
remote 引数には接続先のリモートアドレスを指定します。戻り値には、接続が確立された場合は true 、ノンブロック モードで接続が進行中の場合は false が返ります。
ブロック モードの場合、このメソッドの呼び出しは、接続が確立されるかエラーが発生するまでブロックします。つまり、戻り値は必ず true が返ります (例外がスローされなければ) 。
ノンブロック モードの場合、このメソッドの呼び出しによってリモートアドレスへの接続が開始されますが、メソッド自体はすぐに返ります。返ったときに接続が確立されているとは限りません。すぐに接続が確立された場合、 true が返ります。すぐには接続が確立されず、接続処理が進行中の場合は false が返ります。この場合、 SocketChannel#finishConnect メソッドを呼び出して接続処理を完了する必要があります。
接続が完了していれば true 、接続が完了していなければ false を返します。接続に失敗したなどの場合は例外をスローします。このメソッドを定期的に呼び出すことで、接続が完了したかどうかを判断することができます。
SocketChannel#read メソッドを呼び出すと、バッファにバイトデータを読み込みます。
ブロック モードの場合、 1 バイト以上読み込むかストリームの末端に達するまでブロックされます。
ノンブロック モードの場合、読み込まなくても返ります。戻り値からは、読み込めなかったのか、ストリームの末端に達したのかは判断できません。
いずれにしろ、一度にバッファがいっぱいになるまで読み込めるとは限りません。例えば、ノンブロック モードの場合、バッファに空きがあっても、直ちに読み込める分のバイトデータしか読み込めません。全く読み込まない場合もあります。
SocketChannel#write メソッドを呼び出すと、バッファにバイトデータを書き込みます。
ブロック モードの場合、全てのバイトデータが書き込まれます。メソッド呼び出しは全てのバイトデータが書き込まれるまでブロックされます。
ノンブロック モードの場合、全てのバイトデータが書き込まれるとは限らず、一部のバイトデータしか書き込まれないか、全く書き込まれない場合もあります。メソッド呼び出しはすぐに返ります。
SocketChannel#close メソッドを呼び出すと、接続を切断し、チャネルを閉じます。
ストリーム型リスニング ソケット用の選択可能チャネルです。つまり、サーバ側に使用することができます。
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。システム全体のデフォルト プロバイダからサーバ ソケット チャネルを開くことができます。
ServerSocketChannel.open メソッドは上記のコードと同じ効果を持ちます。
取得した ServerSocketChannel インスタンスは未バインド状態です。また、 ServerSocketChannel クラスはバインドするメソッドを持っていません。 ServerSocketChannel#socket メソッドで ServerSocket インスタンスを取得することができるので、 ServerSocket#bind メソッドを呼び出すことでバインドします。
SocketChannel クラスと同じで、 ServerSocketChannel#configureBlocking メソッドを呼び出すことでブロッキング モードを変更することができます。
インスタンスかで説明した通り、 ServerSocketChannel#socket メソッドで取得した ServerSocket インスタンスの bind メソッドを呼び出します。
ServerSocketChannel#accept メソッドを呼び出すことで、クライアントからの接続要求を受け入れて接続を確立することができます。接続を確立するとクライアントと対話するための SocketChannel インスタンスが返ります。
ブロック モードの場合、クライアントからの接続要求があるまでブロックされます。
ノンブロック モードの場合、クライアントからの接続要求が無くてもすぐに返ります。その場合、 null が返ります。
ServerSocketChannel#close メソッドを呼び出すと、バインドを解除し、チャネルを閉じます。
ノンブロック モードのチャネルを扱う場合、いつ使用可能なのか判断できません。例えば、 ServerSocketChannel#accept メソッドを呼び出して取得できるのかどうか、 SocketChannel#read メソッドを呼び出してデータを取得できるのかどうか、これらを判断することはできません。セレクタを使用すると、登録したチャネルのうち、使用可能なチャネルを取得することができます。
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。システム全体のデフォルト プロバイダからセレクタを開くことができます。
Selector.open メソッドは上記のコードと同じ効果を持ちます。
セレクタに利用可能なチャネルを選択させるには、チャネルをセレクタに登録する必要があります。 SelectableChannel#register メソッドを呼び出すことで、チャネルをセレクタに登録することができます (Selector のメソッドではないことに注意してください) 。
sel 引数はチャネルを登録するセレクタを指定します。 ops 引数はチャネルが選択される条件、 att 引数にはキーに添付するインスタンスを指定します。戻り値の SelectionKey インスタンスはセレクタに登録したチャネルを識別するキーです。 SelectionKey インスタンスからチャネルを取り出して使用することになります。
以下のようなコードでチャネルをセレクタに登録します。
セレクタにチャネルを登録すると、利用可能なチャネルだけをセレクタに選択させ、選択したチャネルを取り出すことができます。つまり、まず利用可能なチャネルを選択し、次に選択したチャネルを取り出します。
Selector#select 、 Selector#selectNow メソッドを呼び出すことで、利用可能なチャネルを選択することができまする
いずれも、利用可能なチャネルを選択し、選択したチャネルの数を返します。それぞれのメソッドの違いは、「チャネルが利用可能になるまでブロックする」「チャネルが利用可能になるかタイムアウトするまでブロックする」「チャネルが利用可能でなくても即座に返る」です。
Selector#select メソッドが 1 以上の数を返した場合、セレクタは 1 つ以上の利用可能なチャネルを選択したことになります。次に、 Selector#selectedKeys メソッドを呼び出すと、利用可能なチャネルを識別するキーのセットを返します。
戻り値の Set インスタンスには利用可能なチャネルを識別する SelectionKey インスタンスが格納されています。
キーがどのような状態であるかを判断し、チャネルを取り出します。
セレクタを閉じると、全てのチャネルが登録解除されます。登録が解除されてもチャネルが閉じるわけではないので注意してください。
SocketChannel
ストリーム型接続ソケット用の選択可能チャネルです。つまり、クライアント側に使用することができます。
インスタンス化と接続
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。 SocketChannel.open メソッドを呼び出します。
SocketChannel channel = SocketChannel.open();
SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 8888));
SocketChannel.open メソッドを呼び出すと SocketChannel インスタンスを取得することができます。 SocketChannel.open メソッドにリモートアドレスを指定すると、 SocketChannel インスタンスを取得したときに既に接続されています。
ブロッキング モードの変更
SocketChannel のスーパークラスである AbstractSelectableChannel クラスに用意されている AbstractSelectableChannel#configureBlocking メソッドを呼び出すと、チャネルのブロッキング モードを変更することができます。
public final SelectableChannel configureBlocking(boolean block) throws IOException
block 引数には、ブロックモードにする場合は true 、ノンブロックモードにする場合は false を指定します。
チャネルは、既定ではブロック モードになっています。
接続
SocketChannel.open メソッドにリモートアドレスを指定して接続した場合は、インスタンスを取得できたときに接続が完了しています。
SocketChannel.open メソッドを引数なしで呼び出した場合、 SocketChannel#connect メソッドを呼び出して明示的に接続する必要があります。
boolean connect(SocketAddress remote) throws IOException
remote 引数には接続先のリモートアドレスを指定します。戻り値には、接続が確立された場合は true 、ノンブロック モードで接続が進行中の場合は false が返ります。
ブロック モードの場合、このメソッドの呼び出しは、接続が確立されるかエラーが発生するまでブロックします。つまり、戻り値は必ず true が返ります (例外がスローされなければ) 。
channel.connect(new InetSocketAddress("localhost", 8888));
ノンブロック モードの場合、このメソッドの呼び出しによってリモートアドレスへの接続が開始されますが、メソッド自体はすぐに返ります。返ったときに接続が確立されているとは限りません。すぐに接続が確立された場合、 true が返ります。すぐには接続が確立されず、接続処理が進行中の場合は false が返ります。この場合、 SocketChannel#finishConnect メソッドを呼び出して接続処理を完了する必要があります。
boolean finishConnect() throws IOException
接続が完了していれば true 、接続が完了していなければ false を返します。接続に失敗したなどの場合は例外をスローします。このメソッドを定期的に呼び出すことで、接続が完了したかどうかを判断することができます。
SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(new InetSocketAddress("localhost", 8888)); while (!channel.finishConnect()) { // 接続が完了していません。 }
読み込み
SocketChannel#read メソッドを呼び出すと、バッファにバイトデータを読み込みます。
int read(ByteBuffer dst) throws IOException
ブロック モードの場合、 1 バイト以上読み込むかストリームの末端に達するまでブロックされます。
ノンブロック モードの場合、読み込まなくても返ります。戻り値からは、読み込めなかったのか、ストリームの末端に達したのかは判断できません。
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); int len = channel.read(buffer); if (len > 0) { String text = new String(buffer.array(), 0, len); System.out.prnitln(text); }
いずれにしろ、一度にバッファがいっぱいになるまで読み込めるとは限りません。例えば、ノンブロック モードの場合、バッファに空きがあっても、直ちに読み込める分のバイトデータしか読み込めません。全く読み込まない場合もあります。
書き込み
SocketChannel#write メソッドを呼び出すと、バッファにバイトデータを書き込みます。
int write(ByteBuffer src) throws IOException
ブロック モードの場合、全てのバイトデータが書き込まれます。メソッド呼び出しは全てのバイトデータが書き込まれるまでブロックされます。
ノンブロック モードの場合、全てのバイトデータが書き込まれるとは限らず、一部のバイトデータしか書き込まれないか、全く書き込まれない場合もあります。メソッド呼び出しはすぐに返ります。
切断
SocketChannel#close メソッドを呼び出すと、接続を切断し、チャネルを閉じます。
ServerSocketChannel
ストリーム型リスニング ソケット用の選択可能チャネルです。つまり、サーバ側に使用することができます。
インスタンス化
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。システム全体のデフォルト プロバイダからサーバ ソケット チャネルを開くことができます。
ServerSocketChannel serverChannel = SelectorProvider.provider().openServerSocketChannel();
ServerSocketChannel.open メソッドは上記のコードと同じ効果を持ちます。
ServerSocketChannel serverChannel = ServerSocketChannel.open();
取得した ServerSocketChannel インスタンスは未バインド状態です。また、 ServerSocketChannel クラスはバインドするメソッドを持っていません。 ServerSocketChannel#socket メソッドで ServerSocket インスタンスを取得することができるので、 ServerSocket#bind メソッドを呼び出すことでバインドします。
ブロッキング モードの変更
SocketChannel クラスと同じで、 ServerSocketChannel#configureBlocking メソッドを呼び出すことでブロッキング モードを変更することができます。
SelectableChannel configureBlocking(boolean block) throws IOException
バインド
インスタンスかで説明した通り、 ServerSocketChannel#socket メソッドで取得した ServerSocket インスタンスの bind メソッドを呼び出します。
serverChannel.socket().bind(new InetSocketAddress(8888));
待ち受け
ServerSocketChannel#accept メソッドを呼び出すことで、クライアントからの接続要求を受け入れて接続を確立することができます。接続を確立するとクライアントと対話するための SocketChannel インスタンスが返ります。
SocketChannel channel = serverChannel.accept();
ブロック モードの場合、クライアントからの接続要求があるまでブロックされます。
ノンブロック モードの場合、クライアントからの接続要求が無くてもすぐに返ります。その場合、 null が返ります。
切断
ServerSocketChannel#close メソッドを呼び出すと、バインドを解除し、チャネルを閉じます。
セレクタ
ノンブロック モードのチャネルを扱う場合、いつ使用可能なのか判断できません。例えば、 ServerSocketChannel#accept メソッドを呼び出して取得できるのかどうか、 SocketChannel#read メソッドを呼び出してデータを取得できるのかどうか、これらを判断することはできません。セレクタを使用すると、登録したチャネルのうち、使用可能なチャネルを取得することができます。
インスタンス化
コンストラクタが protected であるため、 new 演算子でインスタンスを生成することはできません。システム全体のデフォルト プロバイダからセレクタを開くことができます。
Selector selector = SelectorProvider.provider().openSelector();
Selector.open メソッドは上記のコードと同じ効果を持ちます。
Selector selector = Selector.open();
セレクタへの登録
セレクタに利用可能なチャネルを選択させるには、チャネルをセレクタに登録する必要があります。 SelectableChannel#register メソッドを呼び出すことで、チャネルをセレクタに登録することができます (Selector のメソッドではないことに注意してください) 。
SelectionKey register(Selector sel, int ops);
SelectionKey register(Selector sel, int ops, Object att);
sel 引数はチャネルを登録するセレクタを指定します。 ops 引数はチャネルが選択される条件、 att 引数にはキーに添付するインスタンスを指定します。戻り値の SelectionKey インスタンスはセレクタに登録したチャネルを識別するキーです。 SelectionKey インスタンスからチャネルを取り出して使用することになります。
以下のようなコードでチャネルをセレクタに登録します。
- ソケット チャネル (書き込み、読み込み)
Selector selector = Selector.open(); SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 8888)); channel.register(selector, SelectionKey.OP_READ); channel.register(selector, SelectionKey.OP_WRITE);
- サーバ ソケット チャネル (接続受諾)
Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.socket().bind(new InetSocketAddress(8888)); serverChannel.register(selector, SelectionKey.OP_ACCEPT);
セレクタによるチャネルの選択
セレクタにチャネルを登録すると、利用可能なチャネルだけをセレクタに選択させ、選択したチャネルを取り出すことができます。つまり、まず利用可能なチャネルを選択し、次に選択したチャネルを取り出します。
Selector#select 、 Selector#selectNow メソッドを呼び出すことで、利用可能なチャネルを選択することができまする
int select();
int select(long timeout);
int selectNow();
いずれも、利用可能なチャネルを選択し、選択したチャネルの数を返します。それぞれのメソッドの違いは、「チャネルが利用可能になるまでブロックする」「チャネルが利用可能になるかタイムアウトするまでブロックする」「チャネルが利用可能でなくても即座に返る」です。
if (selector.selectNow() > 0) { // セレクタからチャネルを取り出す。 // チャネルを使用する。 }
チャネルの取り出し
Selector#select メソッドが 1 以上の数を返した場合、セレクタは 1 つ以上の利用可能なチャネルを選択したことになります。次に、 Selector#selectedKeys メソッドを呼び出すと、利用可能なチャネルを識別するキーのセットを返します。
Set selectedKeys();
戻り値の Set インスタンスには利用可能なチャネルを識別する SelectionKey インスタンスが格納されています。
for (Iterator keyIte = selector.selectedKeys().iterator(); keyIte.hasNext();) { SelectionKey key = (SelectionKey) keyIte.next(); }
キーがどのような状態であるかを判断し、チャネルを取り出します。
if (key.isAcceptable()) { // 接続を受諾できる状態である。 // つまり、このキーにはサーバ ソケット チャネルが関連づけられている。 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); // serverChannel#accept メソッドはブロック モードであってもすぐに返る。 // なぜなら、セレクタによって利用可能であることが保証されているから。 SocketChannel channel = serverChannel.accept(); } else if (key.isReadable()) { // データを読み込める状態である。 // つまり、このキーにはソケット チャネルが関連づけられている。 SocketChannel channel = (SocketChannel) key.channel(); // channel#read メソッドはブロック モードであってもすぐに返る。 int len = channel.read(buffer); } else if (key.isWritable()) { // データを書き込める状態である。 // つまり、このキーにはソケット チャネルが関連づけられている。 SocketChannel channel = (SocketChannel) key.channel(); // channel#write メソッドはブロック モードであってもすぐに返る。 int len = channel.write(buffer); }
セレクタを閉じる
セレクタを閉じると、全てのチャネルが登録解除されます。登録が解除されてもチャネルが閉じるわけではないので注意してください。
selector.close();
2005年12月07日(水) 12:33:30 Modified by uguuxp