10

I'm writing a client/server application in C#, and it's going great. For now, everything works and it's all pretty robust. My problem is that I run into some delays when sending packets across the connection.

On the client side I'm doing this:

NetworkStream ns = tcpClient.GetStream();

// Send packet

byte[] sizePacket = BitConverter.GetBytes(request.Length);
byte[] requestWithHeader = new byte[sizePacket.Length + request.Length];
sizePacket.CopyTo(requestWithHeader, 0);
request.CopyTo(requestWithHeader, sizePacket.Length);

ns.Write(requestWithHeader, 0, requestWithHeader.Length);

// Receive response

ns.Read(sizePacket, 0, sizePacket.Length);
int responseLength = BitConverter.ToInt32(sizePacket, 0);
byte[] response = new byte[responseLength];

int bytesReceived = 0;
while (bytesReceived < responseLength)
{
  int bytesRead = ns.Read(response, bytesReceived, responseLength - bytesReceived);
  bytesReceived += bytesRead;
}

(Left out some exception catching etc.) The server does the opposite, i.e. it blocks on NetworkStream.Read() until it has a whole request, then processes it and sends a response using Write().

The raw speed of Write()/Read() isn't a problem (i.e. sending large packets is fast), but sending several small packets, one after another, without closing the connection, can be terribly slow (delays of 50-100 ms). What's strange is that these delays show up on LAN connections with typical ping times <1 ms, but they do not occur if the server is running on localhost, even though the ping time would effectively be the same (at least the difference should not be on the order of 100 ms). That would make sense to me if I were reopening the connection on every packet, causing lots of handshaking, but I'm not. It's just as if the server going into a wait state throws it out of sync with the client, and then it stumbles a bit as it reestablishes what is essentially a lost connection.

So, am I doing it wrong? Is there a way to keep the connection between TcpServer and TcpClient synchronised so the server is always ready to receive data? (And vice versa: sometimes processing the request from the client takes a few ms, and then the client doesn't seem to be ready to receive the response from the server until it's had a few moments to wake up after blocking on Read().)

user7116
  • 63,008
  • 17
  • 141
  • 172
ReturningTarzan
  • 1,068
  • 1
  • 13
  • 23
  • Just out of curiosity did you try using a StreamReader? – Conrad Frix May 25 '11 at 16:13
  • 1
    Any particular reason why your not using [WCF](http://msdn.microsoft.com/en-us/netframework/aa663324)? This framework has everything your trying to do and more built in. – Jay May 25 '11 at 16:04
  • 3
    On the contrary IMO; WCF is hugely bloated, and most people only use a tiny fraction of it. Raw sockets are often fine, and generally significantly more efficient. – Marc Gravell May 25 '11 at 17:48
  • I had a quick look at WCF, and while I can see how it might be useful in a huge mesh of interconnected enterprise applications, I'm really only after the ability to send simple byte arrays between two computers on a network. .NET has a bunch of very nice features (serialization, DeflateStream, XML stuff, etc.) that extend this, making it all more than adequate for my purposes. Anyeay **I FIXED IT NOW**, see below. – ReturningTarzan May 25 '11 at 18:30
  • Jay: I tried StreamReader BTW, no difference. But thx. – ReturningTarzan May 25 '11 at 19:04
  • @ReturningTarzan: I moved your update to your answer and +1'd both the question and the answer. Nice detective work. – user7116 May 26 '11 at 13:44

1 Answers1

8

It turns out my server and client were not completely symmetrical after all. I had noticed, but I didn't think it mattered at all. Apparently it's a huge deal. Specifically, the server did this:

ns.Write(sizePacket, 0, sizePacket.Length);
ns.Write(response, 0, response.Length);

Which I changed into this:

// ... concatenate sizePacket and response into one array, same as client code above
ns.Write(responseWithHeader, 0, responseWithHeader.Length);

And now the delay is completely gone, or at least it's no longer measurable in milliseconds. So that's something like a 100x speedup right there. \o/

It's still odd because it's writing exactly the same data to the socket as before, so I guess the socket receives some secret metadata during the write operation, which is then somehow communicated to the remote socket, which may interpret it as an opportunity to take a nap. Either that, or the first write puts the socket into receive mode, causing it to trip up when it's then asked to send again before it has received anything.

I suppose the implication would be that all of this example code you find lying around, which shows how to write to and read from sockets in fixed-size chunks (often preceded by a single int describing the size of the packet to follow, same as my first version), is failing to mention that there's a very heavy performance penalty for doing so.

user7116
  • 63,008
  • 17
  • 141
  • 172
ReturningTarzan
  • 1,068
  • 1
  • 13
  • 23
  • 10
    Just to explain the delays, I believe you hit Nagles Algorithm in Windows sockets: Small Packets will be put in a Buffer to improve Network overall Performance. Each single Packet might see a 200ms Delay before reaching the network peer: http://support.microsoft.com/kb/214397/en-us http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.nodelay.aspx – Stephan B May 26 '11 at 11:08
  • 1
    Well, thanks for that, it was quite informative. I wish Microsoft were better at linking to excellent articles like that from MSDN. Or maybe I just need to know where to look. Either way, I feel smarter now. Still a little odd that I was measuring ~100 ms delays, nothing like the 200 ms timeout for the Winsock buffer, but maybe I was measuring it wrong. – ReturningTarzan May 26 '11 at 14:05
  • 4
    It's not really specific to Microsoft (apart from their own choice of algorithm). Any production TCP stack will favor sending large packets over small ones to reduce overhead. So when you write a small amount of data to the TCP stream it will be buffered in the hopes that some other writes will occur. A good TCP stack algorithm strives to balance low latency (by sending immediately) and high bandwidth (by using the largest packet size possible). Most stacks offer a way to either manually flush the buffer on command, or for better performance specify no delay on a specific socket. – David May 26 '11 at 14:29
  • 1
    In case this helps anyone: there's a property called NoDelay in the TcpClient that you can use to "fix" this – HDD Dec 22 '16 at 18:52
  • Hi @ReturningTarzan, I am sending 15bytes of data for every 100ms, I have timed out 800ms at server side. I am able to sending data till 3-5mins, then suddenly it is getting timedout. Could you please help me what could be the issue ? – Android Developer World Jun 01 '22 at 12:09