Selector.select do not block as expected - java

In my current project I notice that select() do not block as expected.
It do not block at all and return always, even when no IO was present. So I got a busy cpu.
The registration will always invoked by another thread, so I need the lock and the wakeup.
The doc says for selectNow():
Invoking this method clears the effect of any previous invocations of the wakeup method.
So I invoke the method at the end of each iteration. no succsess.
I found no example or explanation how to use selectNow for my purpose.
What is wrong with the code?
Here is my example code, so you can test this.
BTW: Another stackoverflow question was the rolemodel of my code.
EDIT: Example fixed! It works now.
import java.io.IOException;
import java.net.*;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable {
ReentrantLock selectorLock = new ReentrantLock();
Selector selector;
boolean alive;
#Override
public void run() {
SelectionKey key;
Iterator<SelectionKey> keys;
alive = true;
try {
while (alive) {
selectorLock.lock();
selectorLock.unlock();
selector.select();
System.out.println("select() returned");
keys = selector.selectedKeys().iterator();
// handle each "event"
while (keys.hasNext()) {
key = keys.next();
// mark as handled
keys.remove();
// handle
handleKey(key);
}
//selector.selectNow(); // don't fix this
}
} catch ( IOException e ) {
e.printStackTrace();
}
}
private void handleKey(SelectionKey key)
throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if (key.isConnectable()) {
System.out.println("connecting");
if ( channel.finishConnect() ) {
key.interestOps(SelectionKey.OP_READ);
} else {
key.cancel();
}
} else if (key.isReadable()) {
System.out.println("reading");
// read and detect remote close
channel.read(ByteBuffer.allocate(64));
}
}
public void register(SelectableChannel channel, int ops, Object attachment)
throws ClosedChannelException {
selectorLock.lock();
try {
System.out.println("wakeup");
selector.wakeup();
channel.register(selector, ops, attachment);
} finally {
selectorLock.unlock();
}
}
public Test()
throws IOException {
selector = Selector.open();
}
public static void main(String[] args)
throws IOException {
Test t = new Test();
new Thread(t).start();
SocketAddress address = new InetSocketAddress("localhost", 8080);
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(address);
t.register(channel, SelectionKey.OP_CONNECT, "test channel attachment");
}
}

Don't register OP_READ until OP_CONNECT has fired and finishConnect() has returned 'true'. At that point you must deregister OP_CONNECT.
Similarly don't register channels for OP_WRITE until you have something to write. OP_WRITE is always ready except when the socket send buffer is full, so it should only be registered after you have detected that condition (write() returns zero), and you should de-register it immediately it fires (unless the condition happens again).
And finally OP_CONNECT and OP_WRITE are the same thing under the hood, which given what I've just said about OP_WRITE explains your selector spins.

Related

NIO selector howto achive active send message without spin

I wrote this NIO server, This will causeing self-spin:
public class ServerTest {
private final Queue<String> queue = new ConcurrentLinkedQueue<>();
#Test
public void server() throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(25780));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (serverSocketChannel.isOpen()) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
iterator.remove();
if (next.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
// In normal server usage server won't write active, They write when read trigger
if (next.isReadable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
socketChannel.read(ByteBuffer.allocate(1024));
socketChannel.write(ByteBuffer.wrap("ECHO!".getBytes(StandardCharsets.UTF_8)));
}
// This causing self-spin and one CPU thread 100%
if (next.isWritable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
while (queue.size() > 0) socketChannel.write(ByteBuffer.wrap(queue.poll().getBytes(StandardCharsets.UTF_8)));
}
}
}
selector.close();
serverSocketChannel.socket().close();
}
/**
* I will call this method when I want send active message from other thread
*/
#Test
public void sendMessage(String message) {
queue.add(message);
}
}
As
Is it possible to keep NIO in OP_WRITE mode without high CPU usage
Avoiding high CPU usage with NIO
those post I find out the OP_WRITE will cause self-spin, I think I need that:
if (next.isWritable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
while (queue.size() > 0) socketChannel.write(ByteBuffer.wrap(queue.poll().getBytes(StandardCharsets.UTF_8)));
next.interestOps(SelectionKey.OP_READ);
// Unregister WRITE when done
}
public void sendMessage(String message) {
queue.add(message);
// How to register interest from other thread ?
// Outside the selector loop I can't even get the "next" instance
}
But I have no idea how to do that, Then I try something absolute wor't work:
private static SelectionKey key;
...
while (iterator.hasNext()) {
...
key = next;
...
public void sendMessage(String message) {
queue.add(message);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// this will cause exception, because KEY is not selected
}
public void sendMessage(String message) {
queue.add(message);
selector.wakeup();
}
selector.selectedKeys(); // It's empty
selector.keys(); // Force get key by this will cause exception same as last one
So how to achive that ? How to interestOps in other thread ?

Java asynchronous client, unexpected behaviour

I opened recently one of my asynchronous socket snippets, and I was amazed by the buggy behavior of the following client code, it is a basic PoC client using AsynchronousSocketChannel.
This snippet ideally, it should never reach the "I am freaking out", but it does.
Basically the problem is I use a ByteBuffer which at the end of the loop I set its position to 0, at the beginning of the loop I expect it to be 0, but SOMETIMES it is not.
The video showing of the bug is:
https://www.youtube.com/watch?v=bV08SjYutRw&feature=youtu.be
I can solve the problem calling .clear() just after .nextLine() but, I feel curious, what is going on in this innocent snippet?
package com.melardev.sockets.clients;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Scanner;
public class AsyncTcpClientCallbacks {
// Horrible demo, I would never write asynchronous sockets this way, I would use attachments
// which allows our code to be cleaner and isolate everything into their own classes
// This is only here to show you how you could do it without attachments, but you have
// to expose the socketChannel so it can be accessible from everywhere, my recommendation is not to bother
// learning, this, go to the demo where I use attachments, it is a lot more readable
static CompletionHandler<Integer, ByteBuffer> readHandler = new CompletionHandler<Integer, ByteBuffer>() {
#Override
public void completed(Integer bytesReaded, ByteBuffer buffer) {
buffer.flip();
byte[] receivedBytes = new byte[buffer.limit()];
// Get into receivedBytes
buffer.get(receivedBytes);
String message = new String(receivedBytes);
System.out.println(message);
buffer.clear();
socketChannel.read(buffer, buffer, this);
}
#Override
public void failed(Throwable exc, ByteBuffer buffer) {
System.err.println("Error reading message");
System.exit(1);
}
};
static private CompletionHandler<Integer, Void> writeHandler = new CompletionHandler<Integer, Void>() {
#Override
public void completed(Integer bytesWritten, Void attachment) {
}
#Override
public void failed(Throwable exc, Void attachment) {
System.err.println("Something went wrong");
System.exit(-1);
}
};
private static AsynchronousSocketChannel socketChannel;
public static void main(String[] args) {
try {
socketChannel = AsynchronousSocketChannel.open();
//try to connect to the server side
socketChannel.connect(new InetSocketAddress("localhost", 3002), null
, new CompletionHandler<Void, Void>() {
#Override
public void completed(Void result, Void attachment) {
ByteBuffer receivedBuffer = ByteBuffer.allocate(1024);
socketChannel.read(receivedBuffer, receivedBuffer, readHandler);
Scanner scanner = new Scanner(System.in);
System.out.println("Write messages to send to server");
ByteBuffer bufferToSend = ByteBuffer.allocate(1024);
String line = "";
while (!line.equals("exit")) {
if (bufferToSend.position() != 0) {
System.err.println("I am freaking out 1");
}
line = scanner.nextLine();
if (bufferToSend.position() != 0) {
System.err.println("I am freaking out 2");
}
byte[] bytesToWrite = line.getBytes();
// bufferToSend.clear();
bufferToSend.put(bytesToWrite);
System.out.println(bufferToSend.limit());
bufferToSend.flip();
System.out.println(bufferToSend.limit());
if (bufferToSend.position() != 0) {
System.err.println("I am freaking out 3");
}
if (bufferToSend.limit() != line.length()) {
System.err.println("I am freaking out 4");
}
socketChannel.write(bufferToSend, null, writeHandler);
bufferToSend.limit(bufferToSend.capacity());
bufferToSend.position(0);
// The two lines above are the same as
// bufferToSend.clear(); // Reuse the same buffer, so set pos=0
// limit to the capacity which is 1024
if (bufferToSend.position() != 0) {
System.err.println("I am freaking out 5");
}
}
}
#Override
public void failed(Throwable exc, Void nothing) {
System.out.println("Error connection to host");
}
});
while (true) {
try {
Thread.sleep(60 * 1000);
// Sleep 1 min ... who cares ?
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
How on earth the "I am freaking out" statements are executed? I see no way it could be, the conditions should never be evaluated to true, I made the video where the bug is clearly shown, sometimes at the beginning of the while() loop the position of the buffer is different than 0, but it should not, because at the end of the loop I set it to 0.
SURPRISINGLY ENOUGH, this behavior DOES NOT occur, when I make a breakpoint before launching the app, and I trace line by line ... how could it be?
I recorded it, I began with tracing through the debugger, everything worked fine, but once I removed the breaking and let the debugger run, the same code that worked before, now it does not. What am I missing?
The video showing when it worked with tracing is here:
https://www.youtube.com/watch?v=0H1OJdZO6AY&feature=youtu.be
If you wanna a server to play with, then this is the one used in the video
package com.melardev.sockets.servers;
import com.melardev.sockets.Constants;
import java.io.IOException;
import java.net.InetAddress;
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 AsyncTcpEchoServerKey {
public static void main(String[] args) {
try {
// Create new selector
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
// By default this is true, so set it to false for nio sockets
serverSocketChannel.configureBlocking(false);
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
// Bind to localhost and specified port
serverSocketChannel.socket().bind(new InetSocketAddress(loopbackAddress, Constants.SOCKET_PORT));
// ServerSocketChannel only supports OP_ACCEPT (see ServerSocketChannel::validOps())
// it makes sense, server can only accept sockets
int operations = SelectionKey.OP_ACCEPT;
serverSocketChannel.register(selector, operations);
while (true) {
if (selector.select() <= 0) {
continue;
}
try {
processReadySet(selector.selectedKeys());
} catch (IOException e) {
continue;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void processReadySet(Set readySet) throws IOException {
Iterator iterator = readySet.iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
// After processing a key, it still persists in the Set, we wanna remove it
// otherwise we will get it back the next time processReadySet is called
// We would end up processing the same "event" as many times this method is called
iterator.remove();
System.out.printf("isAcceptable %b isConnectable %b isReadable %b isWritable %b\n"
, key.isAcceptable(), key.isConnectable(), key.isReadable(), key.isWritable());
if (key.isAcceptable()) {
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
// Get the client socket channel
SocketChannel clientSocketChannel = (SocketChannel) ssChannel.accept();
// Configure it as non-blocking socket
clientSocketChannel.configureBlocking(false);
// Register the socket with the key selector, we want to get notified when we have
// something to read from socket(OP_READ)
clientSocketChannel.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
// A Remote client has send us a message
String message = "nothing";
// Get the socket who sent the message
SocketChannel sender = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesCount = 0;
try {
bytesCount = sender.read(buffer);
if (bytesCount > 0) {
// 1. Get manually
message = new String(buffer.array(), 0, bytesCount);
// 2. Or, use flip
// set buffer.position =0 and buffer.limit = bytesCount
buffer.flip();
byte[] receivedMessageBytes = new byte[bytesCount];
buffer.get(receivedMessageBytes);
message = new String(receivedMessageBytes);
System.out.println("Receive " + message);
// Writing
// 1. Easy approach, create a new ByteBuffer and send it
// ByteBuffer outputBuffer = ByteBuffer.wrap(message.getBytes());
// sender.write(outputBuffer);
// 2. Or to reuse the same buffer we could
// buffer.limit(buffer.position());
// buffer.position(0);
// 3. Or the same as point 2, but one line
buffer.flip();
sender.write(buffer);
} else {
SocketChannel ssChannel = (SocketChannel) key.channel();
ssChannel.close();
System.out.println("Client disconnected");
break;
}
} catch (IOException e) {
e.printStackTrace();
try {
sender.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
}
PD: The first video shows even more than what I have said before, notice that before setting the breaking the console showed I am freaking out 2 and 4, when the breaking was set, I also triggered I am freaking out 1, which at the beginning it wasn't, not only that, but when I resumed the process, this time, I am freaking out 1 was not triggered!!!
The documentation of AsynchronousSocketChannel.write() says:
Buffers are not safe for use by multiple concurrent threads so care should be taken to not access the buffer until the operation has completed.
You don't take such care, rather you set bufferToSend's limit and position regardless of the write completion, so the unexpected behavior may be due to this carelessness.

SelectionKey is not writable

I am curious why the code always tells me "key is not writable"? Is there anything with my code? Every time the socket has read something, I set the key to be interested in OP_WRITE, and test it whether it is writable. However, it always says not writable. I am totally a newbie in Java socket programming.
By the way, I don't close the client.
public final class DateServer {
private DateServer() {
throw new IllegalStateException("Instantiation not allowed");
}
public static void main(final String[] args) throws Exception {
try (final Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open();) {
InetSocketAddress hostAddress = new InetSocketAddress("127.0.0.1", 9999);
serverSocket.bind(hostAddress);
serverSocket.configureBlocking(false);
serverSocket.register(selector, serverSocket.validOps(), null);
while (true) {
int numSelectedKeys = selector.select();
if (numSelectedKeys > 0) {
handleSelectionKeys(selector.selectedKeys(), serverSocket);
}
}
}
}
private static void handleSelectionKeys(Set<SelectionKey> selectionKeys, ServerSocketChannel serverSocket) throws IOException {
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
while (selectionKeyIterator.hasNext()) {
SelectionKey key = selectionKeyIterator.next();
if (key.isAcceptable()) {
acceptClientSocket(key, serverSocket);
} else if (key.isReadable()) {
readRequest(key);
}
selectionKeyIterator.remove();
}
}
private static void acceptClientSocket(SelectionKey key, ServerSocketChannel serverSocket) throws IOException {
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
client.register(key.selector(), SelectionKey.OP_READ);
System.out.println("Accepted connection from client");
}
private static void readRequest(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
System.out.println(String.format("Request data: %s", new String(buffer.array())));
int interestOps = 0;
interestOps |= SelectionKey.OP_WRITE;
key.interestOps(interestOps);
if (key.isWritable()){
System.out.println("key is writable");
} else {
System.out.println("key is not writable");
}
interestOps = 0;
interestOps |= SelectionKey.OP_READ;
key.interestOps(interestOps);
}
}
}
The interestOps only tell the selector what to select for next time. Setting OP_WRITE doesn't magically equip the selection key with the ability to predict the future. You would have to call select() again for OP_WRITE to actually get set as a result of this code.
But you don't need the selector's 'permission' to write to the channel. You just write, and only if the write count is zero do you need to worry about OP_WRITE, as per numerous answers on that topic here.
When you register your client, you only specify SelectionKey.OP_READ, so the channel is never ready for writing. (Look at the isWritable method inside Selectionkey.java)
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
In your case OP_WRITE was never registered and hence it didn't work.
Use this to make it work
client.register(key.selector(), SelectionKey.OP_READ | SelectionKey.OP_WRITE);

Java Selector returns SelectionKey with OP_READ without data in infinity loop after writing to channel

I've trouble with my code: i've written simple SocketChannel client with Selector, after starting it successfully reads messages from server (server sends events). But after writing to socket (see main method) selector starts returning readable socket in infinyty loop, handleKey returns that -1 bytes readed, so selector all time returns OP_READ SelectionKey without data for reading.
Sorry for my English.
Thanks.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class SelectorTest
{
public SelectorTest() throws IOException {
selector = Selector.open();
}
private void runSelector() {
new Thread(new Runnable() {
public void run()
{
alive = true;
try {
while(alive) {
System.out.println("Selector started...");
selector.select();
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while(keyIter.hasNext()) {
SelectionKey key = keyIter.next();
keyIter.remove();
handleKey(key);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
}
private void handleKey(SelectionKey key) throws IOException {
SocketChannel chan = (SocketChannel) key.channel();
System.out.println("Processing selected...");
if(key.isConnectable()) {
System.out.println("Connecting ...");
if(chan.finishConnect()) {
key.interestOps(SelectionKey.OP_READ);
} else {
key.channel();
}
} else if(key.isReadable()) {
System.out.println("Processing reading...");
ByteBuffer buf = ByteBuffer.allocate(1024);
int readedBytes = chan.read(buf);
System.out.println("Readed: " + readedBytes);
buf.flip();
for(byte b : buf.array()) {
System.out.print((char) b);
}
} else if(key.isWritable()) {
System.out.println("Finishing writing...");
key.interestOps(SelectionKey.OP_READ);
}
}
public static void main(String[] args) throws IOException {
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("t1.sis.lan", 6001));
SelectorTest ds = new SelectorTest();
ds.runSelector();
channel.register(ds.selector, SelectionKey.OP_CONNECT);
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for(;;) {
String line = in.readLine();
if(line==null) break;
if(line.toLowerCase().equals("bye")) break;
if (line.toLowerCase().equals("write")) {
String command = "GET_STREAMS\r\n\0";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(command.getBytes());
buf.flip();
channel.write(buf);
}
System.out.println("echo: "+line); // is it alive check
}
ds.alive = false;
ds.selector.wakeup();
channel.close();
}
private Selector selector;
private boolean alive;
}
read() returns -1 at EOS, which you are completely ignoring. When you get EOS, you must either close the channel or at least deregister interest in OP_READ. Otherwise you will just get another OP_READ and another -1 when you read, as you are doing, forever. Contrary to your comments above, read() returns zero on an empty read. You can ignore that, indeed you won't even see it if you only read when isReadable(), unless you read in a loop, but you must not ignore EOS.
read() returns -1 when it has read EOF. Definition:
read() returns: The number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream
This means you should unregister the interest for OP_READ.

How can I interrupt a ServerSocket accept() method?

In my main thread I have a while(listening) loop which calls accept() on my ServerSocket object, then starts a new client thread and adds it to a Collection when a new client is accepted.
I also have an Admin thread which I want to use to issue commands, like 'exit', which will cause all the client threads to be shut down, shut itself down, and shut down the main thread, by turning listening to false.
However, the accept() call in the while(listening) loop blocks, and there doesn't seem to be any way to interrupt it, so the while condition cannot be checked again and the program cannot exit!
Is there a better way to do this? Or some way to interrupt the blocking method?
You can call close() from another thread, and the accept() call will throw a SocketException.
Set timeout on accept(), then the call will timeout the blocking after specified time:
http://docs.oracle.com/javase/7/docs/api/java/net/SocketOptions.html#SO_TIMEOUT
Set a timeout on blocking Socket operations:
ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();
The option must be set prior to entering a blocking operation to take effect. If the timeout expires and the operation would continue to block, java.io.InterruptedIOException is raised. The Socket is not closed in this case.
Is calling close() on the ServerSocket an option?
http://java.sun.com/j2se/6/docs/api/java/net/ServerSocket.html#close%28%29
Closes this socket. Any thread currently blocked in accept() will throw a SocketException.
You can just create "void" socket for break serversocket.accept()
Server side
private static final byte END_WAITING = 66;
private static final byte CONNECT_REQUEST = 1;
while (true) {
Socket clientSock = serverSocket.accept();
int code = clientSock.getInputStream().read();
if (code == END_WAITING
/*&& clientSock.getInetAddress().getHostAddress().equals(myIp)*/) {
// End waiting clients code detected
break;
} else if (code == CONNECT_REQUEST) { // other action
// ...
}
}
Method for break server cycle
void acceptClients() {
try {
Socket s = new Socket(myIp, PORT);
s.getOutputStream().write(END_WAITING);
s.getOutputStream().flush();
s.close();
} catch (IOException e) {
}
}
The reason ServerSocket.close() throws an exception
is because you have an outputstream or an inputstream
attached to that socket.
You can avoid this exception safely by first closing the input and output streams.
Then try closing the ServerSocket.
Here is an example:
void closeServer() throws IOException {
try {
if (outputstream != null)
outputstream.close();
if (inputstream != null)
inputstream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
if (!serversock.isClosed())
serversock.close();
}
}
You can call this method to close any socket from anywhere without getting an exception.
Use serverSocket.setSoTimeout(timeoutInMillis).
OK, I got this working in a way that addresses the OP's question more directly.
Keep reading past the short answer for a Thread example of how I use this.
Short answer:
ServerSocket myServer;
Socket clientSocket;
try {
myServer = new ServerSocket(port)
myServer.setSoTimeout(2000);
//YOU MUST DO THIS ANYTIME TO ASSIGN new ServerSocket() to myServer‼!
clientSocket = myServer.accept();
//In this case, after 2 seconds the below interruption will be thrown
}
catch (java.io.InterruptedIOException e) {
/* This is where you handle the timeout. THIS WILL NOT stop
the running of your code unless you issue a break; so you
can do whatever you need to do here to handle whatever you
want to happen when the timeout occurs.
*/
}
Real world example:
In this example, I have a ServerSocket waiting for a connection inside a Thread. When I close the app, I want to shut down the thread (more specifically, the socket) in a clean manner before I let the app close, so I use the .setSoTimeout() on the ServerSocket then I use the interrupt that is thrown after the timeout to check and see if the parent is trying to shut down the thread. If so, then I set close the socket, then set a flag indicating that the thread is done, then I break out of the Threads loop which returns a null.
package MyServer;
import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
public class Server {
public Server (int port) {this.port = port;}
private boolean threadDone = false;
private boolean threadInterrupted = false;
private boolean threadRunning = false;
private ServerSocket myServer = null;
private Socket clientSocket = null;
private Thread serverThread = null;;
private int port;
private static final int SO_TIMEOUT = 5000; //5 seconds
public void startServer() {
if (!threadRunning) {
serverThread = new Thread(thisServerTask);
serverThread.setDaemon(true);
serverThread.start();
}
}
public void stopServer() {
if (threadRunning) {
threadInterrupted = true;
while (!threadDone) {
//We are just waiting for the timeout to exception happen
}
if (threadDone) {threadRunning = false;}
}
}
public boolean isRunning() {return threadRunning;}
private Task<Void> thisServerTask = new Task <Void>() {
#Override public Void call() throws InterruptedException {
threadRunning = true;
try {
myServer = new ServerSocket(port);
myServer.setSoTimeout(SO_TIMEOUT);
clientSocket = new Socket();
} catch (IOException e) {
e.printStackTrace();
}
while(true) {
try {
clientSocket = myServer.accept();
}
catch (java.io.InterruptedIOException e) {
if (threadInterrupted) {
try { clientSocket.close(); } //This is the clean exit I'm after.
catch (IOException e1) { e1.printStackTrace(); }
threadDone = true;
break;
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
};
}
Then, in my Controller class ... (I will only show relevant code, massage it into your own code as needed)
public class Controller {
Server server = null;
private static final int port = 10000;
private void stopTheServer() {
server.stopServer();
while (server.isRunning() {
//We just wait for the server service to stop.
}
}
#FXML private void initialize() {
Platform.runLater(()-> {
server = new Server(port);
server.startServer();
Stage stage = (Stage) serverStatusLabel.getScene().getWindow();
stage.setOnCloseRequest(event->stopTheServer());
});
}
}
I hope this helps someone down the road.
Another thing you can try which is cleaner, is to check a flag in the accept loop, and then when your admin thread wants to kill the thread blocking on the accept, set the flag (make it thread safe) and then make a client socket connection to the listening socket.
The accept will stop blocking and return the new socket.
You can work out some simple protocol thing telling the listening thread to exit the thread cleanly.
And then close the socket on the client side.
No exceptions, much cleaner.
You can simply pass the timeout limit (milli seconds) as a parameter while calling accept function.
eg serverSocket.accept(1000);
automatically close the request after 1 sec

Categories

Resources