正如 Dashogun 和 Charlie Martin 所指出的,这是一个大问题。他们的某些部分答案不准确,所以我也将回答。
我有兴趣编写单独的程序模块,这些模块作为独立线程运行,我可以与管道挂钩。
小心尝试使用管道作为单个进程的线程之间的通信机制。因为您将在单个进程中同时打开管道的读取和写入端,所以您永远不会得到 EOF(零字节)指示。
如果您真的指的是进程,那么这就是经典 Unix 构建工具的方法的基础。许多标准的 Unix 程序都是过滤器,它们从标准输入中读取,以某种方式对其进行转换,然后将结果写入标准输出。例如,tr
、sort
、grep
和cat
都是过滤器,仅举几例。当您操作的数据允许时,这是一个很好的范例。并非所有的数据操作都有助于这种方法,但有很多。
动机是我可以完全独立地编写和测试每个模块,甚至可以用不同的语言编写它们,或者在不同的机器上运行不同的模块。
好点。请注意,机器之间并没有真正的管道机制,尽管您可以使用诸如rsh
or (better)之类的程序来接近它ssh
。然而,在内部,这些程序可能会从管道读取本地数据并将该数据发送到远程机器,但它们通过套接字在机器之间进行通信,而不是使用管道。
这里有各种各样的可能性。我已经使用管道一段时间了,但我不熟悉其行为的细微差别。
好的; 提问是一种(好的)学习方式。当然,实验是另一回事。
似乎接收端会阻塞等待输入,这是我所期望的,但是发送端有时会阻塞等待某人从流中读取吗?
是的。管道缓冲区的大小是有限制的。传统上,这非常小 - 4096 或 5120 是常见值。您可能会发现现代 Linux 使用了更大的值。您可以使用fpathconf()
_PC_PIPE_BUF 来找出管道缓冲区的大小。POSIX 只要求缓冲区为 512(即 _POSIX_PIPE_BUF 为 512)。
如果我将 eof 写入流,我可以继续写入该流直到我关闭它吗?
从技术上讲,没有办法将 EOF 写入流;您关闭管道描述符以指示 EOF。如果您将 control-D 或 control-Z 视为 EOF 字符,那么就管道而言,它们只是常规字符 - 它们仅在以规范模式运行的终端(熟,或正常)。
命名管道和未命名管道的行为是否存在差异?
是的,没有。最大的区别是未命名管道必须由一个进程设置,并且只能由该进程和作为共同祖先共享该进程的子进程使用。相比之下,命名管道可以由以前未关联的进程使用。下一个重大差异是第一个的结果。使用未命名管道,您可以从单个函数(系统)调用返回两个文件描述符,但您使用常规函数pipe()
打开 FIFO 或命名管道。open()
(在打开它之前,必须有人使用调用创建一个 FIFO mkfifo()
;未命名管道不需要任何此类事先设置。)但是,一旦您打开了文件描述符,命名管道和未命名管道之间几乎没有区别。
我首先用命名管道打开管道的哪一端有关系吗?
不,第一个打开 FIFO 的进程(通常)会阻塞,直到有一个进程打开另一端。如果您打开它进行读写(非常规但可能),那么您将不会被阻止;如果您使用 O_NONBLOCK 标志,您将不会被阻止。
不同 Linux 系统之间管道的行为是否一致?
是的。在我使用过的任何系统上,我都没有听说过或遇到过任何管道问题。
管道的行为是否取决于我使用的 shell 或我配置它的方式?
否:管道和 FIFO 独立于您使用的外壳。
如果我想以这种方式使用管道,是否还有其他问题我应该问或我应该注意的问题?
请记住,您必须在将要写入的进程中关闭管道的读取端,并在将要读取的进程中关闭管道的写入端。如果要通过管道进行双向通信,请使用两个单独的管道。如果您创建复杂的管道布置,请注意死锁 - 这是可能的。然而,线性管道不会死锁(尽管如果第一个进程从不关闭其输出,则下游进程可能会无限期地等待)。
我在上面和对其他答案的评论中都观察到管道缓冲区通常仅限于非常小的尺寸。@Charlie Martin 反驳说,某些版本的 Unix 具有动态管道缓冲区,这些缓冲区可能非常大。
我不确定他心中有哪些。我在 Solaris、AIX、HP-UX、MacOS X、Linux 和 Cygwin / Windows XP 上使用了以下测试程序(结果如下):
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
static const char *arg0;
static void err_syserr(char *str)
{
int errnum = errno;
fprintf(stderr, "%s: %s - (%d) %s\n", arg0, str, errnum, strerror(errnum));
exit(1);
}
int main(int argc, char **argv)
{
int pd[2];
pid_t kid;
size_t i = 0;
char buffer[2] = "a";
int flags;
arg0 = argv[0];
if (pipe(pd) != 0)
err_syserr("pipe() failed");
if ((kid = fork()) < 0)
err_syserr("fork() failed");
else if (kid == 0)
{
close(pd[1]);
pause();
}
/* else */
close(pd[0]);
if (fcntl(pd[1], F_GETFL, &flags) == -1)
err_syserr("fcntl(F_GETFL) failed");
flags |= O_NONBLOCK;
if (fcntl(pd[1], F_SETFL, &flags) == -1)
err_syserr("fcntl(F_SETFL) failed");
while (write(pd[1], buffer, sizeof(buffer)-1) == sizeof(buffer)-1)
{
putchar('.');
if (++i % 50 == 0)
printf("%u\n", (unsigned)i);
}
if (i % 50 != 0)
printf("%u\n", (unsigned)i);
kill(kid, SIGINT);
return 0;
}
我很想从其他平台获得额外的结果。这是我找到的尺寸。我必须承认,所有结果都比我预期的要大,但是当谈到缓冲区大小时,查理和我可能正在争论“相当大”的含义。
- 8196 - 适用于 IA-64 的 HP-UX 11.23(fcntl(F_SETFL) 失败)
- 16384 - 索拉里斯 10
- 16384 - MacOS X 10.5(O_NONBLOCK 不起作用,尽管 fcntl(F_SETFL) 没有失败)
- 32768 - AIX 5.3
- 65536 - Cygwin / Windows XP(O_NONBLOCK 不起作用,尽管 fcntl(F_SETFL) 没有失败)
- 65536 - SuSE Linux 10(和 CentOS)(fcntl(F_SETFL) 失败)
从这些测试中清楚的一点是,O_NONBLOCK 在某些平台上与管道一起工作,而在其他平台上则不工作。
该程序创建一个管道和分叉。孩子关闭管道的写入端,然后进入睡眠状态,直到它收到信号——这就是 pause() 所做的。然后父级关闭管道的读取端,并在写入描述符上设置标志,这样它就不会在尝试在完整管道上写入时阻塞。然后它循环,一次写一个字符,为每个写的字符打印一个点,每 50 个字符打印一个计数和换行符。当它检测到写入问题(缓冲区已满,因为孩子没有读东西)时,它会停止循环,写入最终计数并杀死孩子。