2

我正在尝试在 Linux 上创建一个 p2p 应用程序,我希望它尽可能高效地运行。

我遇到的问题是管理数据包。众所周知,在任何时候,recv() 缓冲区中都可能有多个数据包,因此需要某种消息帧系统来确保多个数据包不会被视为一个大数据包。

所以目前我的数据包结构是:

(u16int Packet Length):(Packet Data)

这需要两次调用 recv(); 一个获取数据包大小,一个获取数据包。

这有两个主要问题:

1. A malicious peer could send a packet with a size header of 
  something large, but not send any more data. The application will 
  hang on the second recv(), waiting for data that will never come.
2. Assuming that calling Recv() has a noticeable performance penalty
  (I actually have no idea, correct me if I am wrong) calling Recv() twice 
  will slow the program down.

为了获得最佳效率和稳定性,构建数据包/接收系统的最佳方法是什么?其他应用程序是如何做到的?你有什么建议吗?

先感谢您。

4

5 回答 5

5

我认为您在 TCP 流中的消息“框架”是正确的。

您可以考虑在每个帧的前面放置一个“神奇的 cookie”(例如,除了数据包长度之外,在每个帧头的顶部写入 32 位 int“0xdeadbeef”),这样很明显您正在阅读一个帧每个 recv() 对的第一个上的标题。如果消息开头没有出现魔法整数,则您已经不同步并且需要断开连接。

多次 recv() 调用不太可能对性能造成影响。事实上,由于 TCP 消息会以不可预知的方式被分段、合并和停止,您可能需要在循环中调用 recv() 直到获得您期望的所有数据。这包括您的两个字节标头以及有效负载字节的更大读取。您完全有可能使用 2 字节缓冲区调用“recv”来读取消息的“大小”,但只能返回 1 个字节。(再次调用recv,你会得到后续字节)。我告诉我团队中的开发人员 - 对您的网络解析器进行编码,就好像 recv 一次只传递 1 个字节一样。

您可以使用非阻塞套接字和“选择”调用来避免挂起。如果数据没有在合理的时间内到达(或者到达的数据比预期的多——这样下一条消息的同步变得不可能),你只需断开连接。

我正在开发自己的 P2P 项目。很想交换票据。如果您愿意,请离线关注我。

于 2011-11-16T07:57:11.787 回答
3

我不同意其他人的观点,TCP 是一种可靠的协议,因此除非您担心您的客户端代码不稳定或未经请求的客户端连接到您的端口号,否则数据包魔术头是无用的。

为每个客户端创建一个缓冲区并使用非阻塞套接字和select/// 。如果客户端有可用的数据,请尽可能多地阅读,如果您阅读更多“数据包”并不重要。然后检查您是否已阅读足够的内容以使 size 字段可用,如果是,请检查您是否已阅读整个数据包(或更多)。如果是,则处理数据包。然后,如果有更多数据,您可以重复此过程。如果剩下部分数据包,您可以将其移动到缓冲区的开头,或者使用循环缓冲区,这样您就不必执行那些 memmove-s。pollepollkqueue

客户端超时可以在您的select/... 循环中处理。

如果你对接收到的数据包数据做一些复杂的事情,这就是我会使用的。如果您所做的只是将结果写入文件(以更大的块),那么sendfile/会splice产生更好的性能。只需读取数据包长度(可能是多次读取),然后对 sendfile 使用多次调用,直到您读取了整个数据包(跟踪剩余的读取量)。

于 2011-11-20T12:19:55.307 回答
1

您可以使用非阻塞调用recv()(通过在套接字上设置 SOCK_NONBLOCK),并等待它们准备好在select()循环中使用(超时)读取数据。

然后如果一个文件描述符处于“等待数据”状态的时间过长,你可以直接关闭套接字。

于 2011-11-16T07:50:14.607 回答
1

TCP 是一个面向流的协议——它实际上没有任何数据包的概念。因此,除了在一次recv()调用中接收多个应用层数据包外,您还可能只接收应用层数据包的一部分,其余部分将在未来的recv()调用中出现。

这意味着通过在每次调用时接收尽可能多的数据recv(),然后在应用层缓冲区中缓冲该数据,直到您拥有至少一个完整的应用层数据包,才能获得稳健的接收器行为。这也避免了您的两次呼叫recv()问题。

要始终在每个 接收尽可能多的数据recv()而不阻塞,您应该使用非阻塞套接字并调用recv()直到它返回 -1 并errno设置为EWOULDBLOCK

于 2011-11-20T11:49:18.730 回答
0

正如其他人所说,领先的幻数(OT:man file)是识别数据报边界的好(99.999999%)解决方案,超时(使用非阻塞recv())对于检测丢失/延迟数据包很有用。

如果您指望攻击者,您应该在您的数据包中放置一个CRC。如果一个专业的攻击者真的想要,他/她迟早会弄清楚你的 CRC 是如何工作的,但这比创建没有 CRC 的数据包更难。(此外,如果安全至关重要,您可以在网上找到 SSL 库/示例/代码。)

于 2011-11-16T08:58:02.837 回答