QTcpSocket 和 TCP 填充

QTcpSocket and TCP padding

美好的一天。

我正在发送一个通过 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)显然假定这两个额外的字节实际上是有效载荷,这显然弄乱了我的协议。

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

如果可能,我想避免执行整个 'create a sufficiently large buffer and preassemble the entire packet in it so it will be sent out in one go' 方法。

非常感谢。

因为被问到,发送数据的代码是:

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);

其中 'send' 声明为(SourceLen 是参数):

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);
}

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

还要注意,发送方需要在嵌入式系统上运行,因此不能使用QTcpServer

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

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

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

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

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) 的文档...

Reads at most maxSize bytes from the device into data, and returns the number of bytes read

如果您对 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.
 */