2

For an exercise, we are to implement a server that has a thread that listens for connections, accepts them and throws the socket into a BlockingQueue. A set of worker threads in a pool then goes through the queue and processes the requests coming in through the sockets.

Each client connects to the server, sends a large number of requests (waiting for the response before sending the next request) and eventually disconnects when done.

My current approach is to have each worker thread waiting on the queue, getting a socket, then processing one request, and finally putting the (still open) socket back into the queue before handling another request, potentially from a different client. There are many more clients than there are worker threads, so many connections queue up.

The problem with this approach: A thread will be blocked by a client even if the client doesn't send anything. Possible pseudo-solutions, all not satisfactory:

  • Call available() on the inputStream and put the connection back into the queue if it returns 0. The problem: It's impossible to detect if the client is still connected.
  • As above but use socket.isClosed() or socket.isConnected() to figure out if the client is still connected. The problem: Both methods don't detect a client hangup, as described nicely by EJP in Java socket API: How to tell if a connection has been closed?
  • Probe if the client is still there by reading from or writing to it. The problem: Reading blocks (i.e. back to the original situation where an inactive client blocks the queue) and writing actually sends something to the client, making the tests fail.

Is there a way to solve this problem? I.e. is it possible to distinguish a disconnected client from a passive client without blocking or sending something?

Kalsan
  • 822
  • 1
  • 8
  • 19

4 Answers4

3

Short answer: no. For a longer answer, refer to the one by EJP.

Which is why you probably shouldn't put the socket back on the queue at all, but rather handle all the requests from the socket, then close it. Passing the connection to different worker threads to handle requests separately won't give you any advantage.

If you have badly behaving clients you can use a read timeout on the socket, so reading will block only until the timeout occurs. Then you can close that socket, because your server doesn't have time to cater to clients that don't behave nicely.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
2

Is there a way to solve this problem? I.e. is it possible to distinguish a disconnected client from a passive client without blocking or sending something?

Not really when using blocking IO.

You could look into the non-blocking (NIO) package, which deals with things a little differently.

In essence you have a socket which can be registered with a "selector". If you register sockets for "is data ready to be read" you can then determine which sockets to read from without having to poll individually.

Same sort of thing for writing.

Here is a tutorial on writing NIO servers

ptomli
  • 11,730
  • 4
  • 40
  • 68
  • If I understood correctly the problem has nothing to do with blocking IO, in the question available() is used to solve that. Rather the problem is how to tell whether a client has shut down unexpectedly, or is just being quiet. Using NIO wouldn't help with this I think. – Bjørn Moholt Oct 10 '17 at 12:24
  • I was trying to deal with the larger query `The problem with this approach: A thread will be blocked by a client even if the client doesn't send anything`. Disconnected clients exist in both models, but at least in NIO you're not waiting on them – ptomli Oct 10 '17 at 12:33
  • same goes for using available() no? Still no waiting. – Bjørn Moholt Oct 10 '17 at 12:34
  • Perhaps individually it's a small wait, but it's cumulative (per connection) – ptomli Oct 10 '17 at 12:36
  • I still don't see how there's any waiting, yeah sure you have to call available for each socket to see if there's any data available, but NIO also has to check each socket for data. I will concede that NIO might do this faster, do you have any evidence for that? – Bjørn Moholt Oct 10 '17 at 12:45
  • 1
    Nothing beyond anecdotes from the field. But here's another look at what is essentially the same thing https://stackoverflow.com/a/13712950/134894 – ptomli Oct 10 '17 at 13:01
  • @BjørnMoholt `available()` is a lot less useful than people think. Mainly because the javadoc makes it seem like it's reliable, when it only guarantees one thing: if `available() == n`, you can read `n` bytes without blocking. This does **not** mean the reverse, i.e. if you can read `n` bytes without blocking, `available() == n`. – Kayaman Oct 11 '17 at 05:54
1

Turns out the problem is solvable with a few tricks. After long discussions with several people, I combined their ideas to get the job done in reasonnable time:

  • After creating the socket, configure it such that a blocking read will only block for a certain time, say 100ms: socket.setSoTimeout(100);
  • Additionally, record the timestamp of the last successful read of each connection, e.g. with System.currentTimeMillis()
  • In principle (see below for exception to this principle), run available() on the connection before reading. If this returns 0, put the connection back into the queue since there is nothing to read.
  • Exception to the above principle in which case available() is not used: If the timestamp is too old (say, more than 1 second), use read() to actually block on the connection. This will not take longer than the SoTimeout that you set above for the socket. If you get a TimeoutException, put the connection back into the queue. If you read -1, throw the connection away since it was closed by the remote end.

With this strategy, most read attempts terminate immediately, either returning some data or nothing beause they were skipped since there was nothing available(). If the other end closed its connection, we will detect this within one second since the timestamp of the last successful read is too old. In this case, we perform an actual read that will return -1 and the socket's isClosed() is updated accordingly. And in the case where the socket is still open but the queue is so long that we have more than a second of delay, it takes us aditionally 100ms to find out that the connection is still there but not ready.

EDIT: An enhancement of this is to change "last succesful read" to "last blocking read" and also update the timestamp when getting a TimeoutException.

Kalsan
  • 822
  • 1
  • 8
  • 19
0

No, the only way to discern an inactive client from a client that didn't shut down their socket properly is to send a ping or something to check if they're still there.

Possible solutions I can see is

  • Kick clients that haven't sent anything for a while. You would have to keep track of how long they've been quiet for, and once they reach a limit you assume they've disconnected .
  • Ping the client to see if they're still there. I know you asked for a way to do this without sending anything, but if this is really a problem, i.e you can't use the above solution, this is probably the best way to do it depending on the specifics(since it's an exercise you might have to imagine the specifics).
  • A mix of both, actually this is probably better. Keep track of how long they've been quiet for, after a bit send them a ping to see if they still live.
Bjørn Moholt
  • 468
  • 2
  • 6
  • 21