Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/search/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#StreamReader.ReadLine在流结束前返回null_C#_Streamreader_Ssh.net - Fatal编程技术网

C#StreamReader.ReadLine在流结束前返回null

C#StreamReader.ReadLine在流结束前返回null,c#,streamreader,ssh.net,C#,Streamreader,Ssh.net,我正在使用该库在远程linux服务器上使用命令实现一个文件系统监视程序。本质上,它是一个包装: ssh myhost "inotifywait -m -e close_write --format '%:e %f' /dropzone" 该命令将打印(到标准输出): 这非常简单,可以解析并转换为事件。无论如何,我的c#代码本质上是: var privateKeyFile = new PrivateKeyFile(identity); var client = n

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

ssh myhost "inotifywait -m -e close_write --format '%:e %f' /dropzone"
该命令将打印(到标准输出):

这非常简单,可以解析并转换为事件。无论如何,我的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
监视事件
会立即显示,并等待写入第一个文件(正如我在
StreamReader
中所期望的那样,阻止等待)。但是,下一个
读取行
,而不是另一个阻塞等待,返回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并进行更新。我提交此报告是为了解决此问题。

观察到行为的原因是阶级。它的工作原理类似于字节队列。当您从
PipeStream
读取字节时,实际上是将它们出列,因此流长度减少。读取所有字节时,流长度变为0。这意味着在您读取第一行(实际上可以是多行,只是数据的第一部分)之后,流的长度为0,因此实际上结束了。下一次读取将不阻塞地返回,直到下一部分数据到达(如果有)

不幸的是,这些流似乎不是为在您的情况下工作而设计的——它们是为执行命令、接收一个结果并完成而设计的。如果您想读取连续的数据流(例如您的案例或“tail-f”结果),您唯一的选择似乎是返回到
Thread.Sleep
,至少在快速搜索之后,我没有找到任何替代方法

更新:仍然有一些反射,你可以达到你想要的结果。不存在的通道有DataReceived事件,你可以使用它在有新数据时得到通知。下面的代码应该可以做到这一点(注意这是一个草图,所以要小心):


当然,如果您联系此库的开发人员并解释问题总是更好的,也许他们能够添加缺少的行为。

@Evk的回答是正确的,即
PipeStream
是罪魁祸首。
PipeStream
的另一个问题是,如果您尝试读取的字节数超过可用字节数,它将被阻塞。例如出于性能原因,阻塞应该是
管道流
的使用者的工作。我使用SSH.NET执行
SshCommand
并异步读取标准输出/错误。我解决问题的方法是写入中间
内存流
,然后使用标准机制,如
StreamReader
。这是从
管道流
中读取的更一般的答案:

public class SshCommandStreamReader : IDisposable
{
    private readonly Stream stream;
    private readonly MemoryStream intermediateStream;
    private readonly StreamReader reader;

    public SshCommandOutputReader(Stream stream)
    {
        this.stream = stream;
        this.intermediateStream = new MemoryStream();
        this.reader = new StreamReader(intermediateStream, Encoding.UTF8);
    }

    private int FlushToIntermediateStream()
    {
        var length = stream.Length;

        if (length == 0)
        {
            return 0;
        }

        // IMPORTANT: Do NOT read with a count higher than the stream length (which is typical of reading
        // from streams). The streams for SshCommand are implemented by PipeStream (an internal class to
        // SSH.NET). Reading more than the current length causes it to *block* until data is available.
        // If the stream is flushed when reading, it does not block. It is not reliable to flush and then
        // read because there is a possible race condition where a write might occur between flushing and
        // reading (writing resets the flag that it was flushed). The only reliable solution to prevent
        // blocking when reading is to always read the current length rather than an arbitrary buffer size.
        var intermediateOutputBuffer = new byte[length];
        var bytesRead = stream.Read(intermediateOutputBuffer, 0, intermediateOutputBuffer.Length);
        intermediateStream.Write(intermediateOutputBuffer, 0, bytesRead);
        return bytesRead;
    }

    public string Read()
    {
        var bytesFlushed = FlushToIntermediateStream();

        // Allow reading the newly flushed bytes.
        intermediateStream.Position -= bytesFlushed;

        // Minor optimization since this may be called in a tight loop.
        if (intermediateStream.Position == intermediateStream.Length)
        {
            return null;
        }
        else
        {
            var result = reader.ReadToEnd();
            return result;
        }
    }

    public void Dispose()
    {
        reader.Dispose();
        intermediateStream.Dispose();
    }
}
然后使用它:

using (var command = client.CreateCommand("your command text"))
{
    var cmdAsyncResult = command.BeginExecute();

    using (var standardOutputReader = new SshCommandStreamReader(command.OutputStream))
    {
        while (!cmdAsyncResult.IsCompleted)
        {
            var result = standardOutputReader.Read();
            if (!String.IsNullOrEmpty(result))
            {
                Console.Write(result);
            }

            // Or what ever mechanism you'd like to use to prevent CPU thrashing.
            Thread.Sleep(1);
        }

        // This must be done *after* the loop and *before* EndExecute() so that any extra output
        // is captured (e.g. the loop never ran because the command was so fast).
        var resultFinal = standardOutputReader.Read();
        if (!String.IsNullOrEmpty(resultFinal))
        {
            Console.Write(resultFinal);
        }
    }

    command.EndExecute(cmdAsyncResult);
}

您应该能够修改此示例以读取标准错误(通过
ExtendedOutputStream
)并将其更改为逐行读取,这是特定于您的应用程序的。

它是如何摆脱阻塞等待的?您使用的是相同的阻塞读取器。ReadLine。@Evk,我不明白您在说什么……是的,我使用的是相同的
读取器。ReadLine
,但据我所知,
StreamReader
调用
ReadLine
应该如果当前没有要读取的数据,并且您尚未到达流的结尾,则阻止。由于此命令永远不会到达流的结尾,因此每次调用都应该阻止,直到出现新消息。至少这是所需的行为…因此您验证了在一个reader.ReadLine返回null后,下一个reader.ReadLine不会阻止并立即返回null再次?我的意思是你验证了“循环不断旋转”的假设吗?@Evk,是的,我验证了。(事实上,这就是为什么我在if语句中包装了
控制台。WriteLine(line)
,以防止打印所有空值).reader.EndOfStream在发生这种情况时是否也会返回true?我看到您只检查结果。已完成。我只想指出,我对该特定库进行了一些研究,我可以说它非常敏感,因此它可能不是寻找原因的最佳场所,因为有些东西只是有小故障…我不太怪开发人员sinc整个SSH的事情在多个层面上都有点敏感,但只是说,这一个可能是你找到了一个有效的解决方案,而忘记了其他应该有效的解决方案,但他们没有……我在这些问题上花了太多时间,但没有弄清楚原因:)@Evk,这个更新看起来很有希望。不管怎样,我一直在想办法进入频道。明天我会尝试一下,更新这个问题。。。并同意让开发人员看一看是最好的,但该项目的最后一次发布是在2013年。。。似乎仍然是C#的最佳ssh实现。。。
public class SshCommandStreamReader : IDisposable
{
    private readonly Stream stream;
    private readonly MemoryStream intermediateStream;
    private readonly StreamReader reader;

    public SshCommandOutputReader(Stream stream)
    {
        this.stream = stream;
        this.intermediateStream = new MemoryStream();
        this.reader = new StreamReader(intermediateStream, Encoding.UTF8);
    }

    private int FlushToIntermediateStream()
    {
        var length = stream.Length;

        if (length == 0)
        {
            return 0;
        }

        // IMPORTANT: Do NOT read with a count higher than the stream length (which is typical of reading
        // from streams). The streams for SshCommand are implemented by PipeStream (an internal class to
        // SSH.NET). Reading more than the current length causes it to *block* until data is available.
        // If the stream is flushed when reading, it does not block. It is not reliable to flush and then
        // read because there is a possible race condition where a write might occur between flushing and
        // reading (writing resets the flag that it was flushed). The only reliable solution to prevent
        // blocking when reading is to always read the current length rather than an arbitrary buffer size.
        var intermediateOutputBuffer = new byte[length];
        var bytesRead = stream.Read(intermediateOutputBuffer, 0, intermediateOutputBuffer.Length);
        intermediateStream.Write(intermediateOutputBuffer, 0, bytesRead);
        return bytesRead;
    }

    public string Read()
    {
        var bytesFlushed = FlushToIntermediateStream();

        // Allow reading the newly flushed bytes.
        intermediateStream.Position -= bytesFlushed;

        // Minor optimization since this may be called in a tight loop.
        if (intermediateStream.Position == intermediateStream.Length)
        {
            return null;
        }
        else
        {
            var result = reader.ReadToEnd();
            return result;
        }
    }

    public void Dispose()
    {
        reader.Dispose();
        intermediateStream.Dispose();
    }
}
using (var command = client.CreateCommand("your command text"))
{
    var cmdAsyncResult = command.BeginExecute();

    using (var standardOutputReader = new SshCommandStreamReader(command.OutputStream))
    {
        while (!cmdAsyncResult.IsCompleted)
        {
            var result = standardOutputReader.Read();
            if (!String.IsNullOrEmpty(result))
            {
                Console.Write(result);
            }

            // Or what ever mechanism you'd like to use to prevent CPU thrashing.
            Thread.Sleep(1);
        }

        // This must be done *after* the loop and *before* EndExecute() so that any extra output
        // is captured (e.g. the loop never ran because the command was so fast).
        var resultFinal = standardOutputReader.Read();
        if (!String.IsNullOrEmpty(resultFinal))
        {
            Console.Write(resultFinal);
        }
    }

    command.EndExecute(cmdAsyncResult);
}