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

时间:2017-02-18 20:11:09

标签: python linux python-3.x sockets networking

我希望复制像Git和Rsync这样的程序通过SSH连接进行通信和传输数据的方式,但是在Python中。据我所知,这些程序分叉并执行SSH命令,该命令在服务器端启动进程,并且通过父进程与分叉子进程的STDIN和STDOUT进行通信来实现通信。

在CI中已经通过创建一个Unix套接字对(s0,s1),分叉进程,将分叉进程的stdin / stdout指向子进程上的s1,然后读取和写入套接字s0来完成在父进程上。

我想在Python3中做同样的事情。作为概念验证,这里是我的玩具远程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()

这种作品。但是如果我在套接字上发送'true \ n',因为'recv'阻塞所以这不是很好。我可以使它死锁。

  1. 在网络编程中,这是一个明智/常见的事情吗?
  2. 使用不会死锁的Python3标准库,这是一种更惯用的方法吗?

1 个答案:

答案 0 :(得分:2)

我将Web浏览器连接到SSH连接远程端上运行的命令的stdinstdout。听起来你正在尝试做类似的事情:将套接字连接到远程机器上运行的程序,以便客户端可以连接到主机/端口并使用所述远程程序。

您需要做的第一件事是建立SSH连接。我在子进程中使用外部ssh程序(sshplink,具体取决于操作系统)。你可以使用类subprocess.Popen来做到这一点。子进程启动(外部)ssh应用程序以连接到远程主机并运行所需的命令。然后坐在那里听'stdin'并愿意回复'stdout'。

(你可以在这里使用像Paramiko这样的Python SSH实现)

处于非常高的水平(其中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)

这为您提供了一个子流程,您可以通过其stdinstdoutstderr进行交互。

先让这一点工作。

您需要的下一件事是绑定套接字,监听并接受连接。然后从客户端读取并写入服务器。我写了一个轻量级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)

您应该阅读套接字:thisthis在这里很有用。

然后,您可以使用此功能连接到remote_command

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

缺少一些内容

您会注意到socat课程采用stream(与我的示例不同),其中包含writereadline方法。我实际上在一个类中封装了SSH连接,但我将把它作为读者的练习!

(基本上class Stream包含sproc并提供writereadline方法 - 有效sproc.stdin.write()sproc.stdout.readline() - 你得到了想法!)

此外,您需要一个协议来允许remote_command发出传输结束的信号。我使用ASCII EOT字符,我的远程程序在响应结束时发送它。 socat使用它来关闭客户端连接并接受新连接。根据您的使用情况,您需要提出适合您的协议。

<强>最后

以上所有都可以改进。以上是我的第一次切割,因为很容易看到内部工作原理。我已经扩展它以在一个线程中运行accept循环并优雅地处理断开连接等等。

但希望上面的内容是一个合理的起点。