当使用Boost ASIO时,有效负载分配两个TCP数据包,当它适合MTU时

时间:2011-07-27 15:24:50

标签: c++ boost tcp boost-asio

我有一个boost :: asio :: ip :: tcp :: iostream的问题。我试图发送大约20个原始字节。问题是这个20字节的有效载荷被分成两个TCP数据包,其中包含1个字节,然后是19个字节。简单的问题,为什么会发生我不知道。我正在为传统的二进制协议写这个,非常需要有效载荷适合单个TCP数据包(呻吟)。

从我的程序中粘贴整个源代码将是漫长而过于复杂的,我在这里发布了2个函数内的函数问题(经过测试,它确实重现了这个问题);

#include <iostream>

// BEGIN cygwin nastyness
// The following macros and conditions are to address a Boost compile
// issue on cygwin. https://svn.boost.org/trac/boost/ticket/4816
//
/// 1st issue
#include <boost/asio/detail/pipe_select_interrupter.hpp>

/// 2nd issue
#ifdef __CYGWIN__
#include <termios.h>
#ifdef cfgetospeed
#define __cfgetospeed__impl(tp) cfgetospeed(tp)
#undef cfgetospeed
inline speed_t cfgetospeed(const struct termios *tp)
{
    return __cfgetospeed__impl(tp);
}
#undef __cfgetospeed__impl
#endif /// cfgetospeed is a macro

/// 3rd issue
#undef __CYGWIN__
#include <boost/asio/detail/buffer_sequence_adapter.hpp>
#define __CYGWIN__
#endif
// END cygwin nastyness.

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <iostream>

typedef boost::asio::ip::tcp::iostream networkStream;

void writeTestingData(networkStream* out) {
        *out << "Hello world." << std::flush;
//      *out << (char) 0x1 << (char) 0x2 << (char) 0x3 << std::flush;
}

int main() {
        networkStream out("192.168.1.1", "502");

        assert(out.good());

        writeTestingData(&out);
        out.close();
}

要添加到这个奇怪的问题,如果我发送字符串“Hello world。”,它会进入一个数据包。如果我发送0x1,0x2,0x3(原始字节值),我在数据包1中获得0x1,然后在下一个TCP数据包中获得其余数据。我使用wireshark来查看数据包,dev机器和192.168.1.1之间只有一个开关。

5 个答案:

答案 0 :(得分:10)

别担心,你是来自唯一有这个问题的人。肯定有一个解决方案。事实上,您的遗留协议存在两个问题,而不仅仅是一个。

旧的旧协议需要一个“应用程序消息”以适应“一个且仅一个TCP数据包”(因为它错误地使用面向TCP流的协议作为面向数据包的协议)。所以我们必须确保:

  1. 没有“应用程序消息”被分割为多个TCP数据包(您看到的问题)
  2. 没有TCP数据包包含多个“应用程序消息”(你没有看到这个,但肯定会发生)
  3. 解决方案:

    问题1

    您必须立即为您的套接字提供所有“消息”数据。目前还没有发生这种情况,因为正如其他人所概述的那样,当您使用连续的“&lt;&lt;”时,您使用的boost流API会将数据放入单独调用的套接字中并且操作系统的底层TCP / IP堆栈没有足够的缓冲(并且有理由,以获得更好的性能)

    多种解决方案:

    • 您传递了一个字符缓冲区而不是单独的字符,因此您只能对&lt;&lt;
    • 进行一次调用
    • 你忘了提升,打开一个操作系统套接字并在一次调用send()(在Windows上,查找“winsock2”API,或在unix / cygwin上查找“sys / socket.h”)< / LI>

    问题2

    您必须激活套接字上的TCP_NODELAY选项。此选项特别适用于此类传统协议案例。它将确保OS TCP / IP堆栈“无延迟地”发送您的数据,并且不会将其与您稍后发送的另一个应用程序消息一起缓冲。

    • 如果您坚持使用Boost,请查找TCP_NODELAY选项,它位于文档中
    • 如果使用操作系统套接字,则必须在套接字上使用 setsockopt() 功能。

    结论

    如果你解决了这两个问题,你应该没问题!

    Windows或Linux上的OS套接字API使用起来有点棘手,但您可以完全控制其行为。 Unix example

答案 1 :(得分:9)

您的代码:

out << (char) 0x1 << (char) 0x2 << (char) 0x3;

将进行3次operator<<功能调用。

由于TCP的Nagle's algorithm,TCP堆栈将在第一次((char)0x1)呼叫之后/期间立即向对等方发送可用数据operator<<。 所以其余的数据(0x2和0x3)将转到下一个数据包。

避免1字节TCP段的解决方案: 使用更大的数据集调用发送函数。

答案 2 :(得分:1)

我不确定是谁会强制要求整个有效负载在一个TCP数据包中。 TCP本质上是一种流协议,发送的数据包数量和有效负载大小等的大部分细节都留给操作系统的TCP堆栈实现。

我会仔细检查这是否是您协议的实际限制。

答案 3 :(得分:1)

我同意User1的回答。你可能会多次调用operator <<;在第一次调用时,它立即通过网络发送第一个字节,然后Nagle的算法开始运行,因此剩余的数据在一个数据包内发送。

尽管如此,即使分组化不是问题,在小块数据上经常调用套接字发送函数也是一个大问题。在套接字上调用的每个函数都会调用一个繁重的内核模式事务(系统调用),为每个字节调用send只是疯了!

您应首先在内存中格式化您的消息,然后发送它。对于您的设计,我建议创建一种缓存流,它将在其内部缓冲区中累积数据并立即将其发送到底层流。

答案 4 :(得分:0)

将通过TCP套接字发送的数据视为数据包是错误的。它是一个字节流,您如何构建数据是特定于应用程序的。

  

有什么建议吗?

我建议您实现一个协议,以便接收方知道要预期的字节数。实现此目的的一种流行方法是发送固定大小的标头,指示有效负载的字节数。