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

时间:2016-03-01 19:48:38

标签: c++ sockets tcp

我想使用函数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)

3 个答案:

答案 0 :(得分:2)

一种相当常见的技术是读取前导消息长度字段,然后发出读取预期消息的确切大小。

无论其!不要假设第一次读取将为您提供所有八个字节(请参阅注释),或者第二次读取将为您提供整个消息/数据包。

您必须始终检查读取的字节数并发出另一个读取(或两个(或三个,或......))以获取所需的所有数据。

注意:由于TCP是一种流协议,并且由于“线路上”的数据包大小根据旨在最大化网络性能的非常神秘的算法而变化,因此您可以轻松地发出八个字节的读取,并且读取可能会返回只读三个(或七个或......)字节。保证是,除非存在不可恢复的错误,否则您将至少收到一个字节,最多只能收到您请求的字节数。因此,您必须准备进行字节地址算法并在循环中发出所有读取,这些循环将重复,直到返回所需的字节数。

答案 1 :(得分:1)

由于TCP是流式传输,因此您收到的数据并没有任何结束,直到连接关闭或出现错误。

相反,您需要在TCP之上实现自己的协议,该协议包含特定的消息结束标记,数据长度字段字段,或者可能是基于命令的协议,其中每个协议的数据命令是一个众所周知的大小。

通过这种方式,您可以读入一个小的固定大小的缓冲区,并根据需要附加到更大的(可能正在扩展的)缓冲区。在C ++中,“可能正在扩展”的部分非常简单,使用std::vectorstd::string(取决于您拥有的数据)

还有一件重要的事情要记住,因为TCP是基于流的,所以单个readrecv调用实际上可能无法获取您请求的所有数据。您需要循环接收数据,直到收到所有内容。

答案 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 实现此功能。您可以在使用此标志时请求标头。然后你可以为整个包分配足够的空间。