代码之家  ›  专栏  ›  技术社区  ›  Ian Newson

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

  •  2
  • Ian Newson  · 技术社区  · 2 年前

    我正试图在一段时间内使用协议缓冲区 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);
    }
    

    这里提供完整的复制解决方案: https://github.com/IanPNewson/ProtobufNetworkStreamIssue

    怎么了?

    1 回复  |  直到 2 年前
        1
  •  2
  •   Marc Gravell    2 年前

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

    因此,您通常使用 框架 使用protobuf,这意味着:在开放流中表示单个消息的一些方法。这通常通过长度前缀完成(不能使用sentinel值终止,因为protobuf可以包含任何字节值)。

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