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' 声明为(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 *)×tamp, 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
。
如果填充的长度始终是固定的,那么只需添加 socket->read(2);
即可忽略这 2 个字节。
另一方面,这可能只是冰山一角。你用什么读写?
你不应该调用 send 三次而只能调用一次。要转换为 BigEndian,您可以使用 Qt functions 并将所有内容写入单个缓冲区,并且只调用一次发送。这不是您想要的,但我认为这是您需要做的,而且应该很容易,因为您已经知道消息的大小。您也不需要离开 Qt 世界来发送消息。
您的阅读逻辑似乎不正确。你有...
uint32_t timestamp;
if (Sock.read((char *)×tamp, 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.
*/
美好的一天。
我正在发送一个通过 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' 声明为(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 *)×tamp, 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
。
如果填充的长度始终是固定的,那么只需添加 socket->read(2);
即可忽略这 2 个字节。
另一方面,这可能只是冰山一角。你用什么读写?
你不应该调用 send 三次而只能调用一次。要转换为 BigEndian,您可以使用 Qt functions 并将所有内容写入单个缓冲区,并且只调用一次发送。这不是您想要的,但我认为这是您需要做的,而且应该很容易,因为您已经知道消息的大小。您也不需要离开 Qt 世界来发送消息。
您的阅读逻辑似乎不正确。你有...
uint32_t timestamp;
if (Sock.read((char *)×tamp, 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.
*/