Networking code is notoriously difficult to write, test and debug.
You often have lots of things to consider such as:
-
what “endian” will you use for the data that is exchanged (Intel x86/x64 is based on little-endian) – systems that use big-endian can still read data that is in little-endian (and vice versa), but they have to rearrange the data. When documenting your “protocol” just make it clear which one you are using.
-
are there any “settings” that have been set on the sockets which can affect how the “stream” behaves (e.g. SO_LINGER) – you might need to turn certain ones on or off if your code is very sensitive
-
how does congestion in the real world which causes delays in the stream affect your reading/writing logic
If the “message” being exchanged between a client and server (in either direction) can vary in size then often you need to use a strategy in order for that “message” to be exchanged in a reliable manner (aka Protocol).
Here are several different ways to handle the exchange:
-
have the message size encoded in a header that precedes the data – this could simply be a “number” in the first 2/4/8 bytes sent (dependent on your max message size), or could be a more exotic “header”
-
use a special “end of message” marker (sentinel), with the real data encoded/escaped if there is the possibility of real data being confused with an “end of marker”
-
use a timeout….i.e. a certain period of receiving no bytes means there is no more data for the message – however, this can be error prone with short timeouts, which can easily be hit on congested streams.
-
have a “command” and “data” channel on separate “connections”….this is the approach the FTP protocol uses (the advantage is clear separation of data from commands…at the expense of a 2nd connection)
Each approach has its pros and cons for “correctness”.
The code below uses the “timeout” method, as that seems to be the one you want.
See http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx. You can get access to the NetworkStream
on the TCPClient
so you can change the ReadTimeout
.
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
// Set a 250 millisecond timeout for reading (instead of Infinite the default)
stm.ReadTimeout = 250;
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytesread = stm.Read(resp, 0, resp.Length);
while (bytesread > 0)
{
memStream.Write(resp, 0, bytesread);
bytesread = stm.Read(resp, 0, resp.Length);
}
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
As a footnote for other variations on this writing network code…when doing a Read
where you want to avoid a “block”, you can check the DataAvailable
flag and then ONLY read what is in the buffer checking the .Length
property e.g. stm.Read(resp, 0, stm.Length);