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

xco*_*ist 9 c++ boost tcp boost-asio

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

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

#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();
}
Run Code Online (Sandbox Code Playgroud)

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

Off*_*rmo 10

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

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

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

解决方案 :

问题1

您必须立即为您的套接字提供所有"消息"数据.这当前没有发生,因为正如其他人所概述的那样,当您使用连续的"<<"并且操作系统的底层TCP/IP堆栈不缓冲时,您使用的boost流API将数据放入单独调用的套接字中足够(并且有理由,为了更好的表现)

多种解决方案

  • 你传递一个char缓冲区而不是单独的chars,这样你只能对<<进行一次调用
  • 你忘记了提升,打开一个操作系统套接字,并在一次调用send()(在Windows上,查找"winsock2"API,或在unix/cygwin上查找"sys/socket.h")提供它

问题2

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

  • 如果您坚持使用Boost,请查找TCP_NODELAY选项,它位于doc中
  • 如果使用OS套接字,则必须在套接字上使用setsockopt()函数.

结论

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

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


SKi*_*SKi 9

你的代码:

out << (char) 0x1 << (char) 0x2 << (char) 0x3;
Run Code Online (Sandbox Code Playgroud)

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

由于Nagle的 TCP 算法,TCP堆栈将((char)0x1)在第一次operator<<调用之后/期间立即向对等方发送可用数据.因此剩下的数据(0x2和0x3)将转到下一个数据包.

避免1字节TCP段的解决方案:使用更大的数据束调用发送功能.