gpt4 book ai didi

C++ TCP recv 未知缓冲区大小

转载 作者:可可西里 更新时间:2023-11-01 02:35:57 25 4
gpt4 key购买 nike

我想使用函数 recv(socket, buf, len, flags) 来接收传入的数据包。但是我在运行前不知道这个数据包的长度,所以前 8 个字节应该告诉我这个数据包的长度。我不想只分配一个任意大的 len 来完成这个所以是否可以设置 len = 8buf 是一个类型uint64_t。之后

memcpy(dest, &buf, buf)?

最佳答案

由于 TCP 是基于流的,所以我不确定您指的是什么类型的包。我假设您指的是应用程序级包。我的意思是包是由您的应用程序定义的,而不是由像 TCP 这样的底层协议(protocol)定义的。为了避免混淆,我将称它们为消息

我将展示两种可能性。首先,我将展示如何在阅读完之前不知道长度的情况下阅读消息。第二个例子将进行两次调用。首先它读取消息的大小。然后它会立即阅读整条消息。


读取数据直到消息完成

由于 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) {
// Interrupted, just try again ...
continue;
} else {
// Error occured. Throw exception.
throw IOException(strerror(errno));
}
} else if (ret == 0) {
// No data available anymore.
if (offset == 0) {
// Client did just close the connection
return std::vector<char>(); // return empty vector
} else {
// Client did close connection while sending package?
// It is not a clean shutdown. Throw exception.
throw ProtocolException("Unexpected end of stream");
}
} else if (isMessageComplete(buf)) {
// Message is complete.
buf.resize(offset + ret); // Truncate buffer
std::vector<char> msg = std::move(buf);
std::size_t msgLen = getSizeOfMessage(msg);
if (msg.size() > msgLen) {
// msg already contains the beginning of the next message.
// write it back to buf
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)) // prepare buffer for next message
return msg;
} else {
// Message is not complete right now. Read more...
offset += ret;
buf.resize(std::max(buf.size(), 2 * offset)); // double available memory
}
}
}

你必须定义 bool isMessageComplete(std::vector<char>)std::size_t getSizeOfMessage(std::vector<char>)自己。

读取包头并查看包的长度

第二种可能是先阅读header。在您的案例中,只有 8 个字节包含包的大小。之后,您就知道包裹的大小了。这意味着您可以分配足够的存储空间并立即阅读整条消息:

/// Reads n bytes from fd.
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) {
// Error occurred
throw IOException(strerror(errno));
}
} else if (ret == 0) {
// No data available anymore
if (offset == 0) return false;
else throw ProtocolException("Unexpected end of stream");
} else if (offset + ret == n) {
// All n bytes read
return true;
} else {
offset += ret;
}
}
}

/// Reads message from fd
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 {
// connection was closed
return std::vector<char>();
}
}

国旗MSG_WAITALL请求函数阻塞,直到全部数据可用。但是,您不能依赖它。如果缺少某些内容,您必须检查并再次阅读。就像我在上面所做的那样。

readNBytes(fd, buf, n)读取 n 个字节。只要连接没有从另一端关闭,该函数将不会在不读取 n 字节的情况下返回。如果连接被另一端关闭,函数返回 false .如果连接在消息中间关闭,则会抛出异常。如果发生 i/o 错误,则会抛出另一个异常。

readMessage读取 8 个字节 [ sizeof(std::unit64_t) ] 并将它们用作下一条消息的大小。然后它会读取消息。

如果你想拥有平台独立性,你应该转换size到定义的字节顺序。计算机(具有 x86 架构)正在使用 little endian。在网络流量中使用 big endian 很常见。

注意:MSG_PEEK可以为 UDP 实现此功能。您可以在使用此标志时请求 header 。然后你可以为整个包分配足够的空间。

关于C++ TCP recv 未知缓冲区大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35732112/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com