You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用boost::asio::read_some依次接收客户端发送的多段序列化大数据?

用boost::asio::read_some依次接收多段二进制数据的解决方案

首先得明确一个核心点:TCP是无边界的字节流协议,你直接发送两个大对象的序列化数据,服务器根本没法区分哪里是第一个对象的结尾、第二个对象的开头。所以第一步必须让客户端配合,给每个对象的二进制数据加上长度前缀——先发送对象的序列化长度,再发送实际数据。服务器端通过先读取长度,再按长度读取完整的对象数据,就能准确区分两个对象了。

下面是具体的实现步骤和代码示例:

一、先搞定客户端的发送逻辑(必须配合)

客户端在发送每个对象前,要先把对象的序列化长度转换成网络字节序(解决不同机器的大小端问题),然后用boost::asio::write依次发送长度前缀和对象数据:

#include <boost/asio.hpp>
#include <vector>
#include <cstdint>

using boost::asio::ip::tcp;

// 发送单个序列化对象的辅助函数
void send_serialized_object(tcp::socket& socket, const std::vector<char>& serialized_data) {
    // 把长度转成网络字节序(这里用4字节的uint32_t,适合最大4GB的对象)
    uint32_t data_len = htonl(static_cast<uint32_t>(serialized_data.size()));
    // 先发送长度前缀
    boost::asio::write(socket, boost::asio::buffer(&data_len, sizeof(data_len)));
    // 再发送实际的序列化数据
    boost::asio::write(socket, boost::asio::buffer(serialized_data));
}

// 调用示例:发送两个对象
// int main() {
//     boost::asio::io_context io_context;
//     tcp::socket socket(io_context);
//     socket.connect(tcp::endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 12345));
//
//     std::vector<char> obj1_data = ...; // 第一个对象的序列化结果
//     std::vector<char> obj2_data = ...; // 第二个对象的序列化结果
//
//     send_serialized_object(socket, obj1_data);
//     send_serialized_object(socket, obj2_data);
//
//     socket.close();
//     return 0;
// }

二、服务器端用read_some实现可靠接收

read_some的特点是只读取当前可用的数据,不会保证一次读完你需要的所有字节,所以我们需要封装一个read_exact辅助函数,循环调用read_some直到读取到指定长度的数据。然后按照「读长度→读对应数据」的流程依次接收两个对象:

1. 封装read_exact辅助函数

#include <boost/asio.hpp>
#include <vector>
#include <cstdint>
#include <stdexcept>

using boost::asio::ip::tcp;

// 从socket读取指定长度的字节,直到读满为止
void read_exact(tcp::socket& socket, std::vector<char>& buffer, std::size_t required_bytes) {
    std::size_t total_read = 0;
    buffer.clear();
    buffer.resize(required_bytes);
    boost::system::error_code ec;

    while (total_read < required_bytes && !ec) {
        // 每次读取剩余需要的字节数
        std::size_t bytes_read = socket.read_some(
            boost::asio::buffer(buffer.data() + total_read, required_bytes - total_read),
            ec
        );
        if (!ec) {
            total_read += bytes_read;
        }
    }

    if (ec) {
        throw std::runtime_error("Read failed: " + ec.message());
    }
}

2. 服务器接收两个对象的主逻辑

int main() {
    try {
        boost::asio::io_context io_context;
        // 监听12345端口
        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 12345));
        std::cout << "Waiting for client connection..." << std::endl;

        tcp::socket socket(io_context);
        acceptor.accept(socket); // 等待客户端连接
        std::cout << "Client connected." << std::endl;

        // ===== 接收第一个对象 =====
        std::vector<char> len_buffer;
        // 先读4字节的长度前缀
        read_exact(socket, len_buffer, sizeof(uint32_t));
        // 转成主机字节序
        uint32_t obj1_len = ntohl(*reinterpret_cast<const uint32_t*>(len_buffer.data()));
        // 读取第一个对象的完整数据
        std::vector<char> obj1_data;
        read_exact(socket, obj1_data, obj1_len);
        std::cout << "Received first object, size: " << obj1_len << " bytes" << std::endl;
        // 这里可以把obj1_data反序列化成你的对象

        // ===== 接收第二个对象 =====
        read_exact(socket, len_buffer, sizeof(uint32_t));
        uint32_t obj2_len = ntohl(*reinterpret_cast<const uint32_t*>(len_buffer.data()));
        std::vector<char> obj2_data;
        read_exact(socket, obj2_data, obj2_len);
        std::cout << "Received second object, size: " << obj2_len << " bytes" << std::endl;
        // 这里可以把obj2_data反序列化成你的对象

        socket.close();
        std::cout << "Connection closed." << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

三、关键注意事项

  • 字节序处理:必须用htonl/ntohl(或者htonll/ntohll处理超大对象)转换长度,否则不同大小端的机器之间通信会出错。如果需要更通用的处理,可以用Boost.Endian库。
  • 超大对象支持:如果你的对象序列化后超过4GB,把长度类型换成uint64_t即可,对应的字节序函数换成htonll/ntohll(注意有些平台可能需要手动实现,或者用Boost的字节序工具)。
  • 异常处理:示例中用了try-catch捕获异常,实际项目中可以根据需求选择用error_code版本的接口替代异常抛出。
  • 异步场景适配:如果你的服务器是异步架构,逻辑类似——在异步回调中循环读取直到满足长度要求,不过需要用async_read_some并维护接收状态(已读字节数、目标长度等)。

内容的提问来源于stack exchange,提问作者user9414424

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:20:49