Is an OutputStream in Java blocking? (Sockets)
Asked Answered
A

3

18

I am currently writing naive network code for a project and a mate hinted me at the possibility that when I send a package of information from the server to all clients in an iterative fashion I might get intense lag when one of the clients is not responding properly.

He is reknown for trolling so I was kind of sceptical when implementing a secondary thread that is now responsible to send data over to a client, having a queue that the Server simply adds the packages over that is then read by the thread to send data.

The question I now have after thinking it over is weather or not the OutputStream of a Java Socket actually queues the stuff he wants to send by itself, thus eliminating the need of a queue beforehand. The possibility of having intense problems occurrs only when the Server is blocking as long as he does not get a response from a client that the sent object was recieved.

Thanks.

Accent answered 13/5, 2012 at 19:34 Comment(1)
I've run into a similar issue if I try to get the ouput or input stream in the run function of a Thread. The problem with the blocking (for both getInputStream, and getOutputStream) of the socket is because it is in the run function... the solution seems to be to put it in the constructor, saving the variable and then reference the variable in run.Reversible
J
5

Of course, when you write to a socket, this write is buffered. Socket object have a setSendBufferSize() method for setting this buffer size. If your write can be cached in this buffer, then of course, you may iterate immediately on the following socket. Otherwise this buffer need to be flushed to the client immediately. So, during flushing you are going to be blocked. If you want to avoid being blocked while flushing the buffer, you have to use a SocketChannel in non blocking I/O. Anyway, the best option for writing to many socket concurrently, is to manage each socket with a different thread, so that all writes may be executed at the same time.

Johanna answered 13/5, 2012 at 19:54 Comment(2)
"Otherwise this buffer need to be flushed to the client immediately" is not a correct way of putting it. The buffer is being written to the network all the time, asynchronously, but it can't be sent until there is room in the peer's receive buffer. If the peer isn't reading, or is reading slowing, his receive buffer will fill up, so your send buffer will fill up, so your subsequent writes will block.Marbut
Even though Tomasz's post was a better example this is exactly what I was looking for in terms of explanation EJP. Thank you very much.Accent
P
14

Your friend is right but it has more to do with how protocol works. Largely simplifying packets sent to the client need to be confirmed. If the client is not responding (fails to read incoming data, computer is under heavy load, etc.) the server won't receive acknowledgements and will stop sending the data. This mechanism built into TCP/IP prevents one end of the communication from sending large amounts of data without making sure the other end received them. In turns this avoid the requirement to resend big amounts of data.

In Java this manifests as blocking write to OutputStream. The underlying TCP/IP stack/operating system does not allow sending more data until the client is ready to receive it.

You can easily test this! I implemented simple server that accepts connection but fails to read incoming data:

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            final ServerSocket serverSocket = new ServerSocket(4444);
            final Socket clientSocket = serverSocket.accept();
            final InputStream inputStream = clientSocket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}).start();

And a simple client that just sends as much data as it can in 4K batches:

final Socket client = new Socket("localhost", 4444);
final OutputStream outputStream = client.getOutputStream();
int packet = 0;
while(true) {
    System.out.println(++packet);
    outputStream.write(new byte[1024 * 4]);
}

The client loop hangs on my computer after 95 iterations (your mileage may vary). However if I read from inputStream in server thread - the loop goes on and on.

Pachalic answered 13/5, 2012 at 19:44 Comment(1)
I've run into a similar issue if I try to get the ouput or input stream in the run function of a Thread. The problem with the blocking (for both getInputStream, and getOutputStream) of the socket is because it is in the run function... the solution seems to be to put it in the constructor, saving the variable and then reference the variable in run.Reversible
J
5

Of course, when you write to a socket, this write is buffered. Socket object have a setSendBufferSize() method for setting this buffer size. If your write can be cached in this buffer, then of course, you may iterate immediately on the following socket. Otherwise this buffer need to be flushed to the client immediately. So, during flushing you are going to be blocked. If you want to avoid being blocked while flushing the buffer, you have to use a SocketChannel in non blocking I/O. Anyway, the best option for writing to many socket concurrently, is to manage each socket with a different thread, so that all writes may be executed at the same time.

Johanna answered 13/5, 2012 at 19:54 Comment(2)
"Otherwise this buffer need to be flushed to the client immediately" is not a correct way of putting it. The buffer is being written to the network all the time, asynchronously, but it can't be sent until there is room in the peer's receive buffer. If the peer isn't reading, or is reading slowing, his receive buffer will fill up, so your send buffer will fill up, so your subsequent writes will block.Marbut
Even though Tomasz's post was a better example this is exactly what I was looking for in terms of explanation EJP. Thank you very much.Accent
R
3

An OutputStream is blocking. It probably has some buffering, but that doesn't help you much if the server is never consuming bytes (any fixed buffer will eventually fill up). So your friend is right, you need to write in a separate thread, or use something more advanced, like nio.

On the reading side, you can use available() to avoid blocking. No matching call exists on the write side. I wish there was.

Ridings answered 13/5, 2012 at 19:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.