当使用Boost ASIO时,有效载荷拆分为两个TCP数据包,当它适合MTU时
Payload split over two TCP packets when using Boost ASIO, when it fits within the MTU
我有一个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来查看数据包,在开发机器和192.168.1.1之间只有一个开关。
代码:
out << (char) 0x1 << (char) 0x2 << (char) 0x3;
将调用3次operator<<
函数。
由于TCP的Nagle算法,TCP栈将在第一次operator<<
调用之后/期间立即将可用数据((char)0x1)
发送给对等体。因此,其余的数据(0x2和0x3)将进入下一个数据包。
避免1字节TCP段的解决方案:使用更大的数据集调用发送函数。
别担心,你是唯一一个有这个问题的人。肯定有解决办法。实际上,您的遗留协议存在两个问题,而不仅仅是一个。
您的旧遗留协议需要一个"应用程序消息"来适应"一个且只有一个TCP数据包"(因为它错误地将TCP面向流的协议用作面向数据包的协议)。所以我们必须确保:
- 没有"应用程序消息"被分割成多个TCP数据包(你看到的问题)
- 没有TCP数据包包含多个"应用程序消息"(你没有看到这个,但它肯定会发生)
解决方案:
<标题>问题1你必须一次给你的套接字提供所有的"消息"数据。这目前还没有发生,因为,正如其他人所概述的那样,当您使用连续的"<<"并且操作系统的底层TCP/IP堆栈没有足够的缓冲(并且有理由,为了更好的性能)时,您使用的boost流API将数据放入分隔调用的套接字中
多个解:
- 你传递一个字符缓冲区而不是单独的字符,这样你只调用一次<<
- 你忘记了boost,打开一个OS套接字并在一个调用中发送它()(在windows上,查找"winsock2"API,或者在unix/cygwin上查找"sys/socket.h")
- 如果你坚持使用Boost,寻找TCP_NODELAY选项,它在doc 中
- 如果你使用OS套接字,你必须在套接字上使用setsockopt()函数。
如果你解决了这两个问题,你就没事了!
操作系统套接字API,无论是在windows还是linux上,使用起来都有点棘手,但你将完全控制它的行为。Unix示例
标题>标题>我不确定谁会强加这样的要求,即整个有效负载在一个TCP数据包内。TCP本质上是一个流协议,发送的数据包数量和有效负载大小等许多细节都留给了操作系统的TCP堆栈实现。
我会仔细检查一下,看看这是否是您的协议的实际限制。
我同意User1的回答。您可能多次调用operator <<
;在第一次调用时,它立即通过网络发送第一个字节,然后内格尔算法开始发挥作用,因此剩余的数据在单个数据包中发送。
然而,即使打包不是问题,对小块数据频繁调用套接字发送函数这一事实也是一个大问题。在套接字上调用的每个函数都会调用一个繁重的内核模式事务(系统调用),为每个字节调用send
简直是疯了!
你应该先在内存中格式化你的信息,然后再发送。对于您的设计,我建议创建一种缓存流,它将在其内部缓冲区中积累数据并立即将其发送到底层流。
认为通过TCP套接字发送的数据是数据包是错误的。它是一个字节流,你如何构建数据是应用程序特定的。
有什么建议吗?
我建议你实现一个协议,这样接收者知道需要多少字节。一种常用的方法是发送固定大小的头,指示有效负载的字节数。
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- 如何返回一个类的两个对象相加的结果
- 如何在C++中将一个无符号的 int 转换为两个无符号的短裤?
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 两个字符串在 c++ 中不相等
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 两个文件使用彼此的功能-如何解决
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 如何确保在使用基于布尔值的两个方法之一调用方法时避免分支预测错误
- 停止cmake target_link_libraries将插件中静态库的两个对象文件链接到静态库本身
- 将fold表达式与std::一起用于两个元组
- 如何在C++中比较两个char数组
- 给定两个偶数,求出它们之间所有偶数的平方和
- 比较两个大小不等的映射c++
- C++需要帮助从用户那里获得一个整数,并确保它在另外两个整数之间
- 如何在for循环中包含两个索引值的测试条件
- 在声明中合并两个常量"std::set"(不是在运行时)
- 当使用Boost ASIO时,有效载荷拆分为两个TCP数据包,当它适合MTU时