What is the minimum size of data I should count on with TcpClient under C# + .Net 2.0

Started by
8 comments, last by Codeka 15 years, 4 months ago
I'm using the TcpClient and TcpListener in C# .Net 2.0 to create client/server architecture. On the server side, I use BeginRead/EndRead for non-blocking sockets. What I have is a header that is sent before the main payload, the header is 48 bytes long. Can I count on those 48 bytes coming through on a single BeginRead call or will I need to concatinate 31 bytes + 17 bytes, or 1 byte + 1 byte + 1 byte + etc, or whatever comes through?
Advertisement
Most of the time, you'll get the full 48 bytes, it's not gauranteed so it's better to be safe and assume that anything can happen. For example, some of the header for the next packet might be concatenated with a previous packet.

TCP is a stream protocol, there's no concept of a "packet" so if you ask it to read x bytes, it actually means "read at most x bytes" - it could hand you anywhere between 1 and x bytes and you should account for that.
Quote:Original post by azherdev

Can I count on those 48 bytes coming through on a single BeginRead


No.

TCP itself cannot make such guarantees, since while 47 bytes might transfer, client could die at that point, and remaining byte could never arrive.

Blocking clients give the option to block until specified number of bytes is received, but also run into the problem of waiting indefinitely due to connection problems.
Got it. So, if my first 4 bytes is the size it should read up to, I can't even count on that? Huh?

So, I would need to read into a buffer until I get at least 4 bytes. Then convert that to the size it needs to wait for, then wait for at least that many bytes. Etc etc.

Is 0 bytes on ReadBegin possible? Or will it not be called if 0 bytes are received in the payload of the tcp packet?
Quote:Original post by azherdev
Is 0 bytes on ReadBegin possible? Or will it not be called if 0 bytes are received in the payload of the tcp packet?


If Read returns 0 it means the other side has disconnected, so if it's still connected, you're always going to get at least 1 byte.

The way I normally write a TCP client is I create a "ReadExactly" function which wraps up the "keep reading until I get a complete packet" functionality. Then I can just go "ReadExactly(48)" to get the full header, then inspect the header for the length of body and ReadExactly(that_value).
Quote:The way I normally write a TCP client is I create a "ReadExactly" function which wraps up the "keep reading until I get a complete packet" functionality


That naive implementation will cause you grief, because it's easy to DOS your server by sending a single byte, and then keeping the connection open without sending anything. Your thread will just sit there, waiting forever.

In general, you want to treat input as a state machine. When data comes in, put it into a queue, and notify the listener for that client. The listener will check whether there's enough for whatever it needs now; if so, dequeue that much, and enter whatever the next state is; else just do nothing and wait for the next notification.
enum Bool { True, False, FileNotFound };
In my experience an exception is thrown during a blocking read operation if the connection is lost.
Quote:Original post by wingbird101
In my experience an exception is thrown during a blocking read operation if the connection is lost.


Eclipse platform 3.4, software updater I ran yesterday stopped at 54% while downloading from a HTTP source. I only noticed it only after some 30 minutes of what should be 30 second download. UI was responsive, but wouldn't shutdown, since thread didn't complete.

Reason was quite simple - server stopped sending data, but disconnect didn't come through or was never sent.

It's one of signature problems of networked applications that do not consider that under TCP connection may simply go silent without any notification whatsoever.

I'd have to say I encounter this type of problem roughly once a day in browser, it's especially problematic for HTTP. But at least browsers have gotten somewhat better at handling it. Before 2.0, Firefox would simply hang itself in such case (IIRC).
Quote:Got it. So, if my first 4 bytes is the size it should read up to, I can't even count on that? Huh?

So, I would need to read into a buffer until I get at least 4 bytes. Then convert that to the size it needs to wait for, then wait for at least that many bytes. Etc etc.

Yes. I use a similar length-tagged message system in my code:

int length;copied = FillHeader(ref buf, 4, read);						if(headerread < 4) return;length = GetInt(msgheader, 0, 4);if(read == copied) return;bytes.Add(buf, 0, read - copied);if(bytes.Length >= length){	// A message was received!	headerread = 0; // reset this for the next message	byte[] msg = bytes.Read(0, length);	OnReadMessage(this, code, msg, length);	// Don't forget to put the rest through the mill	int togo = bytes.Length - length;	if(togo > 0){		byte[] whatsleft = bytes.Read(length, togo);		bytes.Clear();		ReadInternal(whatsleft, whatsleft.Length, true);	} else bytes.Clear();}

(That's simplified because there's a load of stuff in there about encryption and different message types which obscures the point.) FillHeader copies bytes into a header variable and removes them from the front of buf.

The important point is that as well as underreading, you can also overread – so you have to call the method recursively if there is any data left over at the end.
Quote:Original post by hplus0603
That naive implementation will cause you grief, because it's easy to DOS your server by sending a single byte, and then keeping the connection open without sending anything. Your thread will just sit there, waiting forever.


To be clear, my implementation is typically done with asyncronous reads and a queue with callbacks when a complete packet is (finally) received. I can see how such a qualification is needed, though [smile]

This topic is closed to new replies.

Advertisement