我想使用函数recv(socket, buf, len, flags)
来接收传入的数据包。但是我不知道这个数据包在运行时之前的长度,所以前8个字节应该告诉我这个数据包的长度。我不想只是分配一个任意大len
来完成此任务,因此可以设置len = 8
buf
uint64_t
类型。memcpy(dest, &buf, buf)
。然后是
def f(x) {
return x**4
}
range_min = 2
range_max = 4
range = range_max - range_min
sample_size = 100000
sum = 0
loop sample_size times {
sum += f(range_min + range * U) // where U is a Uniform(0,1) random number
}
estimated_area = range * (sum / sample_size)
?
答案 0 :(得分:2)
一种相当常见的技术是读取前导消息长度字段,然后发出读取预期消息的确切大小。
无论其!不要假设第一次读取将为您提供所有八个字节(请参阅注释),或者第二次读取将为您提供整个消息/数据包。
您必须始终检查读取的字节数并发出另一个读取(或两个(或三个,或......))以获取所需的所有数据。
注意:由于TCP是一种流协议,并且由于“线路上”的数据包大小根据旨在最大化网络性能的非常神秘的算法而变化,因此您可以轻松地发出八个字节的读取,并且读取可能会返回只读三个(或七个或......)字节。保证是,除非存在不可恢复的错误,否则您将至少收到一个字节,最多只能收到您请求的字节数。因此,您必须准备进行字节地址算法并在循环中发出所有读取,这些循环将重复,直到返回所需的字节数。
答案 1 :(得分:1)
由于TCP是流式传输,因此您收到的数据并没有任何结束,直到连接关闭或出现错误。
相反,您需要在TCP之上实现自己的协议,该协议包含特定的消息结束标记,数据长度字段字段,或者可能是基于命令的协议,其中每个协议的数据命令是一个众所周知的大小。
通过这种方式,您可以读入一个小的固定大小的缓冲区,并根据需要附加到更大的(可能正在扩展的)缓冲区。在C ++中,“可能正在扩展”的部分非常简单,使用std::vector
和std::string
(取决于您拥有的数据)
还有一件重要的事情要记住,因为TCP是基于流的,所以单个read
或recv
调用实际上可能无法获取您请求的所有数据。您需要循环接收数据,直到收到所有内容。
答案 2 :(得分:1)
由于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) {
// 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>)
。
第二种可能性是首先阅读标题。只包含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 && 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 实现此功能。您可以在使用此标志时请求标头。然后你可以为整个包分配足够的空间。