このbashパイプ構造を使用すると、なぜデータが失われたように見えるのですか?


11

私はそのようないくつかのプログラムを組み合わせようとしています(余分なインクルードは無視してください、これは進行中の重い作業です):

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./repeatstdbuf -i0 -o0 -e0ましたが、それはうまくいかないようです。
  • 私はepollを世論調査に交換しました、動作しません。
  • pvとの間のストリームを見る./repeattee stream.csv、これは正しいように見えます。
  • 以前straceは何が起こっていたのかを確認していましたが、(予想どおり)多くのシングルバイト読み取りを確認し、データが欠落していることも示しています。

どうなっているのかな?または、さらに調査するために何ができますか?

回答:


16

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一部を取得します。


あなたが正しい!ありがとう!でstdinを切断するクリーンな方法を提案できます<(...)か?より良い方法はあり<( 0<&- ...)ますか?
Roel Baardman

5
<(... </dev/null)。使用しないでください。0<&-最初のものが新しいfdとしてopen(2)返さ0れます。ncサポートしている場合は、-dオプションを使用することもできます。
モスビー

3

E / POLLINで返されるepoll()またはpoll()は、単一の read()ブロックしない可能性があることのみを通知します。

あなたがするように、改行まで多くの1バイトのread()を行うことができるというわけではありません。

E / POLLINで返されたepoll()の後のread()がまだブロックする可能性があるため、私はそう言います。

また、コードは過去のEOFを読み取ろうとし、read()エラーを完全に無視します。


これは私の問題の直接的な解決策ではありませんが、コメントをありがとうございます。このコードには欠陥があり、EOF検出は、よりシンプルなバージョン(POLLHUP / POLLNVALの使用による)に存在することを認識しています。私は複数のファイル記述子から行を読み取るためのバッファリングされていない方法を見つけるのに苦労しています。私のrepeatプログラムは基本的に、複数のソースからのNMEAデータ(行ベースで長さインジケーターなし)を処理しています。複数のライブソースからのデータを結合しているので、私のソリューションをバッファリングしないでください。これを行うためのより効率的な方法を提案できますか?
Roel Baardman

fwiw、バイトごとにシステムコール(読み取り)を行うのは、最も効率の悪い方法です。EOFチェックは、POLLHUPを必要とせずに、readの戻り値をチェックするだけで実行できます(POOLNVALは、EOFではなく、偽のfdを渡すときにのみ返されます)。とにかく、お楽しみに。私は、ypee複数のfdsから読み取り、それらを別のfdにミックスしながら、レコードを保持する(行をそのまま保持する)ユーティリティのアイデアを持っています。
-pizdelect

このbash構造はそれを行う必要があることに気付きましたが、stdinをその中に結合する方法がわかりません。{ cmd1 & cmd2 & cmd3; } > fileファイルには、記述した内容が含まれます。ただし、私の場合、tcpserver(3)からすべてを実行しているので、stdin(クライアントデータを含む)も含めたいと思います。どうすればいいのかわかりません。
ロエルバードマン

1
それはcmd1、cmd2、...に依存します。それらがncまたはcatであり、データが行指向である場合、出力は不正な形式である可能性があります-cmd1によって出力される行の始まりとcmd2によって出力される行の終わりからなる行が得られます。
pizdelect
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.