【问题标题】:How can I send an std::vector<std::string> over a UNIX socket?如何通过 UNIX 套接字发送 std::vector<std::string> ?
【发布时间】:2011-02-09 01:51:11
【问题描述】:

对于我的应用程序,我需要能够通过 UNIX 套接字(本地)发送 std::vector&lt;std::string&gt;,并在套接字的另一端获取向量的副本。使用相对于向量大小的O(1) 消息(即不为向量中的每个字符串发送消息)最简单的方法是什么?

因为这一切都在同一个主机上,而且因为我控制着套接字的两端,所以我不关心机器特定的问题,例如 endinness 或向量/字符串表示。

出于各种原因,我想避免使用任何外部库。

【问题讨论】:

    标签: c++ unix sockets


    【解决方案1】:

    std::string 不会阻止您在字符串中包含 nuls。只有当您尝试将这些与 nul 敏感 API 一起使用时,您才会遇到麻烦。我怀疑你会通过在数组的大小前面加上每个字符串的长度来序列化数组。

    ...
    long length = htonl( vec.size() );
    write( socket, &length, sizeof(length) );
    for ( int i = 0; i < vec.size(); ++i ) {
        length = htonl( vec[i].length() );
        write( socket, &length, sizeof(length) );
        write( socket, vec[i].data(), vec[i].length() );
    }
    ...
    

    类似的解包:

    ...
    std::vector vectorRead;
    long size = 0;
    read( socket, &size, sizeof( size ) );
    size = ntohl( size );
    for ( int i = 0; i < size; ++i ) {
        std::string stringRead;
        long length = 0;
        read( socket, &length, sizeof( length ) );
        length = ntohl( length );
        while ( 0 < length ) {
            char buffer[1024];
            int cread;
            cread = read( socket, buffer, min( sizeof( buffer ), length ) );
            stringRead.append( buffer, cread );
            length -= cread;
        }
        vectorRead.push_back( stringRead );
    }
    ...
    

    【讨论】:

    • 我可以保证字符串中没有空值。
    • 添加长度还有一个好处,就是告诉您要从套接字读取多少数据。读取所需的数据比在字符串中搜索 nul 分隔符更容易。您可以通过将所有字符串合并为一个,然后将该字符串的长度写入套接字,以便远程端知道要读取多少字节,然后在读取 blob 后将其分解,从而获得两全其美。
    • 我真的希望这不是真正的代码,而只是一个算法。你真的真的真的想在每次系统调用后检查错误。
    • 感谢 Nikolai 的提醒,错误检查留给读者作为练习。 ;)
    【解决方案2】:

    为发送和接收打包数据结构通常称为序列化。

    您可以使用一个选项:Boost serialization library 具有序列化 STL 向量的能力。

    另一种方法是自己动手——在这种情况下应该不难。例如,您可以将向量的所有字符串连接到一个字符串中(每个组成部分 NULL 分开)并发送该缓冲区,然后以类似方式恢复它。

    【讨论】:

    • 你能举个例子(用代码)来说明如何打包和解包字符串缓冲区吗?
    • @Mike:我相信 Eli 的回答包含一个完全合理的方法来解决这个问题,它不依赖于外部库。
    • @Billy:公平地说,Mike 评论了而我正在添加那段
    【解决方案3】:

    我确定我会因此而被 C++ 狂热者大喊大叫,但请尝试writev(2)(又名scatter/gather I/O)。不过,无论如何,您都必须在接收端处理零分隔符。

    【讨论】:

    • * 这个 C++ 狂热者没有大喊大叫。 UNIX 是一个 C 系统,使用遗留的 C API 完全没问题。
    • 如果格式是{元素个数, {元素长度... }, {元素数据... } } 那么readv可以有效地分发数据。
    • 不,永远不要这样做。永远不要对这个级别的输入做任何假设。
    【解决方案4】:

    我最终采用的解决方案是以&lt;string1&gt;\0&lt;string2&gt;\0...&lt;stringN&gt;\0 的形式序列化字符串向量(预先发送上述字符串的长度)。虽然 David 正确指出这不适用于 std::string 包含 null 的情况,但我可以保证我的应用程序不会出现这种情况。

    【讨论】:

      【解决方案5】:

      没有办法通过套接字发送向量,即使在同一台机器上(或者甚至在同一进程中)。 这有两个问题:

      1. vector 和 string 都维护指向原始内存的内部指针。这会阻止将向量 发送到另一个进程
      2. 向量和字符串的 dtors 将要删除该指针。套接字操作将对您的对象(包括原始指针的值)进行 memcpy,您将获得双重删除。

      所以规则是这样的:为了通过套接字发送对象,它必须能够被 memcpy'd。有几种方法可以做到这一点

      1. 序列化向量 ICE 之类的东西擅长生成这些序列化 http://www.zeroc.com/ 这些有明显的开销
      2. 创建与向量和字符串具有相同接口但能够被 memcpy'd 的东西
      3. 创建看起来像向量的只读版本发送端可以是常规向量接收端可以将接收缓冲区重新解释为只读实现

      一般情况下,第 2 项很难做到,但有一定的限制是可能的。对于高性能应用程序,无论如何您都不会使用矢量。

      数字 3 几乎适用于所有用例,因为阅读器很少修改 recv 缓冲区的内容。如果阅读器不需要随机访问迭代器,并且可以使用 ForwardIterators,则序列化非常简单:分配一个可以容纳所有字符串的缓冲区,每个字符串的加号和整数表示长度,再加上一个整数表示向量的大小。

      结果可以重新解释为用户定义的结构,该结构是只读字符串的只读集合。因此,无需太多麻烦,您至少可以在读取方面获得 O(1) 。

      要在发送端获得 O(1),您必须使用方法 2。我已经这样做了,我知道我的应用程序永远不会使用超过 X 长度的字符串,并且向量永远不会持有超过 Y 项。诀窍是修复容量我永远不必去堆内存。缺点是您要发送每个字符串的全部容量,而不仅仅是使用的内容。但是在许多情况下,发送所有内容比尝试压缩它要快得多,尤其是如果您在同一台机器上 - 在这种情况下,您可以将此结构放在共享内存中并通知 recv 应用程序只查找它。

      您可能希望查看 boost interprocess 以了解有关如何制作无需序列化即可通过套接字推入的容器的更多想法。

      【讨论】:

        猜你喜欢
        • 2013-09-11
        • 2019-10-06
        • 2020-08-31
        • 1970-01-01
        • 1970-01-01
        • 2017-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多