33

I'm studying the following basic Java socket code( source ). It's a Knock-Knock-Joke client/server app.

In the Client, we set up the socket as usual:

try {
  kkSocket = new Socket("localhost", 4444);
  out = new PrintWriter(kkSocket.getOutputStream(), true);
  in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
} catch( UnknownHostException uhe ){ /*...more error catching */

And then later, we just read and write to Server:

BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String fromServer;
String fromUser;

while ((fromServer = in.readLine()) != null) {
  System.out.println("Server: " + fromServer);
  if (fromServer.equals("bye."))
      break;

  fromUser = stdIn.readLine();

  if (fromUser != null){
      System.out.println("Client: " + fromUser);
      out.println(fromUser);
  }

And on the server, we have the corresponding code, to get the joke punch-line.

    KnockKnockProtocol kkp = new KnockKnockProtocol();

    outputLine = kkp.processInput(null);
    out.println(outputLine);

    while ((inputLine = in.readLine()) != null) {
         outputLine = kkp.processInput(inputLine);
         out.println(outputLine);
         if (outputLine.equals("Bye."))
            break;

I want to attach a heartbeat to the whole thing, which will print out to the console whenever it detects that the other side died. Because what happens now if I kill the other side is an exception - like this one below:

enter image description here

So if I am running both KnockKnockClient and KnockKnockServer, then I shut down KnockKnockServer, what should happen is that on the Client I see this outputted:

>The system has detected that KnockKnockServer was aborted

I'm looking for any tips. So far I've mainly been trying to run a daemon thread that periodially creates new connections to the other side. But I'm confused about what condition to check for(but I think it's just a boolean value?). Is that the right approach? I just found out online there's a library called JGroups for multicast networking - would that be a better way? I'm looking for any tips.

My server-code so far(sorry it's messy)

&

Client-side

thanks

Caffeinated
  • 11,982
  • 40
  • 122
  • 216
  • 1
    A real-life solution would use separate threads for reading, writing and user-input since all of those are blocking, that is they will wait until they've received input before they continue. That said there's a minimal solution to your particular program if you're interested. – Nuoji Apr 02 '13 at 22:09
  • @Nuoji - Understood, Thanks very much . By minimal I'm guessing you mean a little bit hacky ? well I'm interested! – Caffeinated Apr 02 '13 at 23:45
  • @Nuoji - so when you say `real-life solution would use separate threads for reading, writing and user-input since all of those are blocking` , do you mean it will probably be a lot of added code (i.e something like 6 extra threads? ) – Caffeinated Apr 04 '13 at 00:34
  • 2
    A real life solution with asynchronous communication would use 3 different threads: One for user input, one for server input and one for server output. (So you would create 2 extra threads). However, if you are doing communication where the protocol is synchronous (the protocol is request-response based), you can often get away with not creating any extra threads at all or at the most one extra. It all depends on how it's used. – Nuoji Apr 04 '13 at 07:33

8 Answers8

16

But the exception you are getting is exactly this! It's telling you that the other side just died. Just catch the exception and print to the console, that "The system has detected that KnockKnockServer was aborted".

You are using TCP connection and TCP has built-in heartbeat (keepalive) mechanism that will do this for you. Just set setKeepAlive() on the socket. That being said - It is possible to control keepalive frequency per each connection, but I do not know how to do that in java.

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

https://stackoverflow.com/a/1480259/706650

mabn
  • 2,473
  • 1
  • 26
  • 47
  • 1
    Thanks, so you mean I can just add a `catch (SocketException e1) { /* handle_here */ }` part – Caffeinated Apr 06 '13 at 17:53
  • 2
    If the server is closed "greacefully" - yes - tcp connection will terminate (http://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_termination). If the connection is interrupted in some other way (someone cut off power to the server, network died, etc) - you'll need to use/tune tcp keepalive or send hearthbeats from your application, as suggested by other answers. – mabn Apr 08 '13 at 12:10
  • Using the built in TCP keepalive is not the best idea. – William Morrison Aug 11 '13 at 19:30
12

you have a Synchronous communication. for having the heartbeat message, use an asynchronous communication. there will be 2 threads. one will read from the socket and another will keep writing to the socket. If you use asynchronous communication, the server will be sending a message every 10 seconds. the client thread will be reading messages from the server and if there is no message, it means the server is down. in your case, the server either sends back the message to client(if client has some message) or send an automatic reply.your server code can be modified like this.

  1. Create a server thread that will keep sending messages to client every 10 seconds.

    public class receiver extends Thread{
    
      public static bool hearbeatmessage=true;
    
      Socket clientSocket=new Socket();
      PrintWriter out=new PrintWriter();
      public receiver(Socket clientsocket){
      clientSocket=clientsocket;
      out = new PrintWriter(clientSocket.getOutputStream(), true);
    }
    
      public void run(){
    
        while(true)
        {
    
          if(heartbeatmessage){
            thread.sleep(10000);
            out.println("heartbeat");
    
          }
        }            
      }
    }
    

In your server code:

KnockKnockProtocol kkp = new KnockKnockProtocol();

outputLine = kkp.processInput(null);
out.println(outputLine);
receiver r=new reciver(clientSocket);
r.run(); /*it will start sending hearbeat messages to clients */

while ((inputLine = in.readLine()) != null) {
     outputLine = kkp.processInput(inputLine);
     reciver.hearbeatMessage=false; /* since you are going to send a message to client now, sending the heartbeat message is not necessary */
     out.println(outputLine);
     reciver.hearbeatMessage=true; /*start the loop again*/
     if (outputLine.equals("Bye."))
        break;

The client code will also be modified, a thread will keep reading messages from the socket and if it has not received message for more than 11 seconds(1 second extra), it will declare the server is not available.

Hope this helps. There might be some flaw in the logic too. Let me know.

ericson
  • 1,658
  • 12
  • 20
Bala
  • 1,129
  • 1
  • 9
  • 23
  • 1
    Hi Bala , for some reason I can't get this working yet.. is the client code part the one shown at the bottom? or do I need to do that( i would like to learn it so i'm notr asking for code ) – Caffeinated Apr 01 '13 at 01:18
  • 2
    There are problems with this implementation. The most serious is that receiver creates a separate PrintWriter. This will mean that the "heartbeat" text may be interspersed with the reply from the server. You might get something like "Knockhe, aknorckt!" followed by "beat" from the server! The correct way to implement heartbeat would involve queuing all output on a single thread (although in this particular case it's possible to simply share PrintWriter). – Nuoji Apr 04 '13 at 21:08
10

The following are best practices which we apply on a daily base when interfacing with hardware (using sockets).

Good practice 1 : SoTimeout

This property enables a read timeout. The goal of this is to avoid the issue that Tom had. He wrote something in the line of : "you will need to wait till the next client message arrives". Well, this offers a solution to that problem. And it's also the key to implementing a heartbeat and many other checks.

By default, the InputStream#read() method will wait forever, until a message arrives. The setSoTimeout(int timeout) changes this behaviour. It will apply a timeout now. When it timeouts it will throw the SocketTimeoutException. Just catch the exception, check a couple of things and continue reading (repeat). So basically, you put your reading method in a loop (and probably even in a dedicated thread).

// example: wait for 200 ms
connection.setSoTimeout(200);

You can use these interruptions (caused by the timeout) to validate the status: E.g. how long has it been since I received my last message.

Here is an example to implement the loop:

while (active)
{
  try
  {
    // some function that parses the message
    // this method uses the InputStream#read() method internally.
    code = readData();

    if (code == null) continue; 
    lastRead = System.currentTimeMillis();

    // the heartbeat message itself should be ignored, has no functional meaning.
    if (MSG_HEARTBEAT.equals(code)) continue;

    //TODO FORWARD MESSAGE TO ACTION LISTENERS

  }
  catch (SocketTimeoutException ste)
  {
    // in a typical situation the soTimeout should be about 200ms
    // the heartbeat interval is usually a couple of seconds.
    // and the heartbeat timeout interval a couple of seconds more.
    if ((heartbeatTimeoutInterval > 0) &&
        ((System.currentTimeMillis() - lastRead) > heartbeatTimeoutInterval))
    {
      // no reply to heartbeat received.
      // end the loop and perform a reconnect.
      break;
    }
    // simple read timeout
  }
}

Another use of this timeout: It can be used to cleanly stop your session by setting active = false. Use the timeout to check if this field is true. If that's the case, then break the loop. Without the SoTimeout logic this would not be possible. You would either be forced to do a socket.close() or to wait for the next client message (which clearly makes no sense).

Good practice 2 : Built-in Keep-Alive

connection.setKeepAlive(true);

Well basically this is pretty much what your heart-beat logic does. It automatically sends a signal after a period of inactivity and checks for a reply. The keep-alive interval is operating system dependent though, and has some shortcomings.

Good practice 3 : Tcp No-Delay

Use the following setting when you are often interfacing small commands that need to be handled quickly.

try
{
  connection.setTcpNoDelay(true);
}
catch (SocketException e)
{
}
bvdb
  • 22,839
  • 10
  • 110
  • 123
  • I know this is an old question, but it's the best answer I've found so far. So if I can ask you, what about in situations where the heartbeat should occur over an extended period of time, say once every 5 to 10 minutes. Is good practice one applicable then or should I look for some other method? – mal Jun 11 '14 at 18:48
  • 1
    Sure, you can use "Good practice 1". You simply need to set the heartbeatTimeoutInterval variable to about 20 minutes (=2*10). (Just for clarity: I usually combine all 3 of these "good practices".) – bvdb Jun 12 '14 at 17:51
  • Great! Thanks, I'll see if I can implement all three before my deadline! Edit: of course I can. – mal Jun 13 '14 at 07:36
  • 2
    It's been about a year. But I took the time to cleanup (and rephrase) some things. Just to improve readability. – bvdb Aug 28 '15 at 13:10
4

I think you are over complicating things.

From the client side:
If the client gets an IOException for the connection reset, then this means the server is dead. Instead of printing the stack trace just do what ever you need to do once you know that the server is down. You already know the server is down due to the exception.

From the server side:
Either start a timer and if you don't get a request for a time more than the interval assume that the client is down.
OR start a background server thread at the client (making the client and server peers) and have the server send a "dummy" hearbeat request (server now acts as a client). If you get exception the client is down.

Cratylus
  • 52,998
  • 69
  • 209
  • 339
3

Figured I'd take a crack at this... I started with the KnockKnockServer and KnockKnockClient that are on the Java site (in your original question).

I didn't add any threading, or heartbeats; I simply changed the KnockKnockClient to the following:

    try {    // added try-catch-finally block
      while ((fromServer = in.readLine()) != null) {
        System.out.println("Server: " + fromServer);
        if (fromServer.equals("Bye."))
          break;

        fromUser = stdIn.readLine();
        if (fromUser != null) {
          System.out.println("Client: " + fromUser);
          out.println(fromUser);
        }
      }
    } catch (java.net.SocketException e) {   // catch java.net.SocketException
      // print the message you were looking for
      System.out.println("The system has detected that KnockKnockServer was aborted");
    } finally {
      // this code will be executed if a different exception is thrown,
      // or if everything goes as planned (ensure no resource leaks)
      out.close();
      in.close();
      stdIn.close();
      kkSocket.close();
    }

This seems to do what you want (even though I modified the original Java website example, rather than your code - hopefully you'll be able to see where it plugs in). I tested it with the case you described (shut down the server while the client is connected).

The downside to this is that, while the client is waiting for user input, you don't see that the server has died; you have to enter client input, and then you'll see that the server has died. If this is not the behavior you want, please post a comment (perhaps that was the whole point of the question - it just seemed like you might have been going down a longer road than you needed in order to get to where you wanted to be).

Tom
  • 2,369
  • 13
  • 21
  • Thank You Very Much Tom! This works great! Out of curiosity, would it be overly complex to get instant output once the server dies? thanks! – Caffeinated Apr 29 '13 at 04:22
  • 1
    Hi! Sorry, was on vacation for a bit; just catching up. I think that, to get instant output, you would need multiple threads (as some of the other responses indicated) - this is because, in the single-threaded design, the `stdIn.readLine()` blocks until the user enters input; while the thread is blocked, it can't do anything else (include find out the server has died). – Tom May 17 '13 at 23:30
  • You need to set an `soTimeout` and put your reading in a loop in a dedicated thread. (see my answer) – bvdb Jul 28 '15 at 09:50
2

Here's a slight modification to the client. It doesn't use an explicit heartbeat, but as long as you keep reading from the server, you'll immediately detect the disconnect anyway.

This is because readLine will immediately detect any read errors.

// I'm using an anonymous class here, so we need 
// to have the reader final.
final BufferedReader reader = in;

// Decouple reads from user input using a separate thread:
new Thread()
{
   public void run()
   {
      try
      {
         String fromServer;
         while ((fromServer = reader.readLine()) != null)
         {
            System.out.println("Server: " + fromServer);
            if (fromServer.equals("Bye."))
            {
                System.exit(0);
            }
         }
      }
      catch (IOException e) {}

      // When we get an exception or readLine returns null, 
      // that will be because the server disconnected or 
      // because we did. The line-break makes output look better if we 
      // were in the middle of writing something.
      System.out.println("\nServer disconnected.");
      System.exit(0);
   }
}.start();

// Now we can just read from user input and send to server independently:
while (true)
{
   String fromUser = stdIn.readLine();
   if (fromUser != null)
   {
      System.out.println("Client: " + fromUser);
      out.println(fromUser);
   }
}

In this case, we allow client writes even when we're waiting for reply from the server. For a more stable application, we'd want to lock the input while we're waiting for a reply by adding a semaphore controlling when we start reading.

These are the modifications we would make to control the input:

final BufferedReader reader = in;

// Set up a shared semaphore to control client input.
final Semaphore semaphore = new Semaphore(1);

// Remove the first permit.
semaphore.acquireUninterruptibly();

new Thread()

... code omitted ...

           System.out.println("Server: " + fromServer);
           // Release the current permit.
           semaphore.release();
           if (fromServer.equals("Bye."))

... code omitted ...

while (true)
{
    semaphore.acquireUninterruptibly();
    String fromUser = stdIn.readLine();

... rest of the code as in the original ...
Nuoji
  • 3,438
  • 2
  • 21
  • 35
  • 1
    So just to understand - this code will allow the Client to see when the Server gets killed correct? And for the reverse of that, it would generally be the same way(i.e using Semaphores?) ? thanks! – Caffeinated Apr 03 '13 at 20:16
  • 1
    If we're talking about the KnockKnock sample code, then the server doesn't need to change at all (from the version in the sample code), since it's blocking at in.readLine. When the client disconnects, in.readLine will return null, and the server code will close the connection. The reason the client did not detect server disconnect was that it wasn't running in.readLine when taking input from the user. Basically all my modification does is to move in.readLine to a separate thread, so we always detect disconnects. – Nuoji Apr 04 '13 at 07:26
1

I think @Bala's answer is correct on server side. I'd like to give a supplementary on client side.

On client side, you should:

  1. use an variable to keep the timestamp of the last message from server;
  2. start a thread which runs periodically(every 1 second, e.g.) to compare current timestamp and the last message timestamp, if it is longer than desired timeout(10 seconds, e.g.), a disconnection should be reported.

Following are some code snippet:

The TimeoutChecker class(thread):

static class TimeoutChecker implements Runnable {

    // timeout is set to 10 seconds
    final long    timeout = TimeUnit.SECONDS.toMillis(10);
    // note the use of volatile to make sure the update to this variable thread-safe
    volatile long lastMessageTimestamp;

    public TimeoutChecker(long ts) {
        this.lastMessageTimestamp = ts;
    }

    @Override
    public void run() {
        if ((System.currentTimeMillis() - lastMessageTimestamp) > timeout) {
            System.out.println("timeout!");
        }
    }
}

Start the TimeoutChecker after connection is established:

try {
  kkSocket = new Socket("localhost", 4444);
  // create TimeoutChecker with current timestamp.
  TimeoutChecker checker = new TimeoutChecker(System.currentTimeMillis());
  // schedule the task to run on every 1 second.
  ses.scheduleAtFixedRate(, 1, 1,
            TimeUnit.SECONDS);
  out = new PrintWriter(kkSocket.getOutputStream(), true);
  in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
} catch( UnknownHostException uhe ){ /*...more error catching */

The ses is a ScheduledExecutorService:

ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

And remember to update the timestamp when receiving messages from server:

BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String fromServer;
String fromUser;

while ((fromServer = in.readLine()) != null) {
  // update the message timestamp
  checker.lastMessageTimestamp = System.currentTimeMillis();
  System.out.println("Server: " + fromServer);
  if (fromServer.equals("bye."))
    break;
ericson
  • 1,658
  • 12
  • 20
  • thanks a lot Ericson. so, to incorporate this code into mine i'm having a few issues. but i think i can figur it out. tyvm – Caffeinated Apr 04 '13 at 16:46
  • 2
    Sorry, but this is severe overdesign. Simply use Socket#setSoTimeout(int timeout). This will cause the read to fail after waiting ms. Also, readLine will detect disconnects automatically. – Nuoji Apr 04 '13 at 21:12
  • @Nuoji I think he is looking for a way to implement heartbeat mechanism himself. Yes, you can rely on the underlying TCP protocol to do the job, but I think it is important to be aware of the alternatives, especially for a starter. – ericson Apr 05 '13 at 03:28
  • @ericson the things is that your solution will not work on the client. If the player does not write any input for a long time, we don't get to try to read from the server, and consequently lastMessageTimestamp will not be updated. In other words, the timeout will be triggered by client inactivity instead of lack of server heartbeat! `checker.lastMessageTimestamp = System.currentTimeMillis();` is also dubious design. Much better `checker.notifyMessageReceived()` and let "TimeoutChecker" handle it internally. – Nuoji Apr 05 '13 at 07:35
  • 1
    @Nuoji thanks for the correction, but I was giving some code snippets to setup an example, not a full solution. I hope Adel would find the first issue while integrating the code to his application. And I agree on the encapsulation of `lastMessageTimestamp`, the direct assignment is for simplicity in an example. – ericson Apr 05 '13 at 15:29
  • 1
    @ericson but the main issue with setting up the client side disconnect detection *is* due to the readLine so it is problematic that your solution does not deal with that. – Nuoji Apr 05 '13 at 19:23
1

Adel,was looking at your code http://pastebin.com/53vYaECK

Can you try the following solution. not sure whether it will work. instead of creating a bufferedreader with the inputstream once, we can create an instance of BufferedReader eachtime. when the kkSocket.getInputStream is null, it comes out of the while loop and set completeLoop to false, so that we exit the while loop. it has 2 while loops and the objects are created each time. if the connection is open but does not have data in it inputstream will not be null, BufferedReader.readLine would be null.

bool completeLoop=true;
while(completeLoop) {

while((inputstream is=kkSocket.getInputStream())!=null) /*if this is null it means the socket is closed*/
{  
BufferedReader in = new BufferedReader( new InputStreamReader(is));
while ((fromServer = in.readLine()) != null) {
            System.out.println("Server: " + fromServer);
            if (fromServer.equals("Bye."))
                break;
            fromUser = stdIn.readLine();
        if (fromUser != null) {
                System.out.println("Client: " + fromUser);
                out.println(fromUser);
           }
        } 
}
completeLoop=false;
System.out.println('The connection is closed');
}
Bala
  • 1,129
  • 1
  • 9
  • 23
  • Hey , I think we're onto something! When I run it I now get this error message onto server side - `java.io.IOException: Stream closed` , and on client side it works then after sending the 2nd joke-response I see `java.net.SocketException: Software caused connection abort: recv failed` - interesting, i'll keep at it! – Caffeinated Apr 09 '13 at 13:46
  • 1
    Hey Adel, did that work? will try implementing it when I get time. – Bala Apr 09 '13 at 17:24
  • 1
    Adel, If its not working, I dont think I deserve the 500 bounty. :) – Bala Apr 09 '13 at 22:07
  • is there an option to take the bounty back? pls do so. – Bala Apr 10 '13 at 03:38
  • but really, its fine Bala - you helped me! i don't want to be a trouble... and i have enough rep – Caffeinated Apr 10 '13 at 03:47
  • Doesn't this leaves `InputStream` opened? – Christopher Francisco Jun 04 '14 at 14:49