通过命名管道发送数据包?单字节缓冲区还是预先确定大小的?

Posted

技术标签:

【中文标题】通过命名管道发送数据包?单字节缓冲区还是预先确定大小的?【英文标题】:Packets down a named pipe? Single-byte buffer or pre-sized? 【发布时间】:2010-01-16 16:22:03 【问题描述】:

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

在我看来,有三种方法可以做到这一点。

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

在第一种情况下,我将能够创建一个已知大小的缓冲区并立即读入它。在第二种情况下,我必须使用单字节缓冲区读取。这可能完全没问题,也可能是非常低效的讽刺。

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

最好的方法是什么?

【问题讨论】:

【参考方案1】:

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

你说:

鉴于我必须提供缓冲区和要读取的缓冲区大小,

你确实...

鉴于读取命令被阻塞(我相信),

是的,除非您在文件描述符上设置 O_NONBLOCK...

我要么必须有一个缓冲区大小,以保证我永远不会欠载,

您要发送什么样的消息?你在处理什么尺寸的?千字节、兆字节还是更大?

或预先知道消息的大小。

在阅读器中拥有一个 4KB 的缓冲区并以块的形式读取消息并没有什么特别的问题。问题是知道何时到达消息的末尾。到目前为止,大多数协议都需要预先设置长度,因为这样可以轻松可靠地编写阅读器代码。

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

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

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

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

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

如果您想升级系统以通过与另一台机器的套接字连接工作,请考虑将来会发生什么,而不是使用命名管道。

我认为您应该使用数据包来设计您的系统,其中数据包标头包含数据的大小(大多数网络协议,如 TCP/IP 的执行方式)。如果有更高级别的未知大小的数据流,请按照上面概述的方式处理它。但即使在那里,如果你能提前知道整体大小会更好。

【讨论】:

谢谢。以上都是我的预期(但最好不要假设你第一次做事)。 FWIW:我希望每个消息小于 500 字节。重新“为什么需要'填充它'?”如果有一个固定的缓冲区大小并且消息长度不是该大小的倍数,那将是必要的(我相信阅读器会阻塞最后一个部分填充的缓冲区?)。 不需要填充;正如我试图指出的,如果缓冲区中有 500 个字节,read() 将返回这 500 个字节,即使它请求 4096 或其他更大的大小。你会得到一个“短读”;这不是错误。【参考方案2】:

一种简单的方法是有一个离散的数据包,其中包含一个 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 中的地址,以防止重新映射接收者进程中的现有内存。

【讨论】:

谢谢,这可能是一种更有效的做事方式。但是,我正在尝试使 IPC 尽可能便携、与语言无关且通用,因此我想将其保留为普通字节流。

以上是关于通过命名管道发送数据包?单字节缓冲区还是预先确定大小的?的主要内容,如果未能解决你的问题,请参考以下文章

如何确定/设置套接字缓冲区大小?

使用命名管道时发送数据的正确方法是啥?

TCP粘包和拆包问题

在 Java 中使用字节缓冲区创建数据包字节数组

进程间通信——消息队列

Netty之路(二)TCP拆包/粘包问题