Neden bu bash boru yapısını kullanarak veri kaybettim?


11

Böyle birkaç programı birleştirmeye çalışıyorum (lütfen herhangi bir ekstra içerir görmezden gelin, bu devam etmekte olan yoğun çalışmadır):

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

Tekrar programının kaynağının aşağıdaki gibi olduğu yerler:

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

Bunu fark ettim:

  • Boruyu sadece için kullandığımda ./repeat, her şey amaçlandığı gibi çalışıyor.
  • Sadece süreç değişimini kullandığımda, her şey amaçlandığı gibi çalışıyor.
  • Ben süreç ikame kullanarak pv kapsüllemek, her şey istendiği gibi çalışır.
  • Ancak, belirli bir yapıyı kullandığımda, stdin'den veriyi (bireysel karakterler) kaybediyorum!

Aşağıdakileri denedim:

  • Ben arasındaki boru devre dışı bırakmak arabelleğe çalıştık pvve ./repeatkullanan stdbuf -i0 -o0 -e0tüm süreçler üzerinde, ama bu işin görünmüyor.
  • Anket için epoll'u değiştirdim, işe yaramıyor.
  • Ben arasındaki dere baktığınızda pvve ./repeatbirlikte tee stream.csv, bu doğru görünüyor.
  • Kullandığım straceneler olduğunu görmek için, ben tek byte çok görmek okur (beklendiği gibi) ve ayrıca bu veri eksik oluyor gösteriyor.

Acaba ne oluyor? Ya da daha fazla araştırmak için ne yapabilirim?

Yanıtlar:


16

Çünkü nciçerideki komut <(...)stdin'den de okunacak.

Daha basit örnek:

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

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

Nereye gitti text? Netcat aracılığıyla.

$ cat /tmp/foo
text

Programınız ve ncaynı stdin için rekabet ve ncbazı alır.


Haklısın! Teşekkürler! Stdin bağlantısını kesmenin temiz bir yolunu önerebilir misiniz <(...)? Bundan daha güzel bir yol var mı <( 0<&- ...)?
Roel Baardman

5
<(... </dev/null). kullanmayın 0<&-: ilk yeni fd olarak open(2)dönmesine neden olur 0. Senin Eğer ncdestekleri bunu, ayrıca kullanabilirsiniz -dseçeneği.
Mosvy

3

epoll () veya anket () E ile dönen / POLLIN sadece söyleyecektir tek okuma () olabilir engellemez.

Sizin gibi yeni bir satıra kadar çok sayıda bayt read () yapabilirsiniz.

Diyorum may E ile döndürülen epoll sonra okuma () () / POLLIN hala engelleyebilir çünkü.

Kodunuz ayrıca geçmiş EOF'u okumaya çalışır ve okuma () hatalarını tamamen yok sayar.


Bu sorunuma doğrudan bir çözüm olmamasına rağmen, yorum yaptığınız için teşekkürler. Bu kod kusurları olduğunu ve EOF algılama (POLLHUP / POLLNVAL kullanarak) daha az soyulmuş bir sürümü mevcut olduğunu biliyoruz. Gerçi birden fazla dosya tanımlayıcısından satırları okumak için arabelleksiz bir yol bulmakta zorlanıyorum. Benim repeatprogramı esas NMEA verileri işlerken (hat-bazlı ve uzunluk göstergeli), birden çok kaynaktan. Birden fazla canlı kaynaktan veri birleştirdiğim için, çözümümün arabelleksiz olmasını istiyorum. Bunu yapmanın daha verimli bir yolunu önerebilir misiniz?
Roel Baardman

fwiw, her bayt için bir sistem çağrısı (okuma) yapmak mümkün olan en az etkili yoldur. EOF kontrolü sadece okumanın dönüş değeri kontrol edilerek yapılabilir, POLLHUP'a gerek yoktur (ve POLLNVAL sadece EOF yerine sahte bir fd'yi geçtiğinizde iade edilir). Ama her neyse, bizi izlemeye devam edin. ypeeBirden fazla fds okuyan ve kayıtları koruyarak (satırları bozulmadan tutarak) başka bir fd içine karıştırmak bir yarar fikri var .
pizdelect

Bu bash yapısının bunu yapması gerektiğini fark ettim, ama stdin'i nasıl birleştireceğimizi bilmiyorum: { cmd1 & cmd2 & cmd3; } > fileDosya tarif ettiklerinizi içerecek. Ancak, benim durumumda tcpserver (3) her şeyi çalıştırıyorum, bu yüzden de stdin (istemci verilerini içeren) dahil etmek istiyorum. Bunu nasıl yapacağımdan emin değilim.
Roel Baardman

1
Bu cmd1, cmd2, ... ne olduğuna bağlıdır. Nc veya cat ise ve verileriniz çizgi yönelimliyse, çıktı hatalı biçimlendirilmiş olabilir - cmd1 tarafından yazdırılan bir satırın başlangıcını ve cmd2 tarafından yazdırılan bir satırın sonunu içeren satırlar alırsınız.
pizdelect
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.