【问题标题】:How to write to stdin/read from stdout of an SSH-connected remote process? (Renci.SshNet)如何从 SSH 连接的远程进程的标准输出写入标准输入/读取? (Renci.SshNet)
【发布时间】:2016-12-08 03:34:55
【问题描述】:

我想连接一个 SSH 服务器,该服务器在连接时运行一个自定义命令,该命令需要 XML 作为输入(UTF-8 编码,多行,每个 XML 文档末尾有一个特殊的行标记其结束)和输出 XML(UTF-8 编码,多行,末尾有相同的特殊行表示文档结束)。

目前有一种使用 plink.exe(以 PuTTy 闻名)的机制可以工作(意思是:服务器端确实按指定工作),但我正在消除对外部进程的需要。

我正在尝试使用 Renci.SshNet 库,但我发现文档不足,并且找不到我需要的示例。

到目前为止我的尝试:

using System;
using Renci.SshNet;

namespace ServerConnect
{
    public class ServerConnection
    {
        private const string EOC= "*-* COMMAND DELIMITER *-*";
        private SshClient scon;
        private System.IO.Stream stdin, stdout, stderr;
        private System.IO.StreamWriter stdin_writer;
        private System.IO.StreamReader stdout_reader;
        private Shell shell;

        public ServerConnection (string keyFileName, string keyFilePassphrase)
        {
            ConnectionInfo ci= new ConnectionInfo("my.server.local", 22, "datamgr",
                                                  new PrivateKeyAuthenticationMethod("datamgr", new PrivateKeyFile(keyFileName, keyFilePassphrase)));
            this.scon= new SshClient(ci);
        }

        public ServerConnection (string keyFilePassphrase): this("client_id_rsa", keyFilePassphrase) {}

        public void Connect() {
            this.scon.Connect();
            this.stdin= new System.IO.MemoryStream();
            this.stdin_writer= new System.IO.StreamWriter(stdin);
            this.stdout= new System.IO.MemoryStream();
            this.stdout_reader= new System.IO.StreamReader(stdout);
            this.stderr= new System.IO.MemoryStream();
            this.shell= this.scon.CreateShell(this.stdin, this.stdout, this.stderr);
            this.shell.Start();
        }

        public void Disconnect() {
            this.scon.Disconnect();
        }

        public string Command(string text) {
            Console.WriteLine("sending command");
            this.stdin_writer.WriteLine(text);
            Console.WriteLine("sending EOC");
            this.stdin_writer.WriteLine(EOC);
            this.stdin_writer.Flush();
            for (;;) {
                string line;
                line= this.stdout_reader.ReadLine();
                if (line == null) {
                    Console.WriteLine("got null");
                    break;
                }
                Console.WriteLine("READ");
                Console.WriteLine(line);
                if (line.StartsWith(EOC)) break;
            }
            return ""; // yes, this should return the built output
        }
    }
}

鉴于这是一个程序集,我正处于尝试连接到服务器的阶段(为方便起见,测试通过 booish 运行):

>>> import ServerConnect
>>> a= ServerConnection("passphrase")
ServerConnect.ServerConnection
>>> a.Connect()
>>> a.Command("<i command=\"ping\"/>")
sending command
sending EOC
got null
''

我可以看到(在服务器的 auth.log 中)在a.Connect() 调用之后建立了连接。但是,似乎我写给stdin_writer 的任何内容都不会发送到服务器。

我怎样才能完成这个机制?向服务器发送数据,然后发送分隔符,然后读取数据,直到我读取分隔符?在连接期间,该循环将重复多次。

请注意,我不是一个有经验的 C# 程序员,所以我可能对 C# 流模型缺乏基本的了解。

PS 我已经看到this 的回答,但似乎CreateShellStream 调用阻塞,可能在PseudoTty 分配中(该过程不需要伪tty,只需输入/输出)。

PS2 我从codeplex.com下载并编译了最新版本的Renci.SshNet,现在我到了这一点(摘自Connect方法):

this.scon.Connect();
this.stream= this.scon.CreateShellStream("dumb", 80, 24, 800, 600, 1024);
this.stdin_writer= new System.IO.StreamWriter(this.stream);
this.stdout_reader= new System.IO.StreamReader(this.stream);
this.shell= this.scon.CreateShell(this.stream, this.stream, this.stream);
this.shell.Start();

但是,对 stdin_writer 的任何写入(以及随后的刷新)似乎都没有通过。

【问题讨论】:

  • 我找不到解决方案,所以我在 Python 中实现了一个自定义 SSL 服务器,在 .NET 中实现了一个 SSL 客户端,因此我设法继续。我不回答这个问题,因为有人可以在某个时候回答,这可能对其他人有用。

标签: c# ssh


【解决方案1】:

抱歉,在输入此内容后,我注意到我在发帖...

您可以使用此处给出的解决方案,这是我在想做与您类似的事情时使用的:

Renci ssh.net - Create console screen from form when connected

您可以放弃使用任何 System.IO 对象,而使用 ShellStream 对象操作 SSH 会话 IO。

ShellStream.Expect 将读取输出,并查找特定字符串。 ShellStream.WriteLine 会将输入发送到 shell。

        string reply = string.Empty;
        ShellStream ss = scon.CreateShellStream("dumb", 80, 24, 800, 600, 1024);

        reply = ss.Expect("Press enter to continue");  //The prompt I get after login
        ParseOutput(reply);

        ss.WriteLine("./myscript.sh")
        reply = ss.Expect("$"); //My prompt, when the script has finished.
        ParseOutput(reply);

ParseOutput 是我编写的一个例程,用于在每个换行符处拆分“回复”字符串并将每一行添加到表单上的列表框中。

【讨论】:

    【解决方案2】:

    这在 Renci.SshNet 中是不可能的(目前)。

    第一个响应没有解决将数据管道传输到正在远程执行的命令的标准输入的原始问题。一个典型的例子是将压缩文件的内容流式传输到解压缩器以在目标主机上执行。

    直到最近(2016 年 9 月 11 日)才为 GitHub 中的 SSH.NET 项目提出了这项增强功能。你可以在这里看到它:

    Support writing to stdin when executing a remote command

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 2011-11-15
      • 1970-01-01
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      相关资源
      最近更新 更多