Search code examples
javasocketsinputstream

Why does the InputStream of a socket returns -1 only when the socket is closed?


I have read on some question of StackOverflow that we usually use the condition (c = inputStream.read()) == -1) to detect if "end of stream" is reached. However, I've also read that the OutputStream of a socket only sends an "end of stream" message if the socket is closed.

This makes this server code...

public class Server {
    public static int port = 27018;

    public static void main(String[] args) throws IOException, InterruptedException {
        char c = ' ';

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server started");
        Socket connectionSocket = serverSocket.accept();
        serverSocket.close();
        String bufferOuputString = "";

        InputStream in = connectionSocket.getInputStream();
        OutputStream out = connectionSocket.getOutputStream();

        System.out.println("Connection established on port " + port);
        out.write("Welcome to my server !".getBytes());


        System.out.println("Waiting for client messages");
        while(c != '/'){
            c = (char) in.read();
            if(c == '*'){
                System.out.println(bufferOuputString);
                bufferOuputString = "";
            } else {
                bufferOuputString += c;
            }
        }

        connectionSocket.close();
    }
}

...alongside this client code...

public class Client {
    public static int port = 27018;

    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("127.0.0.1", port)) {
            int c;
            Scanner sc = new Scanner(System.in);
            String bufferInputString = "";
            String bufferString = "";

            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();

            while((c = in.read()) != -1){
                bufferInputString += (char) c;
            }
            System.out.println(bufferInputString);

            while(!bufferString.contains("/")){
                System.out.println("You can write messages to server:");
                bufferString = sc.nextLine() + "*";
                out.write(bufferString.getBytes());
            }
        }
    }

}

... useless. Because the client awaits for the end of stream signal (in.read() = -1) that never comes, so it stays blocked by the InputStream.read() call.

For the record, I tried this as server code to further check this "rule" of sockets:

public class Server {
    public static int port = 27018;

    public static void main(String[] args) throws IOException, InterruptedException {
        char c = ' ';

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server started");
        Socket connectionSocket = serverSocket.accept();
        serverSocket.close();
        String bufferOuputString = "";

        InputStream in = connectionSocket.getInputStream();
        OutputStream out = connectionSocket.getOutputStream();

        System.out.println("Connection established on port " + port);
        out.write("Welcome to my server !".getBytes());
        Thread.sleep(3000);
        connectionSocket.close();
    }
}

And after 3 seconds, client printed...

Client output

...and proceeded to go into infinite looping because I forgot to change the second while loop. So this confirms that socket's OutputStream object only sends "end of stream" at Socket.close(). My question is : why this design choice ?


Solution

  • I'm going to close this question.

    My conclusion is that the only way to know the end of a message at socket level is really the socket closing. This is because "socket" is at OSI level 5: in TCP/IP, the "end of a message" is indeed the end of the connection. That's why the socket signals "end of connection" only when connection is closed.

    The level of abstraction required to identify and separate messages inside a socket's stream must be done at Application level (OSI level 7, typically HTTP), TCP/IP cannot do it by design.

    So the solution for this problem is either to define a "end of message" character (like CRLF or \r\n in a Java String), or to use an existing application protocol (like HTTP) to exchange messages. From a discovery and experimentation POV, I find that both are interesting!

    I realize now this question was more a misunderstanding of the OSI layers than a Java problem. Thank you for taking the time to answer me!