简体   繁体   English

Python - 通过套接字与子进程通信

[英]Python - communicating with subprocess over a socket

I am looking to replicate the way programs like Git and Rsync communicate and transfer data over an SSH connection, but in Python.我希望复制 Git 和 Rsync 等程序通过 SSH 连接进行通信和传输数据的方式,但在 Python 中。 I understand that these programs fork and exec an SSH command that starts a process on the server side and communication is achieved by the parent processing talking to the STDIN and STDOUT of the forked child process.我知道这些程序分叉并执行一个 SSH 命令,该命令在服务器端启动一个进程,并且通信是通过父进程与分叉子进程的 STDIN 和 STDOUT 对话来实现的。

In CI have seen this done by creating a Unix socket pair (s0, s1), forking the process, pointing the stdin/stdout of the forked process to s1 on the child process, and then reading and writing to the socket s0 on the parent process.在 CI 中,通过创建 Unix 套接字对 (s0, s1),分叉进程,将分叉进程的 stdin/stdout 指向子进程上的 s1,然后读取和写入父进程上的套接字 s0 来完成此操作过程。

I want to do the same thing in Python3.我想在 Python3 中做同样的事情。 As a proof of concept, here is my implementation of a toy remote shell that sends commands down a socket and receives the output from the same socket:作为概念证明,这里是我的玩具远程 shell 的实现,它向套接字发送命令并从同一个套接字接收输出:

import subprocess
import socket


ssh_cmd = 'ssh -q -T ubuntu@xxxxxxxx'
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()

This kind of works.这种作品。 But I can deadlock it if I send 'true \\n' down the socket because 'recv' is blocking so that's not great.但是,如果我将 'true \\n' 发送到套接字下,我可以死锁它,因为 'recv' 正在阻塞,所以这不是很好。

  1. Is this a sensible/common thing to do in network programming?这是网络编程中明智/常见的做法吗?
  2. What is a more idiomatic way of doing this using the Python3 standard libraries that doesn't deadlock?使用不会死锁的 Python3 标准库执行此操作的更惯用方法是什么?

I connected a web browser to the stdin and stdout of a command running on the remote end of a SSH connection.我将 Web 浏览器连接到在 SSH 连接的远程端运行的命令的stdinstdout It sounds like you're trying to do something similar: connect a socket to a program running on a remote machine so that clients can connect to a host/port and use said remote program.听起来您正在尝试做类似的事情:将套接字连接到远程机器上运行的程序,以便客户端可以连接到主机/端口并使用所述远程程序。

The first thing you need to do is establish a SSH connection.您需要做的第一件事是建立 SSH 连接。 I use an external ssh program (either ssh or plink depending on OS) in a subprocess.我在子plink使用外部ssh程序( sshplink具体取决于操作系统)。 You could just use class subprocess.Popen to do that.你可以使用类subprocess.Popen来做到这一点。 The subprocess launches the (external) ssh application to connect to the remote host and runs the required command.子进程启动(外部) ssh应用程序以连接到远程主机并运行所需的命令。 That then sits there listening on its 'stdin' and willing to reply on its 'stdout'.然后坐在那里听它的“标准输入”并愿意回复它的“标准输出”。

(You could possibly use Python SSH implementation such as Paramiko here) (您可以在这里使用 Python SSH 实现,例如 Paramiko)

At a very high level (where remote_command is the thing to run on the far end):在非常高的级别上(其中remote_command是要在远端运行的东西):

import subprocess
command = ['ssh', 'user@host', 'remote_command']

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

This leaves you with a subprocess that you can interact with through its stdin , stdout and stderr .这为您留下了一个子进程,您可以通过其stdinstdoutstderr与之交互。

Get this bit working first.首先让这个位工作。

The next thing you need is to bind a socket, listen and accept a connection.接下来你需要绑定一个套接字,监听并接受一个连接。 Then read from the client and write to the server.然后从客户端读取并写入服务器。 I wrote a lightweight Socat (inspired by Socat ) class:我写了一个轻量级的Socat (受Socat启发)类:

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)

You should read up on Sockets: this and this are useful here.你应该阅读 Sockets: thisthis在这里很有用。

You can then use this to connect to your remote_command :然后您可以使用它来连接到您的remote_command

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

There are some missing bits有一些缺失的位

You'll notice the socat class takes in a stream (unlike my example) that has write and readline methods.您会注意到socat类接受一个具有writereadline方法的stream (与我的示例不同)。 I actually encapsulated the SSH connection in a class but I'll leave that as an exercise for the reader!我实际上将 SSH 连接封装在一个类中,但我将把它留给读者作为练习!

(Basically a class Stream that contains the sproc and provides write and readline methods - effectively sproc.stdin.write() and sproc.stdout.readline() - you get the idea!) (基本上是一个包含sproc并提供writereadline方法的class Stream - 实际上是sproc.stdin.write()sproc.stdout.readline() - 你明白了!)

Also, you need a protocol to allow remote_command to signal the end of a transmission.此外,您需要一个协议来允许remote_command发出传输结束的信号。 I used the ASCII EOT character and my remote program sends that at the end of a response.我使用了 ASCII EOT字符,我的远程程序在响应结束时发送该字符。 The socat uses that to close the client connection and accept a new one. socat使用它来关闭客户端连接并接受新的连接。 Depending on your use-case you will need to come up with a protocol that works for you.根据您的用例,您需要提出适合您的协议。

Finally最后

All of the above can be improved upon.以上都是可以改进的。 What's above is my first cut because it is simple to see the inner workings.上面是我的第一次剪辑,因为很容易看到内部运作。 I have since extended it to run the accept loop in a thread and gracefully handle disconnects and so-on.从那以后,我将它扩展为在线程中运行接受循环并优雅地处理断开连接等。

But hopefully what's above is a reasonable starting point.但希望以上内容是一个合理的起点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM