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

标签 c++ sockets tcp

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

相关文章:

用于简单聊天的 C# TCP 服务器

c++ - 在 Linux 的情况下替换 Windows 特定的 HANDLE、事件创建和同步 API

c++ - 问题向下转换到子类,多态性不起作用

c++ - 如果它是成员变量,是否有可以包装成员指针的 boost 或 STL 类?

C++ - string.replace 不起作用

c# - Socket.Send 和 Stream.Write 有什么区别? (与 tcp ip 连接相关)

Java 通过套接字发送和接收文件 (byte[])

带套接字 tcp 的 C# ssl/tls

ios - 带有 TCP 的 Swift 网络框架

java - Android/Java - ServerSocket 和 Socket 通信的 ClassNotFoundException