在 Linux 上实现双向管道

Implementing bidirectional pipe on Linux

我们要在程序中执行一个交互式命令行实用程序 (Samba smbclient)。 smbclient 就像一个典型的交互式 ftp 客户端,您向它输入命令(在其提示后)并访问网络共享并 returns 您得到结果。对于我们的程序,我们要输入一个命令,检索结果,输入下一个命令,等等..

这是我们的示例程序。 /root/testfifo 是已通过 mkfifo 在 bash 中创建的 fifo。

int main()
{
    const char *cmd = "smbclient //machine/share \
    -U domain/user%pwd  > /root/testfifo";

    FILE *out = popen(cmd, "w");
    if (!out)
    {
        return 0;
    }

    // Write the command to smbclient via 1st pipe
    fputs("ls\n", out);
    fflush(out);

    // Read the result from smbclient via 2nd pipe
    FILE *in = fopen("/root/testfifo", "r");
     
    if (!in)
    {
        return 0;
    }
    
    char buf[1000];
    // Pending here..
    fgets(buf, 1000, in);
    
    fclose(in);
    pclose(out);
    
    return 0;
}

问题是,程序在 fgets 行等待来自 in 的输入。但是,如果我首先删除第二个管道 in、运行 我们的程序,然后打开一个单独的终端从同一个管道读取(例如,tail -f /root/testfifo),它不会阻塞并打印来自 smbclient.

的正确结果

这是为什么?

问题似乎是因为 smbclient 正在做某种缓冲,所以只有当你做 pclose(outpipe);

时结果才会被刷新

此版本 cat 有效:

#include <cstdio>

int main()
{
#define cmd "{ sleep 10; cat; } > /tmp/testfifo"

    FILE* outpipe = popen(cmd, "w");
    if (!outpipe)
    {
        return 0;
    }

    // Write the command to smbclient via 1st pipe
    fputs("Message sent\n", outpipe);
    fflush(outpipe);

    // Read the result from smbclient via 2nd pipe
    FILE* inpipe = fopen("/tmp/testfifo", "r");
     
    if (!inpipe)
    {
        return 0;
    }
    
    char buf[1000];
    // Pending here..
    fgets(buf, 1000, inpipe);
    printf("This is the message received : [%s]\n", buf);
    
    pclose(inpipe);
    pclose(outpipe);

    return 0;
}

以编程方式操作专为人类设计的用户界面并不一定那么容易。通常,基于文本的 UI 更简单,但您似乎偶然发现了其中一个陷阱:如果程序确定它们未连接,则 C 或 C++ 程序的标准流 完全缓冲 到交互式设备(通常是指终端)。通过 fopen()popen().

打开的文件同上

您的程序取决于它自己的流是行缓冲还是非缓冲。您可以通过 setvbuf() or setlinebuf():

进行安排
    setlinebuf(outpipe);
    setlinebuf(inpipe);

但这可能还不够。如果 smbclient 使用 stdio 函数来读取命令和发出输出,那么它的一侧也会有缓冲,这是你无法控制的。

您真正需要的是一个用于 运行 smbclient 的伪终端,这样它就好像是 运行ning 交互一样。您可以自己推出,但是 正是 libexpect 的全部内容。我建议围绕使用此库启动和与 smbclient 通信来修改您的程序。作为奖励,您将不必管理 fifo。

事实证明,这与 smbclient(或具有类似缓冲的任何其他应用程序)中的 I/O 缓冲有关。

要解决它,

const char *cmd =
"stdbuf -oL smbclient //machine/share \
    -U domain/user%pwd  > /root/testfifo";

供更多读者参考: Turn-off-buffering-in-pipe