协议缓冲区、C# 和 NetworkStream:永远不会收到消息

Protocol buffers, C# and NetworkStream: messages are never received

我正在尝试在 NetworkStream 上使用 ProtocolBuffers,但消息从未被完全接收。

这是我的服务器:

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

此处提供完整的重现解决方案:https://github.com/IanPNewson/ProtobufNetworkStreamIssue

怎么了?

protobuf 不是终止协议,我的意思是:当单个消息结束时,字面上没有任何说明。因此,默认情况下:像 ParseFrom 这样的 API 读取 直到流结束 ,并且在打开的 Socket/NetworkStream 中:仅当您发送一条消息然后关闭出站连接时才会发生(目前 没有更多字节 是不够的 - 它需要在流上看到实际的 EOF,这意味着每个套接字只能发送一条消息)。

因此,您通常将 framing 与 protobuf 一起使用,这意味着:在开放流中表示单个消息的某种方式。这通常是通过 length-prefix 完成的(您不能使用标记值来终止,因为 protobuf 可以包含任何字节值)。

一些 API 包括用于此的便捷方法(例如,protobuf-net 中的 *WithLengthPrefix API,尽管您不能将其放在此处)- 或者您可以自己手动实现.或者,也许考虑像 gRPC 这样的东西,它处理所有框架等语义 为你 ,这样你就可以专注于做有趣的工作。如果您真的不想处理 gRPC 的完整 HTTP/2 方面,我有一个适用于裸套接字的 gRPC 变体。