I implemented a simple protocol over tcp that sends a content's size, and then it's contents, it's meant to be very simple way of sending strings and arrays of bytes across the application while detecting end of messages correctly, here's relevant code:
public static class StreamExtension
{
/// <summary>
/// Sends a byte packet that can be read with "ReadBytePacket" on the
/// receiver's side
/// </summary>
/// <param name="target"></param>
/// <param name="content"></param>
public static void SendBytePacket(this NetworkStream target, byte[] content)
{
long packetSize = content.Length;
target.Write(BitConverter.GetBytes(packetSize), 0, 8);
target.Write(content, 0, content.Length);
}
/// <summary>
/// Reads a byte packet sent from the SendBytePacket function
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
public static byte[] ReadBytePacket(this NetworkStream target)
{
//Reads the packet size
long size = target.ReadPacketSize();
//Reads "size" bytes from the stream
return target.ReadPacketBody(size);
}
/// <summary>
/// Reads the packets header, which contains the size of the packet body
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
private static long ReadPacketSize(this NetworkStream target)
{
byte[] packetSizeBytes = new byte[8];
for (int totalRead = 0; totalRead < 8;)
{
totalRead += target.Read(packetSizeBytes, totalRead, 8 - totalRead);
}
return BitConverter.ToInt64(packetSizeBytes, 0);
}
/// <summary>
/// Sends a string over the NetworkStream that can be read with
/// "ReadStringPacket" function on the receiver's side
/// </summary>
/// <param name="target"></param>
/// <param name="content"></param>
public static void SendStringPacket(this NetworkStream target, string content)
{
var bytes = Encoding.ASCII.GetBytes(content);
target.SendBytePacket(bytes);
}
/// <summary>
/// Reads a string packet sent from the SendStringPacket function
/// </summary>
/// <returns></returns>
public static string ReadStringPacket(this NetworkStream target)
{
//Reads the packet size
long size = target.ReadPacketSize();
//Reads "size" bytes from the stream
byte[] resultBytes = target.ReadPacketBody(size);
//Decodes the string from the received bytes
return Encoding.ASCII.GetString(resultBytes, 0, (int) size);
}
/// <summary>
/// Reads the packet body, which is a sequence of "packetSize" bytes
/// </summary>
/// <param name="target"></param>
/// <param name="packetSize"></param>
/// <returns></returns>
private static byte[] ReadPacketBody(this NetworkStream target, long packetSize)
{
//Reads "size" bytes from the stream
byte[] resultBytes = new byte[packetSize];
for (int byteCount = 0; byteCount < packetSize;)
{
byteCount += target.Read(resultBytes, byteCount, (int)packetSize - byteCount);
}
return resultBytes;
}
}
Now, the problem is, whenever I call "SendStringPacket" with a really long string, some chunks of the string get out of order on the receiving side, here's code from a failing test:
[TestFixture]
public class StreamExtensionTest
{
private Thread _receiver;
private string _results = null;
private Thread CreateReceiver()
{
return new Thread(() =>
{
var listener = new TcpListener(IPAddress.Any, 5000);
listener.Start();
//blocking call to wait for connections
var client = listener.AcceptTcpClient();
client.ReceiveTimeout = 200;
//Connected
var stream = client.GetStream();
_results = stream.ReadStringPacket();
stream.Close();
client.Close();
listener.Stop();
});
}
[Test]
public void StringPackets()
{
var testCases = new string[]
{
//"",
//"This is my simple test case",
//"http://stackoverflow.com/questions/13097269/what-is-the-correct-way-to-read-from-networkstream-in-net",
//"{[:]}",
//System.IO.File.ReadAllText(TestEnvironmentHelper.ScreenVariablesMapPath + "ScreenVariables.map"),
//"%1003",
System.IO.File.ReadAllText(TestEnvironmentHelper.TestTextsPath + "1003")
};
foreach (var testCase in testCases)
{
_receiver = CreateReceiver();
_receiver.Start();
Thread.Sleep(50);
var Client = new TcpClient("127.0.0.1", 5000);
Client.ReceiveTimeout = 5000;
var stream = Client.GetStream();
stream.SendStringPacket(testCase);
Thread.Sleep(3000);
stream.Close();
Client.Close();
//_receiver sets the _results member
Assert.AreEqual(testCase.Length, _results.Length);
System.IO.File.WriteAllText(TestEnvironmentHelper.TestTextsPath + "result.txt", _results);
Assert.AreEqual(testCase, _results);
}
}
}
On the receiving thread I've used a TcpListener, so how could my string be getting scrambled? isn't a NetworkStream from a TcpClient/Listener meant to preserve order?