Here are simplified versions of my socket server and client components.
The primary goal is for the client to detect when the server goes down and for the server to detect when the client goes down.
This works perfectly (on Windows) when either the client or the server are killed (getting IOException "An existing connection was forcibly closed by the remote host").
I would also like to detect when the machine where the client or server is running goes to sleep (or hibernates), eventually using the same mechanism.
Instead, the current behavior is that "the other machine going to sleep" event is not detected, and when the machine is woken up the connection is live again. At this time "the process going down" event is detected as before.
In the case where the client machine goes to sleep, the culprit seems to be "selector.selectedKeys()" not returning a key for the connection to the sleeping machine.
Is this functionality missing in the socket implementation on Windows?
Does anybody have any suggestion on how to fix / go around this issue?
package test;
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 TestServer {
private ByteBuffer _inBuf;
private int _serverPort;
public static void main(String[] args) {
TestServer server = new TestServer(7071);
server.start();
}
public TestServer(int serverPort) {
_serverPort = serverPort;
}
public void start() {
_inBuf = ByteBuffer.allocate(512);
System.out.println("Server starting on port "+_serverPort);
new Thread() {
public void run() {
try {
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(_serverPort));
server.configureBlocking(false);
SelectionKey serverKey = server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {
SelectionKey key = i.next();
i.remove();
if (key == serverKey) {
if (key.isAcceptable()) {
System.out.println("acceptable server key "+Integer.toHexString(key.hashCode()));
try {
SocketChannel client = server.accept();
client.configureBlocking(false);
SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);
System.out.println("registered client key "+Integer.toHexString(clientKey.hashCode()));
} catch (IOException x) {
x.printStackTrace();
}
}
} else {
if (!key.isReadable()) continue;
SocketChannel client = (SocketChannel) key.channel();
System.out.println("reading "+Integer.toHexString(key.hashCode()));
try {
int no = client.read(_inBuf);
if (no<0) throw new IOException("reached end-of-stream"+Integer.toHexString(key.hashCode()));
if (no>0) System.out.println("read "+no+" bytes from "+Integer.toHexString(key.hashCode()));
} catch (IOException x) {
System.out.println(x.getMessage()+" "+Integer.toHexString(key.hashCode()));
key.cancel();
try {
client.close();
} catch (IOException ignore) {
ignore.printStackTrace();
}
continue;
}
_inBuf.flip();
_inBuf.compact();
}
}
}
} catch (Exception x) {
x.printStackTrace();
}
}
}.start();
}
}
and
package test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class TestClient {
private static final int _connectionTimeoutNanos = 10 * 1000000;
private String _serverHost;
private int _serverPort;
private SocketChannel _channel = null;
private ByteBuffer _inBuf;
public static void main(String[] args) {
TestClient client = new TestClient("192.168.1.180", 7071);
client.start();
}
public TestClient(String serverHost, int serverPort) {
_serverHost = serverHost;
_serverPort = serverPort;
}
public void start() {
_inBuf = ByteBuffer.allocate(512);
ClientThread thread = new ClientThread();
thread.start();
}
private class ClientThread extends Thread {
@Override
public void run() {
System.out.println("Client connecting to "+_serverHost+":"+_serverPort);
SocketAddress socketAddress = new InetSocketAddress(_serverHost, _serverPort);
while (true) {
boolean connected = false;
try {
_channel = SocketChannel.open();
_channel.configureBlocking(false);
try {
connected = _channel.connect(socketAddress);
} catch (IOException x) {
try {
_channel.close();
} catch (Throwable suppressed) {
x.addSuppressed(suppressed);
}
throw x;
}
long nanoStart = System.nanoTime();
while (!connected) {
connected = _channel.finishConnect();
if (!connected && (nanoStart+_connectionTimeoutNanos < System.nanoTime())) {
throw new IOException("Non blocking connect failed");
}
}
_channel.socket().setSoLinger(true, 10);
System.out.println("Connected to "+_serverHost+":"+_serverPort);
while (true) {
if (!readFromChannel()) break;
}
System.out.println("Disconnected from "+_serverHost+":"+_serverPort);
} catch (IOException x) {
if (connected) {
System.out.println("Disconnected from "+_serverHost+":"+_serverPort+" "+x.getMessage());
}
}
try {Thread.sleep(1000);} catch (InterruptedException x) {}
}
}
}
public boolean readFromChannel() throws IOException {
int no = _channel.read(_inBuf);
if (no<0) {
return false;
}
if (no>0) System.out.println("read "+no+" bytes from "+_serverHost+":"+_serverPort);
_inBuf.flip();
_inBuf.compact();
return true;
}
}