我正在使用SSH.NET库通过inotifywait命令在远程linux服务器上实现文件系统监视程序。本质上,它是一个包装器:

ssh myhost "inotifywait -m -e close_write --format '%:e %f' /dropzone"


该命令将打印输出(到STDOUT):

CLOSE_WRITE:CLOSE foo
CLOSE_WRITE:CLOSE bar
CLOSE_WRITE:CLOSE baz


这很简单,可以解析并转换为事件。无论如何,我的C#代码本质上是:

        var privateKeyFile = new PrivateKeyFile(identity);
        var client = new SshClient(hostname, username, privateKeyFile);

        SshCommand command = null;
        IAsyncResult result = null;
        try
        {
            client.Connect();
            command = client.CreateCommand("inotifywait -m -e close_write --format '%:e %f' " + dropZone);
            result = command.BeginExecute();

            Console.WriteLine("Watching for events");
            var reader = new StreamReader(command.OutputStream);
            string line = null;
            while ((line = reader.ReadLine()) != null)
            {
                Console.WriteLine(line);
            }
            Console.WriteLine("Reached end of stream");
        }
        finally
        {
            if (client != null)
            {
                Console.WriteLine("Close the connection");
                client.Dispose();
            }
        }

        Console.WriteLine("Press enter to quit");
        Console.ReadLine();


写入单个文件后,运行它会导致以下输出:

Watching for events
CLOSE_WRITE:CLOSE baz
Reached end of stream
Close the connection
Press enter to quit


Watching for events立即显示,将等待第一个文件被写入(如我期望的那样,阻塞等待StreamReader)。但是,下一个ReadLine(而不是另一个阻塞等待)将返回null(指示流结束),即使该命令仍在愉快地运行。我知道我可以这样更改循环:

            while (!result.IsCompleted)
            {
                line = reader.ReadLine();
                if (line != null)
                {
                    Console.WriteLine(line);
                }
            }


结果是:

Watching for events
CLOSE_WRITE:CLOSE baz
CLOSE_WRITE:CLOSE bar
CLOSE_WRITE:CLOSE foo
...


根据需要,但它摆脱了等待新输入的阻塞等待,这意味着循环不断旋转(显然是不希望的……)

你能解释这种行为吗?对其他方法有什么建议吗?

----更新----

好像该库正在迁移到github并进行更新。我已提交this issue试图解决此问题。

最佳答案

观察到的行为的原因是PipeStream类。它像字节队列一样工作。从PipeStream读取字节时,实际上是使它们出队,因此流长度会减小。当您读取所有字节时,流长度变为0。这意味着在您读取第一个“行”(实际上可以是多行,只是数据的第一部分)之后-流的长度为0,因此有效结束了。下次读取将不中断地返回,直到下一部分数据到达(如果有)。

不幸的是,这些流似乎并不是针对您的情况而设计的-它们旨在执行命令,接收一个结果并完成。如果您想读取连续的数据流(例如您的案例或例如“ tail -f”结果),则您唯一的选择似乎是在两次读取之间回退到Thread.Sleep,至少在快速搜索后我没有找到任何内容替代。

更新:仍然需要一些思考,您才能获得想要的结果。撤销通道具有DataReceived事件,可在有新数据可用时使用该事件来通知。下面的代码应该可以解决问题(请注意,这是一个草图,请注意):

    static void Main(string[] args) {
        var privateKeyFile = new PrivateKeyFile(@"somefile");
        using (var client = new SshClient("somehost", "someuser", privateKeyFile)) {
            client.Connect();
            var command = client.CreateCommand("tail -f /tmp/test.txt");

            var result = command.BeginExecute();
            var channelField = command.GetType().GetField("_channel", BindingFlags.Instance | BindingFlags.NonPublic);
            var channel = channelField.GetValue(command);
            var receivedEvent = channel.GetType().GetEvent("DataReceived", BindingFlags.Instance | BindingFlags.Public);
            Console.WriteLine("Watching for events");
            using (var handler = new ReceivedHandler()) {
                // add event handler here
                receivedEvent.AddEventHandler(channel, Delegate.CreateDelegate(receivedEvent.EventHandlerType, handler, handler.GetType().GetMethod("OnReceive")));
                while (true) {
                    // wait on both command completion and our custom wait handle. This is blocking call
                    WaitHandle.WaitAny(new[] {result.AsyncWaitHandle, handler.Signal});
                    // if done - break
                    if (result.IsCompleted)
                        break;
                    var line = handler.ReadLine();
                    Console.WriteLine(line);
                }
            }
            Console.WriteLine("Reached end of stream");
            Console.ReadKey();
        }

    }

    public class ReceivedHandler : IDisposable {
        private readonly AutoResetEvent _signal;
        private readonly StringBuilder _buffer = new StringBuilder();
        public ReceivedHandler() {
            _signal = new AutoResetEvent(false);
        }

        public void OnReceive(object sender, EventArgs e) {
            var dataProp = e.GetType().GetProperty("Data", BindingFlags.Instance | BindingFlags.Public);
            var rawData = (byte[])dataProp.GetValue(e);
            var data = Encoding.ASCII.GetString(rawData);
            lock (_buffer) {
                // append to buffer for reader to consume
                _buffer.Append(data);
            }
            // notify reader
            Signal.Set();
        }

        public AutoResetEvent Signal => _signal;

        public string ReadLine() {
            lock (_buffer) {
                // cleanup buffer
                var result = _buffer.ToString();
                _buffer.Clear();
                return result;
            }
        }

        public void Dispose() {
            _signal.Dispose();
        }
    }


当然,如果您联系该库的开发人员并说明问题,那总会更好,也许他们能够添加缺失的行为。

关于c# - C#StreamReader.ReadLine在流结束之前返回null,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/37059305/

10-13 08:33