为什么我使用这种 bash 管道结构似乎会丢失数据?

为什么我使用这种 bash 管道结构似乎会丢失数据?

我正在尝试组合一些像这样的程序(请忽略任何额外的包含,这是繁重的正在进行中的工作):

pv -q -l -L 1  < input.csv | ./repeat <(nc "host" 1234)

重复程序的源代码如下所示:

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <string>

inline std::string readline(int fd, const size_t len, const char delim = '\n')
{
    std::string result;
    char c = 0;
    for(size_t i=0; i < len; i++)
    {
        const int read_result = read(fd, &c, sizeof(c));
        if(read_result != sizeof(c))
            break;
        else
        {
            result += c;
            if(c == delim)
                break;
        }
    }
    return result;
}

int main(int argc, char ** argv)
{
    constexpr int max_events = 10;

    const int fd_stdin = fileno(stdin);
    if (fd_stdin < 0)
    {
        std::cerr << "#Failed to setup standard input" << std::endl;
        return -1;
    }


    /* General poll setup */
    int epoll_fd = epoll_create1(0);
    if(epoll_fd == -1) perror("epoll_create1: ");
    {
        struct epoll_event event;
        event.events = EPOLLIN;
        event.data.fd = fd_stdin;
        const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd_stdin, &event);
        if(result == -1) std::cerr << "epoll_ctl add for fd " << fd_stdin << " failed: " << strerror(errno) << std::endl;
    }

    if (argc > 1)
    {
        for (int i = 1; i < argc; i++)
        {
            const char * filename = argv[i];
            const int fd = open(filename, O_RDONLY);
            if (fd < 0)
                std::cerr << "#Error opening file " << filename << ": error #" << errno << ": " << strerror(errno) << std::endl;
            else
            {
                struct epoll_event event;
                event.events = EPOLLIN;
                event.data.fd = fd;
                const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
                if(result == -1) std::cerr << "epoll_ctl add for fd " << fd << "(" << filename << ") failed: " << strerror(errno) << std::endl;
                else std::cerr << "Added fd " << fd << " (" << filename << ") to epoll!" << std::endl;
            }
        }
    }

    struct epoll_event events[max_events];
    while(int event_count = epoll_wait(epoll_fd, events, max_events, -1))
    {
        for (int i = 0; i < event_count; i++)
        {
            const std::string line = readline(events[i].data.fd, 512);                      
            if(line.length() > 0)
                std::cout << line << std::endl;
        }
    }
    return 0;
}

我注意到这一点:

  • 当我只是使用管道 to 时./repeat,一切都按预期进行。
  • 当我只使用进程替换时,一切都会按预期进行。
  • 当我使用进程替换封装 pv 时,一切都按预期进行。
  • 但是,当我使用特定的构造时,我似乎从标准输入中丢失了数据(单个字符)!

我已经尝试过以下方法:

  • 我尝试禁用所有进程之间的管道缓冲pv并在所有进程上./repeat使用stdbuf -i0 -o0 -e0,但这似乎不起作用。
  • 我把epoll换成了poll,还是不行。
  • pv当我查看和./repeat之间的流时tee stream.csv,这看起来是正确的。
  • 我曾经strace看到发生了什么,我看到很多单字节读取(正如预期的那样),并且它们还表明数据丢失。

我想知道发生了什么事?或者我可以做些什么来进一步调查?

答案1

因为nc里面的命令<(...)也会从stdin读取。

更简单的例子:

$ nc -l 9999 >/tmp/foo &
[1] 5659

$ echo text | cat <(nc -N localhost 9999) -
[1]+  Done                    nc -l 9999 > /tmp/foo

去哪儿了text?通过网猫。

$ cat /tmp/foo
text

你的程序和nc竞争相同的标准输入,并nc获得其中的一些。

答案2

epoll() 或 poll() 以 E/POLLIN 返回只会告诉您单身的读()可能不阻止。

并不是说您将能够像您一样执行大量单字节 read() 直到换行符。

我说可能因为使用 E/POLLIN 返回的 epoll() 之后的 read() 可能仍然会阻塞。

您的代码还将尝试读取过去的 EOF,并完全忽略任何 read() 错误。

相关内容