【问题标题】:Choice of transports for JSON over TCP基于 TCP 的 JSON 传输选择
【发布时间】:2011-09-28 06:25:45
【问题描述】:

我正在编写一个简单的流式 JSON 服务。它由长时间(数周或数月)间断发送的 JSON 消息组成。

关于通过普通 TCP 套接字发送多个 JSON 消息的最佳做法是什么?

我研究过的一些替代方案(以及它们的缺点)是:

  1. 换行符分隔 JSON - 缺点:JSON 中的换行符需要转义或禁止
  2. 受 websocket 启发的 0x00 0xff 框架 - 缺点:它现在是二进制的,不再是 utf-8
  3. 真正的 websockets - 缺点:缺乏(开源)websocket 客户端
  4. http multipart http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html - 缺点:不完整的客户端支持?
  5. 没有分隔符 - 缺点:分块需要 JSON 解析(由于字符串中的卷曲,不能只计算卷曲)

有没有好的方法,或者至少是成熟的方法?

【问题讨论】:

  • 在每条消息之间打开/关闭套接字怎么样?
  • @fvu 在高峰期,我们每秒可能有十个或更多消息,因此效率不高。它还可能导致弱路由器上的 NAT 耗尽。
  • 为什么一个人不能数卷发?可以检测并避免计算字符串中的卷曲,不是吗?

标签: json websocket streaming multipart ldjson


【解决方案1】:

消息的四个字节中的第一个可以是一个 32 位整数,指示消息的大小(以字节为单位)。那么接收者应该遵循以下步骤:

  1. 读取数据的前四个字节并计算出读取整条消息所需的确切字节数。
  2. 读取消息的其余部分并将其反序列化为 JSON

C# 中的发送方代码:

        public void WriteMessage(Packet packet) {
        // Convert the object to JSON
        byte[] message = Encoding.UTF8.GetBytes(packet.Serialize());

        // Serialize the number of characters
        byte[] messageLength = BitConverter.GetBytes(message.Length);

        // Build the full message that will hold both the size of the message and the message itself
        byte[] buffer = new byte[sizeof(int) + message.Length];

        Array.Clear(message, 0, message.Length);

        // Print the size into the buffer
        for (int i = 0; i < sizeof(int); i++)
        {
            buffer[i] = messageLength[i];
        }

        // Print the message into the buffer
        for (int i = 0; i < message.Length; i++)
        {
            buffer[i + sizeof(int)] = message[i];
        }

        // Send it
        stream.Write(buffer, 0, buffer.Length);
    }

【讨论】:

    【解决方案2】:

    您可以使用Server-Sent Events

    var source = new EventSource('/EventSource');
    
    source.onmessage = function(e) {
      var data = JSON.parse(e.data);
      console.log(e.data);
    };
    
    source.onopen = function(e) {
      console.log('EventSource opened');
    };
    
    source.onerror = function(e) {
      console.log('EventSource error');
    };
    

    【讨论】:

      【解决方案3】:

      我已经编写了我和其他一些开发人员正在做的事情:

      http://en.wikipedia.org/wiki/Line_Delimited_JSON

      它的优点是兼容netcat/telnet。

      另见:http://ndjson.org/

      【讨论】:

      • 正如我在维基百科中所说的那样,这不是创作原创作品的最佳场所,您应该将其放在其他地方
      • 这当然不像任何“标准”,也不是我对任何人的建议。提议的实现要么效率极低(到目前为止在每个换行符上解析所有内容:O(n ^ 2)),要么取决于“自定义解析器”。刚刚注意到,那个维基百科页面是chrisdew写的,“参考实现”是他自己的。这违反了 7 项内容标准中的 4 项
      • @Javier 如果我有 n ligne of m 个字符,则有 n * O(m) 处理,即 O(nm),即 O(N) 字符总数 N = nm
      • 我使用这种格式替代 csv 文件(在处理多维数据时特别有用)。当您拥有庞大的数据集时,从 unix 命令行使用这种格式会更容易。 Json 会转义所有换行符,因此您可以安全地 grep/tail/split 数据以快速获取所需的部分,而无需解码每一行。在对大数据集进行采样时,它可以为我节省几天时间。
      • 已经有一个标准。 RFC 7464 (tools.ietf.org/html/rfc7464)
      【解决方案4】:

      当您想为浏览器客户端提供服务时,最接近原始 TCP 的是 WebSockets。

      WebSockets 有足够的动力,浏览器​​供应商将改进支持(Chrome 14 和 Firefox 7/8 支持最新的协议草案),并且广泛的客户端和服务器框架将支持它。

      已经有几个开源客户端库,包括Autobahn WebSocket

      如果您想为自己烘焙一些东西(在原始 TCP 之上),我建议您的 JSON 消息使用长度前缀格式,即Netstrings

      免责声明:我是 Autobahn 的作者,为 Tavendo 工作。

      【讨论】:

        【解决方案5】:

        我的前两个选项是:

        1. 执行早期 TCP 协议所做的事情:发送一条消息(在您的情况下为 JSON 对象)并关闭连接。客户端检测到它并重新打开以获取下一个对象。

          • 优点:非常容易解析,没有额外的(内容)字节发送。任何数据丢失都意味着只丢失一个对象。如果您能忍受,则无需为您的应用添加重传。
          • 缺点:如果您发送(大量)(非常)小对象,三包 TCP 握手会增加延迟。
        2. 执行分块模式 HTTP 的操作:首先发送 JSON 对象中的字节数、换行符(HTTP 中的 CRLF)和您的 JSON 对象。客户端只需计算字节数即可知道下一个字节何时是下一个对象大小。

          • 优点:您可以保留一个长期存在的流。
          • 缺点:一些额外的字节,您必须保持一个长寿命的流,因此必须将意外中断和重新连接作为异常事件处理,需要建立一些握手以在失败的地方继续。

        【讨论】:

        • 您可以考虑 oberstet 的建议。对于我的项目,我通常使用 Netstrings 或类似 Bencoding 的框架。在大多数情况下易于实施,并且只会增加最小的开销。
        猜你喜欢
        • 1970-01-01
        • 2015-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-30
        • 2019-04-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多