从命名管道

时间:2017-08-01 16:44:23

标签: go named-pipes

我想知道我有什么其他选项,以便使用golang从命名管道连续读取。我当前的代码依赖于在gorutine中运行的无限for循环;但是帽子使一个CPU保持100%的使用率。

func main() {
....

var wg sync.WaitGroup
fpipe, _ := os.OpenFile(namedPipe, os.O_RDONLY, 0600)
defer fpipe.Close()

f, _ := os.Create("dump.txt")
defer f.Close()
var buff bytes.Buffer

wg.Add(1)
go func() {
        for {
          io.Copy(&buff, fpipe)
          if buff.Len() > 0 {
              buff.WriteTo(f)
           }
         }
    }()

    wg.Wait()
}

3 个答案:

答案 0 :(得分:2)

当没有留下作者时,命名管道阅读器将收到EOF。此代码之外的解决方案是确保始终有一个包含文件描述符的编写器进程,尽管它不需要编写任何内容。

在Go程序中,如果您想等待新的编写器,则必须在for循环中轮询io.Reader。您当前的代码使用繁忙的循环执行此操作,这将占用1个cpu核心的100%。添加睡眠和返回其他错误的方法将解决此问题:

for {
    err := io.Copy(&buff, fpipe)
    if buff.Len() > 0 {
        buff.WriteTo(f)
    }

    if err != nil {
        // something other than EOF happened
        return
    }

    time.Sleep(100 * time.Millisecond)
}

答案 1 :(得分:1)

简介

如前所述,如果没有留下作者,则命名管道阅读器将收到EOF。

然而,我发现@ JimB的解决方案不是最佳的:

  1. 命名管道的最大容量(65kB,iirc),可能会在100毫秒的睡眠周期内被填满。当缓冲区被填满时,所有作者都会无缘无故地阻止。
  2. 如果重启,您平均会丢失50毫秒的数据。再一次,没有充分的理由。
  3. 如果您想使用静态缓冲区进行复制,io.CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)将是更好的解决方案,imho。但这甚至不是必需的,因为io.Copy(或底层实现)实际上分配了32kB的缓冲区。
  4. 我的方法

    更好的解决方案是等待写入发生并立即将命名管道的内容复制到目标文件。在大多数系统上,存在关于文件系统事件的某种通知。包github.com/rjeczalik/notify可用于访问我们感兴趣的事件,因为写事件在大多数重要操作系统上跨平台工作。我们感兴趣的另一个事件是删除命名管道,因为我们没有任何东西可以读取。

    因此,我的解决方案是:

    package main
    
    import (
        "flag"
        "io"
        "log"
        "os"
    
        "github.com/rjeczalik/notify"
    )
    
    const (
        MAX_CONCURRENT_WRITERS = 5
    )
    
    var (
        pipePath string
        filePath string
    )
    
    func init() {
        flag.StringVar(&pipePath, "pipe", "", "/path/to/named_pipe to read from")
        flag.StringVar(&filePath, "file", "out.txt", "/path/to/output file")
        log.SetOutput(os.Stderr)
    }
    
    func main() {
        flag.Parse()
    
        var p, f *os.File
        var err error
        var e notify.EventInfo
    
        // The usual stuff: checking wether the named pipe exists etc
        if p, err = os.Open(pipePath); os.IsNotExist(err) {
            log.Fatalf("Named pipe '%s' does not exist", pipePath)
        } else if os.IsPermission(err) {
            log.Fatalf("Insufficient permissions to read named pipe '%s': %s", pipePath, err)
        } else if err != nil {
            log.Fatalf("Error while opening named pipe '%s': %s", pipePath, err)
        }
        // Yep, there and readable. Close the file handle on exit
        defer p.Close()
    
        // Do the same for the output file
        if f, err = os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600); os.IsNotExist(err) {
            log.Fatalf("File '%s' does not exist", filePath)
        } else if os.IsPermission(err) {
            log.Fatalf("Insufficient permissions to open/create file '%s' for appending: %s", filePath, err)
        } else if err != nil {
            log.Fatalf("Error while opening file '%s' for writing: %err", filePath, err)
        }
        // Again, close the filehandle on exit
        defer f.Close()
    
        // Here is where it happens. We create a buffered channel for events which might happen
        // on the file. The reason why we make it buffered to the number of expected concurrent writers
        // is that if all writers would (theoretically) write at once or at least pretty close
        // to each other, it might happen that we loose event. This is due to the underlying implementations
        // not because of go.
        c := make(chan notify.EventInfo, MAX_CONCURRENT_WRITERS)
    
        // Here we tell notify to watch out named pipe for events, Write and Remove events
        // specifically. We watch for remove events, too, as this removes the file handle we
        // read from, making reads impossible
        notify.Watch(pipePath, c, notify.Write|notify.Remove)
    
        // We start an infinite loop...
        for {
            // ...waiting for an event to be passed.
            e = <-c
    
            switch e.Event() {
    
            case notify.Write:
                // If it a a write event, we copy the content of the named pipe to
                // our output file and wait for the next event to happen.
                // Note that this is idempotent: Even if we have huge writes by multiple
                // writers on the named pipe, the first call to Copy will copy the contents.
                // The time to copy that data may well be longer than it takes to generate the events.
                // However, subsequent calls may copy nothing, but that does not do any harm.
                io.Copy(f, p)
    
            case notify.Remove:
                // Some user or process has removed the named pipe,
                // so we have nothing left to read from.
                // We should inform the user and quit.
                log.Fatalf("Named pipe '%s' was removed. Quitting", pipePath)
            }
        }
    }
    

答案 2 :(得分:0)

问题:当“最后一个作者”关闭管道时,即使稍后可能会有新的作者出现,您也会得到EOF。

解决方案:自己打开管道进行写入,而不要关闭它。现在您可以将读取面视为永无止境的读取,而无需获得EOF。在您打开管道阅读的位置后直接放置以下内容:

nullWriter, err := os.OpenFile(pipePath, os.O_WRONLY, os.ModeNamedPipe)
if err != nil {
  logger.Crit("Error opening pipe for (placeholder) write", "err", err)
}
defer nullWriter.Close()