이 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;
}

나는 이것을 발견했습니다 :

  • 파이프를 사용하면 ./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? Netcat을 통해.

$ cat /tmp/foo
text

귀하의 프로그램은 nc동일한 표준 입력과 경쟁하여 nc그 중 일부를 얻습니다.

답변2

E/POLLIN으로 반환되는 epoll() 또는 poll()은 사용자에게만 알려줍니다.하나의읽다()가능한차단하지 마십시오.

당신이 했던 것처럼 개행까지 많은 싱글바이트 read()를 할 수 있다는 것은 아닙니다.

나는 말했다가능한E/POLLIN에서 반환된 epoll()을 사용한 후 read()가 여전히 차단될 수 있기 때문입니다.

또한 코드는 과거 EOF를 읽으려고 시도하고 모든 read() 오류를 완전히 무시합니다.

관련 정보