0

I have a java application which manages several socket connections to devices. I have no control over the protocol which these devices implement, and now I want my java application to send heartbeats for each device. The devices do not send data, but only respond to commands.

The javadoc for InputStream.read() states that if the end of stream is reached, it will return -1. So that seems like a reasonable way to check if the connection is open. But when I implement this solution, there are no bytes available (since the device only responds to commands), and since the connection is open, it will hang at the read call forever. Example, I peek at one byte and if that would be -1 the heartbeat would be "unhealthy":

public static void main(final String[] args) throws IOException {
    try (Socket socket = new Socket()) {
        socket.connect(new InetSocketAddress("192.168.30.99", 25901), 1000);
        System.out.println("Connected");

        final BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        bis.mark(1);
        System.out.println(bis.read()); // Stalls forever here
        bis.reset();
        System.out.println("Done");
    }
}

Is it reasonable to say that, if no byte is received within x milliseconds, the device is connected?

Is there any surefire way to check socket connectivity without heartbeats where the ip and port is important?

user51
  • 8,843
  • 21
  • 79
  • 158
jokarl
  • 1,913
  • 2
  • 24
  • 52
  • End of stream means the connection is closed, your `mark/reset` code wouldn't work. Is there a reason you need to try to implement a heartbeat (which the devices neither require or support) instead of handling the situation when you try to send a command to the device? – Kayaman Sep 03 '19 at 12:43
  • The java application exposes a REST API that represents socket operations. Each operation on a device responds either with OK or NOK. If I write trash to the socket to verify connectivity, the next REST invoked operation might receive a NOK as a response because of my trash writing. Basically, I want to continuously verify connectivity even if an operation is not executed, that way we will be able to react to scenarios before it's a problem (ideally). – jokarl Sep 03 '19 at 12:47

3 Answers3

1

Is there any surefire way to check socket connectivity without heartbeats where the ip and port is important?

No, you can't reliably know if the other end is alive unless you try to communicate with it.

If the other end doesn't have a no-op ping function, you're pretty much out of luck. Waiting in a blocking read() call won't help you if the connection gets cut off.

Is it reasonable to say that, if no byte is received within x milliseconds, the device is connected?

No. It means that the device hasn't sent anything in x milliseconds. Which is normal, as it only responds to commands.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Unfortunately no no-op ping, it will always have a side effect that might be detrimental. Thank you – jokarl Sep 03 '19 at 13:50
1

when the other end of socket do not write any byte and wait to read from socket first, blocking on read is the default behavior.
with no control over the protocol , little can be done.

it is reasonable to say, successful connect is a weaker heartbeat.
you don't have to wait for x miliseconds which makes no difference on such protocol

another tricky way , you can try to send a few bytes that most unlikely being a valid command,
for example the '\0' or '\n' ,
hoping that it will do no harm to the device and the device can close socket actively on such invalid command.
when the other end closes socket actively , read call on such socket should return -1

the better heartbeat way always have something to do with the protocol,
as the no-op ping command suggested by @Kayaman

James Li
  • 469
  • 3
  • 7
  • Thanks for the tip, unfortunately sending `\0` or `\n` results in an operation from the device which might have side effects. – jokarl Sep 03 '19 at 13:54
0

Maybe TCP level keep-alive is solution for you:

You can turn it on by using command:

   socket.setKeepAlive(true);

It sets SO_KEEPALIVE socket option. Quote from SocketOptions java-API:

When the keepalive option is set for a TCP socket and no data has been exchanged across the socket in either direction for 2 hours (NOTE: the actual value is implementation dependent), TCP automatically sends a keepalive probe to the peer. This probe is a TCP segment to which the peer must respond. One of three responses is expected: 1. The peer responds with the expected ACK. The application is not notified (since everything is OK). TCP will send another probe following another 2 hours of inactivity. 2. The peer responds with an RST, which tells the local TCP that the peer host has crashed and rebooted. The socket is closed. 3. There is no response from the peer. The socket is closed. The purpose of this option is to detect if the peer host crashes. Valid only for TCP socket: SocketImpl

You could also use SO_TIMEOUT by using:

   socket.setSoTimeout(timeout);

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.

Call those right after connect() or accept() calls, before the program enters to 'no control of underlying protocl' -state.

SKi
  • 8,007
  • 2
  • 26
  • 57
  • The read timeout won't really help, because there's nothing to read if you don't write to the endpoint (so it would read timeout constantly, and that wouldn't mean anything). Socket keep-alive is theoretically possible, but you'd need to change the [default values](https://stackoverflow.com/questions/1480236/does-a-tcp-socket-connection-have-a-keep-alive) to a lot smaller. – Kayaman Sep 04 '19 at 05:22