协议缓冲区,c#和NetworkStream:消息永远不会被接收



我试图在NetworkStream上使用协议缓冲区,但消息从未完全收到。

这是我的服务器:

var listener = new TcpListener(System.Net.IPAddress.Any, 4989);
listener.Start();
while (true)
{
var client = listener.AcceptTcpClient();
Task.Factory.StartNew(() =>
{
var message = ServerMessage.Parser.ParseFrom(client.GetStream());
Console.WriteLine(message);
});
}
这是我的客户:
Thread.Sleep(2000);//Wait for server to start
var client = new TcpClient();
client.Connect("localhost", 4989);
while (true)
{
var message = new ServerMessage
{
Time = (ulong)DateTime.UtcNow.Ticks,
Type = MessageType.Content
};
message.WriteTo(client.GetStream());
Thread.Sleep(1000);
}

完整的repro解决方案可在这里获得:https://github.com/IanPNewson/ProtobufNetworkStreamIssue

怎么了?

protobuf不是一个终止协议,我的意思是:实际上没有任何内容说明单个消息何时结束。因此,默认情况下:像ParseFrom这样的api读取直到流结束,而在开放的Socket/NetworkStream中:只有在发送单个消息然后关闭出站连接时才会发生这种情况(此时没有更多的字节是不够的-它需要看到流上的实际EOF,这意味着您只能发送一个消息每个套接字)。因此,您通常使用和protobuf一起帧,这意味着:在开放流中表示单个消息的某种方法。这通常是通过长度前缀来完成的(你不能使用哨兵值来终止,因为protobuf可以包含任何字节值)。

一些api包含方便的方法(例如,protobuf-net中的*WithLengthPrefixapi,尽管您不能在这里直接删除它)—或者您可以自己手动实现它。或者,也许可以考虑像gRPC这样的东西,它为你处理所有的框架等语义,所以你可以专注于做有趣的工作。如果你真的不想处理gRPC的完整HTTP/2端,我有一个gRPC的变体,可以在裸套接字上工作。

最新更新