如何写入stdin /从SSH连接的远程进程的stdout读取? (Renci.SshNet)

时间:2015-05-06 14:22:13

标签: c# ssh

我想连接一个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分配(该进程不需要伪ttys,只需输入/输出)。

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(后续刷新)的任何写入似乎都没有通过。

2 个答案:

答案 0 :(得分:1)

对不起,输入后我注意到我发了necro ...

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

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是我写的一个例程,用于在每个换行符处拆分“回复”字符串,并将每一行添加到表单上的列表框中。

答案 1 :(得分:1)

Renci.SshNet(尚未)无法做到这一点。

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

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

Support writing to stdin when executing a remote command