【发布时间】:2016-01-11 08:31:36
【问题描述】:
我在 tcp 上实现了一个简单的协议,它发送内容的大小,然后是内容,这是一种非常简单的方法,可以在正确检测消息结束的同时跨应用程序发送字符串和字节数组,以下是相关代码:
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;
}
}
现在,问题是,每当我用一个非常长的字符串调用“SendStringPacket”时,接收端的某些字符串块会出现故障,这是来自失败测试的代码:
[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);
}
}
}
在接收线程上我使用了 TcpListener,那么我的字符串怎么会被打乱呢?来自 TcpClient/Listener 的 NetworkStream 不是为了保持秩序吗?
【问题讨论】:
-
您没有向我们展示
ReadPacketBody,但我已经可以在ReadPacketSize中发现一个错误,并且它可能存在相同的问题 - 您忽略了来自Read的返回值,它告诉您实际读取了多少字节。可以是 1 和您要求的字节数之间的任何数字。如果您需要读取一定数量的字节,则需要继续循环直到接收到该数量的字节。 -
谢谢,刚刚修复了那个并添加了 ReadPacketBody 的代码,但测试仍然失败,但在同一索引中出现错误
-
尝试在
target.Write()之后调用target.Flush()。 -
@Lorek target.Flush() 让我更加困惑了,它改变了书面测试文件的内容,一些块被替换为一系列 \0 字符,随后调用 target.Flush() Thread.Sleep(200) 导致输出恢复到未调用 Flush 时的输出。我现在还添加了完整的测试代码
-
new TcpClient("127.0.0.1", 5000)将客户端端点绑定到该 IP 和端口。它不会连接到侦听该 IP 和端口的服务器。还是我错过了你打电话给Connect("127.0.0.1", 5000)的地方?
标签: c# network-programming tcpclient