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);