由于TCP是基于流的,我不确定你指的是什么类型的数据包。我会假设你是指应用程序级别的数据包。我的意思是这些数据包是由你的应用程序定义的,而不是由底层协议如TCP定义的。为了避免混淆,我将它们称为消息。
我将展示两种可能性。首先,我将展示如何在读取完成之前不知道消息长度的情况下读取消息。第二个例子将进行两次调用。首先它读取消息的大小。然后一次性读取整个消息。
读取数据直到消息完整
由于TCP是基于流的,当您的缓冲区不够大时,您不会丢失任何数据。因此,您可以读取固定数量的字节。如果有数据缺失,您可以再次调用recv
。这里是一个详细的例子。我只是写了一下,没有测试过。希望一切都能正常工作。
std::size_t offset = 0;
std::vector<char> buf(512);
std::vector<char> readMessage() {
while (true) {
ssize_t ret = recv(fd, buf.data() + offset, buf.size() - offset, 0);
if (ret < 0) {
if (errno == EINTR) {
continue;
} else {
throw IOException(strerror(errno));
}
} else if (ret == 0) {
if (offset == 0) {
return std::vector<char>();
} else {
throw ProtocolException("Unexpected end of stream");
}
} else if (isMessageComplete(buf)) {
buf.resize(offset + ret);
std::vector<char> msg = std::move(buf);
std::size_t msgLen = getSizeOfMessage(msg);
if (msg.size() > msgLen) {
buf.resize(msg.size() - msgLen)
std::memcpy(buf.data(), msg.data() + msgLen, msg.size() - msgLen);
msg.resize(msgLen);
}
buf.resize(std::max(2*buf.size(), 512))
return msg;
} else {
offset += ret;
buf.resize(std::max(buf.size(), 2 * offset));
}
}
}
你需要自己定义
bool isMessageComplete(std::vector<char>)
和
std::size_t getSizeOfMessage(std::vector<char>)
。
读取头文件并检查包的长度
第二种可能性是先读取头文件,只需8个字节,其中包含包的大小。之后,你就知道了包的大小。这意味着你可以分配足够的存储空间,并一次性读取整个消息:
bool readNBytes(int fd, void *buf, std::size_t n) {
std::size_t offset = 0;
char *cbuf = reinterpret_cast<char*>(buf);
while (true) {
ssize_t ret = recv(fd, cbuf + offset, n - offset, MSG_WAITALL);
if (ret < 0) {
if (errno != EINTR) {
throw IOException(strerror(errno));
}
} else if (ret == 0) {
if (offset == 0) return false;
else throw ProtocolException("Unexpected end of stream");
} else if (offset + ret == n) {
return true;
} else {
offset += ret;
}
}
}
std::vector<char> readMessage(int fd) {
std::uint64_t size;
if (readNBytes(fd, &size, sizeof(size))) {
std::vector buf(size);
if (readNBytes(fd, buf.data(), size)) {
return buf;
} else {
throw ProtocolException("Unexpected end of stream");
}
} else {
return std::vector<char>();
}
}
MSG_WAITALL
标志要求函数阻塞,直到完整的数据可用。但是,您不能依赖它。您必须检查它,并在缺少某些内容时再次读取。就像我上面所做的一样。
readNBytes(fd, buf, n)
读取
n 字节。只要连接没有从另一端关闭,该函数将不会返回而不读取
n 字节。如果连接已由另一端关闭,则该函数返回
false
。如果在传输消息过程中连接被关闭,则会抛出异常。如果发生输入/输出错误,则会抛出另一个异常。
readMessage
读取8个字节 [
sizeof(std::unit64_t)
] 并将其用作下一个消息的大小。然后它读取消息。
如果您想要跨平台支持,应该将
size
转换为定义的字节顺序。计算机(使用x86架构)使用小端字节序。在网络传输中使用大端字节序是常见的。
注意: 使用
MSG_PEEK
可以实现UDP的此功能。您可以在使用此标志时请求标头。然后,您可以为整个包分配足够的空间。