Avoid TIME_WAIT state, server side: wait for client to close connection.
authorgu.martinm@gmail.com <gu.martinm@gmail.com>
Sun, 2 Feb 2014 20:49:14 +0000 (21:49 +0100)
committergu.martinm@gmail.com <gu.martinm@gmail.com>
Sun, 2 Feb 2014 20:49:14 +0000 (21:49 +0100)
See:
http://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html
http://stackoverflow.com/questions/3757289/tcp-option-so-linger-zero-when-its-required

By the way, never use SO_LINGER. The application level protocol should be implemented
in such way that allows the client side to find out when server side ended up sending
data and in this way the client is able to close the connectiond and server side does not
finish with sockets in TIME_WAIT state.

Daemon/javafork.c
JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java
JavaExample/pom.xml

index 63f1f25..908654a 100644 (file)
@@ -309,7 +309,7 @@ void *serverThread (void * arg)
             /*          JAVA CLIENT: ------------ COMMAND_LENGTH -------> :SERVER                   */
             /*          JAVA CLIENT: -------------- COMMAND ------------> :SERVER                   */
             /*          JAVA CLIENT: <-------------- RESULTS ------------ :SERVER                   */
-            /*          JAVA CLIENT: <---------- CLOSE CONNECTION ------- :SERVER                   */
+            /*          JAVA CLIENT: ----------- CLOSE CONNECTION ------> :SERVER                   */
             /*                                                                                      */
             /****************************************************************************************/
 
@@ -346,10 +346,23 @@ void *serverThread (void * arg)
 
 
     /*3. RESULTS*/     
-    pre_fork_system(socket, command);
-
+    if (pre_fork_system(socket, command) < 0)
+        goto err;
 
-    /*4. CLOSE CONNECTION AND FINISH*/
+    /*4. WAIT FOR CLIENT TO CLOSE CONNECTION AND FINISH*/
+    // We may avoid the TIME_WAIT state in the server side if we always wait for the client to close the connection.
+    // Never use SO_LINGER!!! The client should be able to know when there are no more data. The protocol
+    // (application level) should notify the client when the server ended up sending data and in this
+    // way the client is able to close the connection.
+    // See:
+    // http://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
+    // http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html
+    // http://stackoverflow.com/questions/3757289/tcp-option-so-linger-zero-when-its-required
+    bzero(buffer, sizeof(buffer));
+    // TODO: I just want to wait until the socket is closed (or there is a timeout).
+    // Should I use another method for that instead of this one? :/ This code does not look clear to me... There must be something better...
+    if (receive_from_socket (socket, buffer, sizeof(uint32_t), timeout, utimeout) < 0)
+        goto err;
 
 err:
     free(command);
index aacaa49..0a93bb0 100644 (file)
@@ -2,7 +2,6 @@ package de.fork.java;
 
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
-import java.io.EOFException;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -43,6 +42,8 @@ public class TCPForkDaemon {
        private String streamStderr;
        //Error by default.
        private int results = -1;
+       int type;
+        int length;
        
        
        /**
@@ -102,7 +103,7 @@ public class TCPForkDaemon {
        /*              JAVA CLIENT: ------------ COMMAND_LENGTH -------> :SERVER                 */
        /*              JAVA CLIENT: -------------- COMMAND ------------> :SERVER                 */
        /*              JAVA CLIENT: <-------------- RESULTS ------------ :SERVER                 */
-       /*              JAVA CLIENT: <---------- CLOSE CONNECTION ------- :SERVER                 */
+       /*              JAVA CLIENT: ----------- CLOSE CONNECTION ------> :SERVER                 */
        /*                                                                                        */
        /******************************************************************************************/
                
@@ -162,9 +163,10 @@ public class TCPForkDaemon {
                        //sends response to Java client and here we know the character set encoding is the defined for TCPForkDaemon protocol 
                        //(for example UTF-8) In this way we do not need to know about the remote encoding and everything could run automatically.              
                        receiveData = new DataInputStream(socket.getInputStream());
-                       while (true) {
-                               int type = receiveData.readInt();
-                               int length = receiveData.readInt();
+                       this.type = receiveData.readInt();
+                        this.length = receiveData.readInt();
+                       while (this.type != 3) {
+
                                switch (type) {
                                case 0:
                                        //STDIN not implemented
@@ -181,18 +183,14 @@ public class TCPForkDaemon {
                                        receiveData.readFully(dataStderr, 0, length);
                                        stderr.write(dataStderr, 0, length);
                                        break;
-                               case 3:
-                                       //RESULTS
-                                       results = length;
-                                       break;
                                default:
                                        throw new IOException("Unrecognized type.");
                                                
                                }
+
+                               this.type = receiveData.readInt();
+                               this.length = receiveData.readInt();
                        }       
-               } catch (EOFException e) {
-                       // 4. SERVER CLOSED CONNECTION
-                       //TODO: Java NIO with select, and stop using this crappy code.
                }
                finally {
                        if (lengthAndCommand != null) {
@@ -207,14 +205,17 @@ public class TCPForkDaemon {
                                socket.close();
                        }
                        
-                       //The remote charset. 
-                       //TODO: my own protocol as above specified.
-                       this.streamStderr = new String(stderr.toByteArray(), "UTF-8");
-                       this.streamStdout = new String(stdout.toByteArray(), "UTF-8");
                }
-               
+
+               //RESULTS
+               //The remote charset.
+                //TODO: my own protocol as above specified.
+                this.streamStderr = new String(stderr.toByteArray(), "UTF-8");
+                this.streamStdout = new String(stdout.toByteArray(), "UTF-8");
+
                //If everything went all right we should be able to retrieve the return 
                //status of the remotely executed command.
+                this.results = this.length;
                return this.results;
                
        }
index aaf5867..e0813e5 100644 (file)
        </scm>
        <dependencies>
                <dependency>
-                       <groupId>com.sun.jdmk</groupId>
-                       <artifactId>jmxtools</artifactId>
-                       <version>1.2.1</version>
-               </dependency>
-               <dependency>
                        <groupId>javax.activation</groupId>
                        <artifactId>activation</artifactId>
                        <version>1.1</version>