5

TCP_NODELAY is an option to enable quick sending of TCP packets, regardless of their size. This is very useful option when speed matters, however, I'm curious what it will do to this:

Socket socket = [some socket];
socket.setTcpNoDelay(true);
OutputStream out = socket.getOutputStream();
out.write(byteArray1);
out.write(byteArray2);
out.write(byteArray3);
out.flush();

I tried to find what flush actually does on a SocketOutputStream, but as far as I know now, it doesn't do anything. I had hoped it would tell the socket "send all your buffered data NOW", but, unfortunately, no it doesn't.

My question is: are these 3 byte arrays sent in one packet? I know you don't have much control over how TCP constructs a network packet, but is there any way to tell the socket to (at least try to) pack these byte arrays, so network overhead is avoided? Could manually packing the byte arrays and sending them in one call to write help?

AyCe
  • 727
  • 2
  • 11
  • 30
  • 4
    "TCP_NODELAY .. is useful when speed matters." *NO*. It's useful when *responsiveness* matters (getting a byte sent to be received as promptly as possible). TCP_NODELAY can *reduce* overall throughput, by sending the *aggregate data* relatively *INEFFICIENTLY*. Buffering is good: whenever/wherever possible. IMHO... – paulsm4 Aug 06 '13 at 22:23
  • 2
    SUGGESTION: Get a copy of [wireshark](http://www.wireshark.org) and trace the packets actually sent for a couple of test socket programs. – paulsm4 Aug 06 '13 at 22:24
  • Yes, that is what I meant. ;-) I know it's inefficient, but in my case, _responsiveness_ is more important than waiting for enough data. However, for the case the question is about, I want to optimize it, because sending the single byte arrays doesn't make much sense, it's the content from all of them that matters. – AyCe Aug 06 '13 at 22:26
  • @AyCe: Then send the data all at once. Takes a few extra microseconds to combine them, and that's assuming not-small buffers. That time is nothing compared to how long network traffic can take. – cHao Aug 06 '13 at 22:33
  • @cHao That would be my current approach, but I was curious if there are better ways, searching did not yield anything, so I asked this question. – AyCe Aug 06 '13 at 22:35
  • I'm not aware of one. The whole point of TCP_NODELAY is to stop the OS from accumulating data before sending it. Effectively, you waste bandwidth in order to get stuff sooner. It seems like you need to decide whether you want efficiency or immediacy...cause i'm fairly certain you can't have both without doing it yourself or switching to UDP or something. – cHao Aug 06 '13 at 22:40
  • @cHao: I want immediacy, but only when it makes sense. In this case it doesn't, so I rather buffer the messages until I can send them all at once. And that seems to work well. Also, all of my messages need to arrive and in the correct order, and UDP cannot provide that, plus, it doesn't distribute bandwidth equally to different connections. Too much extra work to deal with all of that if you can just use TCP if you ask me :) – AyCe Aug 07 '13 at 00:51
  • In general, you're right. I wouldn't bother with UDP if messages *had* to arrive. But there's a bit of tug-of-war between efficiency, immediacy, and reliability. You typically have to pick two and kinda let the third go. I suppose if you're willing to sacrifice immediacy for a bit, though, you could switch nagling back on while you send the byte arrays. – cHao Aug 07 '13 at 02:00
  • Another approach would be to use TCP_CORK and then "flush" with TCP_NODELAY. I just tried it out here: https://stackoverflow.com/a/65889062/2474697 – Niels-Ole Jan 25 '21 at 17:01
  • @Niels-Ole Sadly that seems to be Linux-specific, so no way to do that in Java. It's kind of the opposite, accumulate packets until we explicitly want to send (or, I'd imagine, until the system decides it needs to free the space). Correct? – AyCe Jan 27 '21 at 07:58
  • Oh I am sorry, I assumed since TCP_NODELAY can be set that any option could be set. I only see support for TCP_NODELAY option (https://docs.oracle.com/javase/10/docs/api/java/net/SocketOptions.html), :/ – Niels-Ole Jan 27 '21 at 09:46

2 Answers2

8

My question is: are these 3 byte arrays sent in one packet?

As you have disabled the Nagle algorithm, almost certainly not, but you can't be 100% sure.

I know you don't have much control over how TCP constructs a network packet, but is there any way to tell the socket to (at least try to) pack these byte arrays

Yes. Don't disable the Nagle algorithm.

so network overhead is avoided? Could manually packing the byte arrays and sending them in one call to write help?

Yes, or simpler still just wrap the socket output stream in a BufferedOutputStream and call flush() when you want the data to be sent, as per your present code. You are correct that flush() does nothing on a socket output stream, but it flushes a BufferedOutputStream.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • As I stated in a comment already, I need TCP_NODELAY for my app, so disabling is not an option for me. I actually use `BufferedOutputStream` for sending already, does this guarantee the data to be sent only once `flush()` is called? – AyCe Aug 06 '13 at 23:04
  • Yes, if the `BufferedOutputStream` buffer is big enough to hold all the pending data. (8k by default.) However if you're expecting to *receive* the data all together, think again. There is no guarantee of that. You still have to loop at the receiver until you've got everything you need, or use `DataInputStream.readFully()` if you know the length expected. – user207421 Aug 06 '13 at 23:49
  • 1
    Okay, nice! Of course I know I cannot receive it as a "packet" as I could with UDP. All these byte arrays are single messages, but they only make sense once the last of them is received. So it would be pointless to send them as single packets. – AyCe Aug 07 '13 at 00:45
2

Could manually packing the byte arrays and sending them in one call to write help?

Yes, send them all in a single call to write. This will maximize the chances that your bytes will be sent in a single packet.

Of course, you can never know, since there are too many variables involved - different OSs and lots of different networking gear between you and your peer, but if you give the OS the ability to pack everything together, it will generally try to.

If you disable nagle and make seperate system calls (remember, the OS controls the sockets, not your appliation or java), you're asking the OS to send them individually. The OS has no idea you're about to call write again with some more data.

xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • Thanks for the info! I'm merging the byte packets, and that seems to work pretty good. – AyCe Aug 06 '13 at 23:49
  • @AyCe if you think this answers your question better than the other answer, I'd appreciate if you would switch the accepted answer. – xaxxon Aug 07 '13 at 01:36
  • 1
    You answer is great, you explain the separation of OS/Java for Sockets and also give an answer to my secondary question, while answering my primary question (if these 3 byte arrays will likely get sent in one packet) indirectly as well. EJP answers both questions as well, but also gives the advice to use `BufferedOutputStream`, so I feel like that answer is more complete :) – AyCe Aug 07 '13 at 13:23
  • @AyCe ok, no problem. – xaxxon Aug 07 '13 at 22:03
  • You're *asking* the OS to send them individually, but there is still no guarantee that it will. – user207421 May 03 '15 at 01:02