代码之家  ›  专栏  ›  技术社区  ›  Joe

把一个命名管道打包?单字节缓冲区还是预调整大小?

  •  1
  • Joe  · 技术社区  · 14 年前

    我想通过命名管道在两个程序之间发送“数据包”(即离散消息)。假设我必须提供一个缓冲区和一个缓冲区大小 read ,并且考虑到read命令是阻塞的(我相信),我要么必须有一个缓冲区大小来保证永远不会运行不足,要么提前知道消息的大小。我不希望发送程序必须知道缓冲区的大小并将其填充。

    正如我所见,有三种方法可以做到这一点。

    1. 在每个包前面加上发送消息的大小,以便侦听程序可以读取那么多字节。
    2. 一次从管道中读取一个字节,然后侦听流的特殊结束值。
    3. 更好的方法

    在第一种情况下,我可以创建一个已知大小的缓冲区,并立即将其读取。在第二种情况下,我必须使用一个字节的缓冲区来读取。这可能是完全正常的,也可能是一场低效的嘲弄。

    我选择第二种方法的唯一原因是为了更灵活的输入(例如,如果我需要的话,手动交互)。

    哪条路最好?

    2 回复  |  直到 14 年前
        1
  •  3
  •   Jonathan Leffler    14 年前

    对于命名管道,读和写是(或可以是)原子的。在限制范围内,如果将1024字节写入管道,则在另一端查找至少1024字节的读取调用实际上将接收1024字节,即使在读取时管道中有更多数据。此外,如果命名管道中只有1024个字节,并且读取请求为4096个字节,则在第一次尝试时它将获得1024个字节,而在随后的尝试中只会得到块。

    你说:

    考虑到我必须提供一个缓冲区和一个缓冲区大小来读取,

    你做…

    鉴于read命令正在阻塞(我相信),

    是的,除非你在文件描述符上设置了“非块”…

    我要么必须有一个缓冲区大小,以保证我不会运行不足,

    你要发送什么样的信息?你要处理多大尺寸的?千字节,兆字节,更大?

    或者提前知道消息的大小。

    比如说,在读卡器中有一个4KB的缓冲区,并以块的形式读取消息,没有什么特别的问题。问题是知道你何时到达信息的末尾。到目前为止,大多数协议都需要前面的长度,因为这样可以方便地可靠地编写读卡器代码。

    如果你要做一个“结束流”(EOS)标记,你要做的是“带内信号”。这会带来麻烦。你要用什么角色?当该字符出现在数据中时会发生什么?您需要一个转义机制,例如一个表示“下一个字符不是EOS标记”的字符。例如,在与编程相关的文本中,反斜杠用于此目的。在终端,Control-V通常起作用。

    我不希望发送程序必须知道缓冲区的大小并将其填充。

    为什么发送者很难知道缓冲区的大小?为什么它需要“垫掉”?

    如果您处理的是大量数据(从千字节向上),那么单字符解决方案不太可能产生可接受的性能。我认为最好是让发送者能够确定数据包的大小并告诉读者,或者设计协议以便对数据包的大小有限制。如果您需要传输任意数量的数据,请制定一个协议,其中说明:

    • 大量未知总大小的数据即将出现。
    • 对于每个子数据包,消息显示“这是大小为nn-kb的子数据包”。
    • 对于最后一个子包,大小可能会更短——这可以表示“大量数据的结束”。
    • 如果最后一个子包是“全尺寸”,您可以发送一个空的最后一个包来指示EOS。
    • 或者,如果子包的大小可变,则可以始终发送显式EOS包。

    另外,如果您不想使用命名管道,而是想升级您的系统,使其通过套接字连接工作到另一台计算机,那么将来会发生什么。

    我认为您应该用包头包含数据大小的包设计您的系统(大多数网络协议,如TCP/IP,都是这样做的)。如果有一个更高级别的未知大小的数据流,请按照上面概述的路线处理。但即使在那里,如果你能提前知道整体规模,那就更好了。

        2
  •  1
  •   WarrenB    14 年前

    一种简单的方法是拥有一个包含ftok(基于命名管道)的离散数据包和指向共享内存中使用ftok返回值分配的以空结尾字符串的指针。所有其他离散信息都可以在包结构中传递。

    发件人:

    packet.ident = ftok("./mynamedpipe");
    packet.pointer = shmget(packet.ident, sizeof(message), IPC_CREAT|IPC_EXCL);
    strcpy(packet.pointer, message);
    

    接收机:

    message = shmat(packet.ident, NULL, NULL);   
    

    注意,shmat中的地址没有明确提供,以防止在接收进程中重新映射现有内存。