I am learning the NIO package. I refer the NioServer example from here. The selector thread in NioServer.java blocks on
this.selector.select();
Iterator<SelectionKey> selectedKeys = this.selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
} else if (key.isReadable()) {
this.read(key);
} else if (key.isWritable()) {
this.write(key);
}
When a remote client connects, this.accept(key) is called and in this method the interest
interestOps is changed to Read and awakes the selector.
is this what causes the selector to select this channel? So we signal in this way for the channel to be picked?
Now suppose in write to a socket channel selector is signalled by changing the
interest that the channel is ready for write.
But suppose in write did not complete due to socket buffer full as shown in code, then we don't change the interest and keep it as is as write only.
then when will the selector select this channel?
this.accept(key) calls serverSocketChannel.accept() which returns a new socket channel for communication with the client. It's this channel that is registered with the selector for "read" operations, i.e. the selector now has two registrations:
the original ServerSocketChannel, with OP_ACCEPT
the SocketChannel for the new client, with OP_READ
If a write cannot complete due to the buffer filling up, the corresponding SocketChannel remains registered with OP_WRITE. Once the client reads some data off the other end, the channel will be selected again, allowing us to write the remaining data before flipping the interest set back to OP_READ.
OP_WRITE triggers when then there is room in the socket send buffer.
NB getting a zero length write() result is the only occasion for using OP_WRITE. Most of the time there is room, so OP_WRITE will keep triggering. You don't want this, so you normally don't have OP_WRITE registered for a channel: only when it has just returned zero from write; and you deregister it when that write eventually completes via being re-triggered after an OP_WRITE.
Related
I'm working on a transport mechanism, where I am supposed to ignore OP_READ events on a socket, if the internal queue is already full.
SelectionKey next = it.next();
if (next.isReadable()) {
SocketChannel client = (SocketChannel) next.channel();
if (!innerqueueIsFull()) {
if (client.read(nextRead) == -1) {
break;
}
}
}
After writing a response for one of the pending requests, I want to go back and retry reading the data packets on the channel. How can I retry reading the request?
I'm working on a transport mechanism, where I am supposed to ignore OP_READ events on a socket, if the internal queue is already full.
The correct way to do that is to unregister interest in OP_READ for that channel, or for all channels if there is only one internal queue. Then you won't even get the isReadable() events on the channel. When the queue clears, re-register OP_READ.
The effect will be to block the affected peers from sending.
How can I retry reading the request?
You don't have to retry anything this way. Just react to isReadable() in the normal way. As a matter of fact you don't have to retry anything your way either, as you never did the read in the first place, so there is nothing to retry.
NB If client.read() returns -1 it is utterly bizarre to break. You should close the channel and continue. There's no reason not to process other events just because one client has disconnected.
I want to determine port whether open.
InetSocketAddress address = new InetSocketAddress("www.google.com", 80);
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(address);
socketChannel.register(selector, SelectionKey.OP_WRITE);
int result = selector.select();
System.out.println(result);
If the port is not open, think I look the same it return 0,
but when the port is open,it also return 0,I expect it can return 1.
It's because you're selecting for the wrong event. You should have registered the channel for OP_CONNECT. Then, when you get it, call finishConnect(), and if it returns true deregister OP_CONNECT and register whatever event you're interested in next, i.e. OP_READ or OP_WRITE.
Note that if finishConnect() returns false you should just keep selecting, and if it throws an exception the connection has failed and you should close the channel.
If you want to avoid all this complication it is usually simpler to do the connect while still in blocking mode, and then put the channel into non-blocking mode and select.
Although there is really very little point in using NIO in a client at all.
See here for a fuller version of this answer.
I have working code that uses non-blocking IO to fetch UDP packets like this:
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(AUDIO_PORT));
channel.configureBlocking(false);
while(true){
ByteBuffer packet = ByteBuffer.allocate(MAX_PACKET);
if(channel.receive(packet) != null){
//Got something!
...
}
...
}
That works perfectly.
Now I'm trying to do exactly the same, only this time I want to use selectors, like this:
//Create a datagram channel, bind it to port, configure non-blocking:
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(AUDIO_PORT));
channel.configureBlocking(false);
//Create a selector and register it:
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
//Spin
while(true){
//If there's a packet available, fetch it:
if(selector.selectNow() >= 1){
//**CODE NEVER REACHES THIS POINT**
ByteBuffer packet = ByteBuffer.allocate(MAX_PACKET);
channel.receive(packet);
...
}
...
}
Due to the application I'm making, I really need it to be non-blocking IO (even though it looks like I'm just spinning in my example), and blocking with a short timeout just won't work. I also really have to use Selectors. Issue is, even though I have a server actively sending packets to the device's AUDIO_PORT port, the select() operation always returns 0. I know the server app is doing its work since the first snippet works fine. Am I setting up the Selector wrong? I'm guessing I'm missing some step, but I just can't figure it out.
As I believe we have discussed previously in other threads, if the first code works and the second doesn't, Selectors must be broken in Android. Your code is correct (as long as you clear the selected key set of the Selector every time you get a non-zero return). You can verify that by running it on a Java platform.
You might consider changing select() == 1 to select() > 0 for greater generality, and then looping around the selected-key set as you will see in all the examples, but it shouldn't affect the correctness of this code.
As I also believe we have discussed, you could try blocking mode with a short read timeout instead of the Selector.
NB You aren't spinning, you are blocking forever in select().
This is a pretty straight forward question, but I have found a need to unregister a selector overlooking my socket channel for java.
SocketChannel client = myServer.accept(); //forks off another client socket
client.configureBlocking(false);//this channel takes in multiple request
client.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//changed from r to rw
Where I can later in the program call something like
client.deregister(mySelector);
And that the selector will no longer catch data keys for that socket channel. This would make life much easier for me given my server/client design.
Call cancel() on the selection key:
SelectionKey key = client.register(mySelector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
...
key.cancel();
or
...
SelectionKey key = client.keyFor(mySelector);
key.cancel();
In addition to #Nikolai 's answer. Doing client.close() will also deregister the channel.
A key is added to its selector's cancelled-key set when it is cancelled, whether by closing its channel or by invoking its cancel method.
From https://docs.oracle.com/javase/7/docs/api/java/nio/channels/Selector.html
In writing some test code I have found that Selector.select() can return without Selector.selectedKeys() containing any keys to process. This is happening in a tight loop when I register an accept()ed channel with
SelectionKey.OP_READ | SelectionKey.OP_CONNECT
as the operations of interest.
According to the docs, select() should return when:
1) There are channels that can be acted upon.
2) You explicitly call Selector.wakeup() - no keys are selected.
3) You explicitly Thread.interrupt() the thread doing the select() - no keys are selected.
If I get no keys after the select() I must be in cases (2) and (3). However, my code is not calling wakeup() or interrupt() to initiate these returns.
Any ideas as to what is causing this behaviour?
Short answer: remove OP_CONNECT from the list of operations you are interested in for the accepted connection -- an accepted connection is already connected.
I managed to reproduce the issue, which might be exactly what's happening to you:
import java.net.*;
import java.nio.channels.*;
public class MyNioServer {
public static void main(String[] params) throws Exception {
final ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(true);
serverChannel.socket().bind(new InetSocketAddress("localhost", 12345));
System.out.println("Listening for incoming connections");
final SocketChannel clientChannel = serverChannel.accept();
System.out.println("Accepted connection: " + clientChannel);
final Selector selector = Selector.open();
clientChannel.configureBlocking(false);
final SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
System.out.println("Selecting...");
System.out.println(selector.select());
System.out.println(selector.selectedKeys().size());
System.out.println(clientKey.readyOps());
}
}
After the above server receives a connection, the very first select() on the connection exits without blocking and there are no keys with ready operations. I don't know why Java behaves in this way, but it appears many people get bitten by this behavior.
The outcome is the same on Sun's JVM 1.5.0_06 on Windows XP as well as Sun's JVM 1.5.0_05 and 1.4.2_04 on Linux 2.6.
The reason is that OP_CONNECT and OP_WRITE are the same thing under the hood, so you should never be registered for both simultaneously (ditto OP_ACCEPT and OP_READ), and you should never be registered for OP_CONNECT at all when the channel is already connected, as it is in this case, having been accepted.
And OP_WRITE is almost always ready, except when the socket send buffer in e kernel is full, so you should only register for that after you get a zero length write. So by registering the already connected channel for OP_CONNECT, you were really registering for OP_WRITE, which was ready, so select() got triggered.
You should use OP_CONNECT when you connect to server, not when you are listening for incoming connections. Also make sure to configureBlocking before connect:
Selector selector = Selector.open();
SocketChannel serverChannel = SocketChannel.open(StandardProtocolFamily.INET);
serverChannel.configureBlocking(false);
serverChannel.connect(new InetSocketAddress("localhost", 5454));
serverChannel.register(selector, SelectionKey.OP_CONNECT);
// event process cycle
{
int count = selector.select();
for (SelectionKey key : selector.selectedKeys()) {
log.info(" {}", key.readyOps());
if (key.isConnectable()) {
log.info("Connection is ready");
key.interestOps(SelectionKey.OP_READ);
}
if (key.isReadable()) {
// read data here
}
}