Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences on Java Sockets between Windows and Linux - How to handle them?

Tags:

java

sockets

I am having a lot of trouble in understanding the differences about how Java handles Sockets on Windows and Linux - Particularly when one of the sides (Client or Server) closes the connection abruptly.

I have written the following very simple Server and Client classes to keep my point as simple, objective and as easy for you to understand as possible:

SimpleClient.java:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import java.net.Socket;

public class SimpleClient {

    public static void main(String args[]) {
        try {
            Socket client_socket = new Socket("127.0.0.1", 9009);

            // Used to read from a terminal input:
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

            // Used for client/server communication:
            BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream()));
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream()));

            while(true) {
                System.out.print("Command: ");
                String msg = br.readLine();

                // Send:
                out.write(msg);
                out.newLine();
                out.flush();

                // Receive:
                int ifirst_char;
                char first_char;

                if((ifirst_char = in.read()) == -1) {  // Server Closed
                    System.out.println("Server was closed on the other side.");

                    break;
                }

                first_char = (char) ifirst_char;

                msg = String.valueOf(first_char);

                msg += in.readLine();

                // Shows the message received from the server on the screen:
                System.out.println(msg);
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }

    }
}


SimpleServer.java:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import java.net.ServerSocket;
import java.net.Socket;

public class SimpleServer {

    public static void main(String args[]) {
        try {
            ServerSocket server_socket = new ServerSocket(9009);

            Socket client_socket = server_socket.accept();

            while(true) {
                BufferedReader in = new BufferedReader(new InputStreamReader(client_socket.getInputStream()));
                BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client_socket.getOutputStream()));

                // Receive:
                int ifirst_char;
                char first_char;

                if((ifirst_char = in.read()) == -1) {  // Client Closed
                    System.out.println("Client was closed on the other side.");

                    break;
                }

                first_char = (char) ifirst_char;

                String msg = msg = String.valueOf(first_char);

                msg += in.readLine();

                msg = "Server Received: " + msg;

                // Send:
                out.write(msg);
                out.newLine();
                out.flush();
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}


Of course I could implement a code for properly shutting down the client or the server, but the objective, as I said, is to simulate an abrupt shutdown on either side, where no "disconnection code" could be sent or received. That's why I created these 2 very simple classes.

On Linux, it runs pretty well:

$ java SimpleClient 
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
Server was closed on the other side.
$


On Windows, however:

C:\simplesocket>java SimpleClient
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
java.net.SocketException: Connection reset by peer: socket write error
        at java.net.SocketOutputStream.socketWrite0(Native Method)
        at java.net.SocketOutputStream.socketWrite(Unknown Source)
        at java.net.SocketOutputStream.write(Unknown Source)
        at sun.nio.cs.StreamEncoder.writeBytes(Unknown Source)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(Unknown Source)
        at sun.nio.cs.StreamEncoder.implFlush(Unknown Source)
        at sun.nio.cs.StreamEncoder.flush(Unknown Source)
        at java.io.OutputStreamWriter.flush(Unknown Source)
        at java.io.BufferedWriter.flush(Unknown Source)
        at SimpleClient.main(SimpleClient.java:32)


Let's say I try to ignore this Exception by modifying the following lines on my SimpleClient.java:

// Send:
try {
    out.write(msg);
    out.newLine();
    out.flush();
    }
catch(Exception e) {}


Another Exception is thrown:

C:\simplesocket>java SimpleClient
Command: echo
Server Received: echo
Command: test
Server Received: test
Command: (server now was closed on the other side)
java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
        at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
        at sun.nio.cs.StreamDecoder.read(Unknown Source)
        at java.io.InputStreamReader.read(Unknown Source)
        at java.io.BufferedReader.fill(Unknown Source)
        at java.io.BufferedReader.read(Unknown Source)
        at SimpleClient.main(SimpleClient.java:42)


I don't know if the corresponding lines on the code will be the ones pointed out on these Exceptions, but the first one is thrown on out.flush() and the second one on in.read().

So basically, as you can see on Linux, even after abruptly closing the server:

1. It doesn't throw an Exception when I try to send the data.
2. And more importantly, when I try to receive it, the first char is "-1" and received correctly.

On Windows, it throws Exceptions both when sending and more importantly on receiving - when calling the read() method - I cannot get the "end of the stream" (-1) code.

Which leads to some questions:

1. Why is there such a big difference on Windows x Linux? Why on Linux these Exceptions are not thrown while on Windows they are?

2. Shouldn't Java, with all its cross-platform qualities, try to minimize the differences on running in both the Systems? (by the way I'm using JDK 7 on both)

3. Is there a way to change the code for an abrupt shutdown and get it to work more "Linux-like" on Windows, without throwing all these Exceptions and getting the -1 on my in.read()??

4. If not, any external API recommended?


I've tried to search the web for hours on this specific topic but without success.

I have also tried many solutions like calling methods like isConnected(), isBound(), isClosed(), etc. in the client_socket on the client side without success. They always say that there is an active connection and no problem with it, even after shutting down the server.

Hopefully someone would take the time to answer at least one of these questions.

You have my most sincere thanks in advance for any answers.

like image 995
icmp_request Avatar asked Apr 08 '14 08:04

icmp_request


People also ask

How is the socket programming in Linux different from that in Windows?

This means that on Linux and macOS, you can generally use all of the general purpose file functions with socket handles (e.g. read() , write() ). On Windows, socket handles can only be used with special socket functions.

Do sockets work on Windows?

Run-time requirementsWindows Sockets 2 can be used on all Windows platforms.


1 Answers

Your code doesn't do any close, so I'll assume you actually mean that one endpoint process is stopped aka killed.

Unix socket sd's are "just" fd's, and when a Unix process ends without closing an fd, including the case where a JVM stops and you hadn't called close (or shutdown-WR), the fd is closed by the OS, which for TCP socket does (at least tries) the normal aka graceful close: FIN/ACK exchange with FIN-WAITs and TIME-WAIT. The only way I know to make Unix socket do graceless close at the TCP level (RST) is to set linger to 0 before closing (either explicitly or by exiting). It is also possible and not uncommon for a middlebox to forcibly break your connection with RST; for example I've seen firewalls that RST you after 15min inactivity. I've also more rarely seen middleboxes that fake FIN, or that try to but do it wrong.

Windows sockets (WinSock) are a different API than files. If a Windows process ends without calling closesocket (similar to but separate from close) or at least shutdown-WR, Winsock does RST. To get graceful close (FIN) on Windows you (via JVM) must call one of those. JVM presumably could track java.net.Sockets (but not any in JNI) and do this for you on JVM exit, but it doesn't; you could request an enhancement. Even that might not work if you externally kill it with TaskMgr or similar, and might not work right if you hit a JVM fault: the JVM tries to catch faults and give a minidump, which would be a place to try to cleanup sockets, but if there was a JVM bug it might fail again -- and IME most JVM faults are due to JVM bugs.

If it's enough to handle code bugs (leaks) and signals but not JVM bugs and failures, you could just subclass Socket so that if forces (graceful) close on .finalize and on exit using Runtime.addShutdownHook, and use that instead.

In either Unix or Windows sockets, received FIN is treated as end-of-file, like any other file for example a disk file. Received RST is returned as an error [WSA]ECONNRESET to JVM, which raises an exception. It wouldn't be good to hide this difference, because for apps other than yours it can be significant -- enough so that some protocols have had to be changed to prevent fake-FIN being a security vulnerability notably SSLv2 and HTTP/0.9.

If you also consider cases where the peer system fails (not just the JVM), or certain parts of the network fail, or your network interface fails, the Exceptions you can get are quite a bit more varied. IMHO don't try to handle those, just report what you see and let the sysadmins and netadmins sort it out. I've seen cases where a programmer got Exception X due to problem P in laboratory conditions and coded for that, but in the real world exception X happened for very different reasons and the "helpful" handling actually made it harder to solve the problem.

Asides: the server should create the BufferedReader before not inside the while(true)do-a-line loop; if you ever get/want a client that sends more than one line at a time, the code shown will lose data. You don't need that hair for if first_char==-1 else convert to String; just use in.readLine, it returns null in exactly the same case where initial in.read returns -1, which for (TCP) Socket is when FIN is received. Conversely the client readLine from System.in should be checked; if someone types ^Z or ^D or whatever you'll get NPE.

like image 182
dave_thompson_085 Avatar answered Oct 14 '22 08:10

dave_thompson_085