【问题标题】:QTcpSocket and TCP paddingQTcpSocket 和 TCP 填充
【发布时间】:2017-07-13 07:28:53
【问题描述】:

美好的一天。

我正在发送一个自定义协议,用于通过 TCP 进行日志记录,如下所示:

Timestamp (uint32_t -> 4 bytes)
Length of message (uint8_t -> 1 byte)
Message (char -> Length of message)

时间戳转换为 BigEndian 用于传输,一切正常,除了一个小细节:填充

时间戳是自己发送的,但是我的应用程序(在 Ubuntu 下使用 BSD 套接字)不只是发送时间戳(4 个字节),而是自动向消息附加两个字节的填充。

Wireshark 正确识别了这一点并将两个无关字节标记为填充,但是 QTcpSocket(Qt 5.8,mingw 5.3.0)显然假定这两个额外字节实际上是有效负载,这显然会弄乱我的协议。

我有什么方法可以“教”QTcpSocket 忽略填充(就像它应该的那样)或任何方法来摆脱填充?

如果可能的话,我想避免使用整个“创建一个足够大的缓冲区并将整个数据包预先组装在其中,以便一次性发送出去”的方法。

非常感谢。

因为被问到了,所以用来发送数据的代码是:

return 
    C->sendInt(entry.TS)        &&
    C->send(&entry.LogLen, 1)   &&
    C->send(&entry.LogMsg, entry.LogLen);

其中 sendInt 被声明为(Src 是参数):

Src = htonl(Src);
return send(&Src, 4);

“发送”声明为(Source 和 Len 是参数):

char *Src = (char *)Source;
while(Len) {
    int BCount = ::send(Sock, Src, Len, 0);
    if(BCount < 1) return false;
    Src     += BCount;
    Len     -= BCount;
}
return true;

::send 是标准的 BSD 发送函数。

阅读是通过QTcpSocket:

uint32_t timestamp;
if (Sock.read((char *)&timestamp, sizeof(timestamp)) > 0)
{
    uint8_t logLen;
    char message[256];            
    if (Sock.read((char *)&logLen, sizeof(logLen)) > 0  &&
        logLen > 0                                      &&
        Sock.read(message, logLen) == logLen
    ) addToLog(qFromBigEndian(timestamp), message);
}

Sock是QTcpSocket实例,已经连接到主机,addToLog是处理函数。

另外需要说明的是,发送端需要在嵌入式系统上运行,因此不能使用QTcpServer。

【问题讨论】:

  • 请编辑您的问题以显示用于编码/发送数据的代码。使用 TCP 不应该有任何填充。
  • 您可能会遇到包括校验和在内的最小以太网帧大小,即 64 字节,您在 Wireshark 中看不到。由于 Wireshark 认识到这一点,我想这就是它的本质,并添加了填充。另一方面,这不应该传播到用户空间,并且填充应该显示在以太网部分。
  • @RolfWinter 这很可能,是的。 Wireshark 确实识别它并将它放在正确的部分,QTcpSocket 是一个搞砸了。

标签: c++ qt sockets tcp qtcpsocket


【解决方案1】:

您的阅读逻辑似乎不正确。你有...

uint32_t timestamp;
if (Sock.read((char *)&timestamp, sizeof(timestamp)) > 0)
{
    uint8_t logLen;
    char message[256];            
    if (Sock.read((char *)&logLen, sizeof(logLen)) > 0  &&
        logLen > 0                                      &&
        Sock.read(message, logLen) == logLen
    ) addToLog(qFromBigEndian(timestamp), message);
}

来自QTcpSocket::read(data, MaxSize)它的文档...

从设备中读取最多 maxSize 个字节到数据中,并返回 读取的字节数

如果您对Sock.read 的调用之一读取了部分数据怎么办?您基本上丢弃了这些数据,而不是缓冲它以供下次重用。

假设您有一个适当范围的QByteArray...

QByteArray data;

你的阅读逻辑应该更符合...

/*
 * Append all available data to `data'.
 */
data.append(Sock.readAll());

/*
 * Now repeatedly read/trim messages from data until
 * we have no further complete messages.
 */
while (contains_complete_log_message(data)) {
  auto message = read_message_from_data(data);
  data = data.right(data.size() - message.size());
}

/*
 * At this point `data' may be non-empty but doesn't
 * contain enough data for a complete message.
 */

【讨论】:

  • 是的,我想我自己现在已经意识到了这个错误。我太习惯了我相信的 BSD 方式。因为这些会阻塞,直到有足够的数据可用。我现在觉得很傻。
【解决方案2】:

如果填充的长度始终是固定的,那么只需添加 socket-&gt;read(2); 以忽略 2 个字节。

另一方面,这可能只是冰山一角。你用什么来读写?

【讨论】:

  • 我添加了更多细节
  • 你去。现在你已经得到了我想的一切。
  • 您可以尝试在套接字上使用 QDataStream 进行读取吗?
  • QDataStream 不做千斤顶,但我检查了它是如何使用的链接。好吧,那和@G.M.上面的答案是我在链接之后阅读的。犯了这么愚蠢的错误,我觉得很傻。
  • @RefugnicEternium QDataStream 如果正确使用startTransaction() 和commitTransaction() 确实可以解决您的问题
【解决方案3】:

您不应调用 send 三次,而只能调用一次。要转换为 BigEndian,您可以使用 Qt functions 并将所有内容写入单个缓冲区,并且只调用一次发送。这不是您想要的,但我认为这是您需要做的并且应该很容易,因为您已经知道消息的大小。您也无需离开 Qt 世界即可发送消息。

【讨论】:

  • 我很抱歉,我可能应该提到,发送方根本没有使用 Qt,因为代码需要适用于嵌入式系统。
猜你喜欢
  • 1970-01-01
  • 2015-01-10
  • 1970-01-01
  • 2014-05-10
  • 1970-01-01
  • 2014-06-08
相关资源
最近更新 更多