用于实时数据和元数据的 TCP 通信协议

TCP communication protocols for realtime data plus metadata

我对网络协议设计还很陌生,希望对我的问题提出建议。

我的要求

  1. 我需要将实时数据从 TCP 套接字客户端流式传输到服务器。 我这里没有选择UDP.
  2. 数据需要元数据描述符才能在 服务器端。此描述符可以代表客户端用户在 运行 时间更改。
  3. 我需要偶尔从客户端向服务器发送控制命令以进行数据操作。

设计A

优点

缺点

设计 B

优点

缺点

我倾向于 Design A "realtime" 保证比正确解码更安全。但我想与专家仔细检查我的想法。

我知道那里有成百上千种通信协议,但老实说,看着随机的协议文档让我头晕目眩。毕竟我的应用程序相对轻量级,所以自己动手似乎更有意义。

顺便说一句,我使用Google Protobuf进行协议设计。我认为它的性能是实时就绪的。

如有任何建议,我们将不胜感激。

正如@RonMaupin 在评论中提到的,TCP 和 "real time" 并不是特别兼容,因为 TCP 优先于 "getting the bytes there correctly" 而不是 "getting the bytes there in a particular time frame"。

就是说,您 可以 使用 TCP 实现的是 "getting the bytes there as quickly as possible",只要您同意在某些情况下(例如网络是丢弃大量数据包),"as quickly as possible" 可能不会那么快。

关于使用多少 TCP 流,与做出该决定相关的 TCP 质量是 TCP 流始终强制in-order/FIFO 传送该流中的字节。也就是说,您 send() 发送到 TCP 套接字的所有字节都将按照接收程序的确切顺序 recv()——这种行为可能对您有利,也可能对您不利,因此您想要以适合您的方式设计您的程序。特别是,在决定是使用一个还是多个 TCP 连接时,问问自己,"does the data I'm sending need to be received in the same order it was sent, or does that not matter?" 如果严格的 FIFO 顺序很重要,那么单个 TCP 流是可行的方法; OTOH 如果你例如有两种类型的数据并且 type-B 数据在逻辑上独立于 type-A 数据,那么您可以考虑为 type-B 数据提供其自己的独立 TCP 流,以便 dropped-packet-of-A-data 不会减慢 B-data.

的传输速度

在任何情况下,您都希望至少有一些最小的 protocol/framing(例如,在每个 data-message) 这样接收方就不必猜测它正在接收的字节的含义。 (即使你一开始不需要它们,你也会希望在你的第二个版本中使用它们来帮助维护 backwards-compatibility 与以前版本的协议)

其他一些使您的 TCP-data 尽可能达到 fast/low-latency 的建议:

  1. 禁用 Nagle 的算法(在您完成 send()-ing 特定的数据突发后永久或至少暂时禁用)——否则您将获得 200 多毫秒的不必要时间大部分时间都有延迟。

  2. 假设您的程序 运行 在具有大量 RAM 的平台上运行,setsockopt() 使用 SO_SNDBUFSO_RCVBUF 选项来发送和接收socket-buffers尽可能大;这减少了缓冲区填满和数据包因 no-space-available-in-a-buffer.

  3. 而被丢弃的可能性
  4. 如果可能,请将您的发送算法设计为仅在最后可能的时刻生成 data-to-send,而不是 queue 占用大量 data-to-be-sent进步。例如,如果(由于某些 trigger-event)您的代码决定它需要尽快通过 TCP 套接字发送特定 data-structure 的当前状态,而不是序列化数据结构和 enqueueing (and/or send()-ing) 马上序列化的字节,只要设置一个 dirty-flag 表示需要发送结构。那么,下一次socket表示是ready-for-write,是时候将数据结构序列化,发送给socket。好处是,如果您收到例如快速连续 10 trigger-events,使用这种 dirty-flag 设计,您仍然最终只发送一次最终版本数据结构,而不是连续发送 10 次。第二个好处是,这限制了可以 queue 等待发送的数据积压,从而减少了 data-updates.

  5. 的平均延迟
  6. 在接收端,让 recv() 调用由一个专用的 high-priority 线程在一个紧密的循环中完成,除了尽可能快地接收数据外,它做的很少可能然后 enqueue 它进行进一步处理。这里的想法是尽量减少接收 TCP 套接字的 incoming-data-buffer 变满的可能性,因为如果它确实变满,一些传入的 TCP 数据包可能会被丢弃,迫使 TCP backoff-and-resend,这会减慢速度传输.