2017-02-18 10 views
0

GitとRsyncのようなプログラムがSSH接続を介してデータをやりとりする方法を再現しようとしています。これらのプログラムは、サーバー側でプロセスを開始するSSHコマンドを実行して実行し、フォークされた子プロセスのSTDINおよびSTDOUTと通信する親プロセスによって通信が行われることを理解しています。Python - ソケット上でサブプロセスと通信する

CIでは、プロセスをフォークし、フォークされたプロセスのstdin/stdoutを子プロセスのs1に指定してから、ソケットに読み書きすることで、Unixソケットペア(s0、s1)親プロセス上のs0。

私はPython3でも同じことをしたいと思います。概念実証として、ここではソケットダウンコマンドを送信し、同じソケットからの出力を受けおもちゃリモートシェルの私の実装です:

import subprocess 
import socket 


ssh_cmd = 'ssh -q -T [email protected]' 
s_local, s_remote = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 

ssh = subprocess.Popen(ssh_cmd, shell=True, 
         stdout=s_remote, 
         stdin=s_remote, 
         close_fds=True) 
s_remote.close() 

s_local.send('ls -l\n'.encode()) 
print(s_local.recv(1024).decode()) 
s_local.send('uname -a\n'.encode()) 
print(s_local.recv(1024).decode()) 
s_local.close() 
ssh.kill() 

作品のこの種。しかし、 'recv'がブロックされているので、本当の\ n 'をソケットの下に送るとデッドロックすることができます。

  1. これはネットワークプログラミングでは分かりやすいことですか?
  2. デッドロックしないPython3標準ライブラリを使用してこれを行う方法は、もっと慣れていますか?
+0

あなたがしようとしていることは、かなり賢明です。あなたは 'pexpect'モジュールを調べたいかもしれません。これは、対話型のアプリケーションを駆動するこの種のものに合わせて作られています。良いスタートについてはhttps://pexpect.readthedocs.io/en/stable/overview.htmlを参照してください。それはあなたが応答を期待しているが、何も得られない場合に備えて、あなたの相互作用にタイムアウトを置く能力を持っています。 –

答えて

1

SSH接続のリモート側で実行されているコマンドのstdinstdoutにWebブラウザを接続しました。クライアントがホスト/ポートに接続してリモートプログラムを使用できるように、リモートマシン上で実行されているプログラムにソケットを接続します。

まず、SSH接続を確立するだけです。私はサブプロセス内で外部sshプログラム(OSによってはsshまたはplink)を使用します。これを行うには、クラスsubprocess.Popenを使用します。サブプロセスは、(外部)sshアプリケーションを起動してリモートホストに接続し、必要なコマンドを実行します。それはそこでそこに座って、その「標準」を聞いて、その「標準」について返答します。

import subprocess 
command = ['ssh', '[email protected]', 'remote_command'] 

sproc = subprocess.Popen(command, shell=False, 
         stdin = subprocess.PIPE, 
         stdout = subprocess.PIPE, 
         stderr = subprocess.PIPE) 

この:非常に高いレベルの(remote_commandは、遠端で実行するものである。)で

(あなたはおそらく、そのようなここParamikoとしてPython SSHの実装を使用することができます)

stdinstdoutstderrで対話できるサブプロセスが残っています。

このビットを最初に使用してください。

次に必要なことは、ソケットをバインドして、接続をリッスンして受け入れることです。その後、クライアントから読み取り、サーバーに書き込みます。私は軽量SocatSocatに触発さ)クラスを書いた:

import socket 

class Socat(object): 

EOT = '\004\r\n' 

def __init__(self, binding, stream, eot = EOT): 

    self.stream = stream 
    self.eot = eot 

    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    self.socket.bind(binding) 
    self.socket.listen(5) 

    while True: 
    print "waiting for connection" 
    (clientsocket, address) = self.socket.accept() 
    print "connection from %s:%s" % address 
    data = clientsocket.recv(1024) 
    self.stream.write(data) 
    while data: 
     data = self.stream.readline() 
     if data: 
     print "read %s bytes" % len(data) 
     if data == self.eot: 
      print "read termination signature" 
      clientsocket.close() 
      data = None 
     else: 
      clientsocket.send(data) 

あなたはソケット上に読んでください:thisthisがここに便利です。

あなたは、あなたのremote_commandに接続するためにこれを使用することができ

Socat(('localhost', 8080), sproc) 

一部欠落ビット

があります。あなたがいることを(私の例とは異なり)socatクラスがstreamにかかるわかりますwriteおよびreadlineの方法を有する。私は実際にクラスでSSH接続をカプセル化しましたが、私はそれを読者のための練習として残します!

sprocが含まれており、writereadline方法を提供し、基本的にclass Stream - 効果的sproc.stdin.write()sproc.stdout.readline()! - あなたがアイデアを得る)

また、あなたはremote_commandは、伝送の終了を通知することを可能にするプロトコルを必要とします。私はASCIIのEOT文字を使用し、私のリモートプログラムはレスポンスの終わりにそれを送信します。 socatはそれを使用してクライアント接続を閉じ、新しい接続を受け入れます。あなたのユースケースに応じて、あなたに適したプロトコルを考え出す必要があります。

最後

上記のすべての際に改善することができます。私の最初のカットは、内部の仕組みを見るのが簡単だからです。私はそれ以来、スレッドでacceptループを実行し、正常に切断やその他を処理するように拡張しました。

しかし、うまくいけば、これは合理的な出発点です。

関連する問題