2017-02-18 4 views
0

Я ищу, чтобы реплицировать способы, как Git и Rsync обмениваться данными и передавать данные по SSH-соединению, но в Python. Я понимаю, что эти программы fork и exec SSH-команда, которая запускает процесс на стороне сервера, и связь достигаются родительской обработкой, разговаривающей с STDIN и STDOUT раздвоенного дочернего процесса.Python - связь с подпроцессом над сокетом

В CI это было сделано, создав пару гнезд Unix (s0, s1), нарисовав процесс, указав stdin/stdout разветвленного процесса на 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() 

Этот вид работ. Но я могу зайти в тупик, если я отправлю «true \ n» в сокет, потому что «recv» блокируется, так что это не так.

  1. Является ли это разумным/распространенным явлением в сетевом программировании?
  2. Что более идиоматично для этого, используя стандартные библиотеки Python3, которые не затормозили?
+0

То, что вы пытаетесь сделать, вполне разумно. Вы можете изучить модуль 'pexpect'. Он предназначен для такого рода вещей: вождение интерактивного приложения. См. Https://pexpect.readthedocs.io/en/stable/overview.html для хорошего старта. Он также имеет возможность устанавливать таймауты в ваших взаимодействиях в случае, если вы ожидаете ответа, но не получите его. –

ответ

1

Я соединил веб-браузер с stdin и stdout команды, работающей на удаленном конце соединения SSH. Похоже, вы пытаетесь сделать что-то подобное: подключите сокет к программе, запущенной на удаленной машине, чтобы клиенты могли подключиться к хосту/порту и использовать указанную удаленную программу.

Первое, что вам нужно сделать, это установить соединение SSH. Я использую внешнюю программу ssh (либо ssh, либо plink в зависимости от ОС) в подпроцессе. Для этого вы можете просто использовать класс subprocess.Popen. Подпроцесс запускает (внешнее) приложение ssh для подключения к удаленному хосту и выполняет требуемую команду. Затем он сидит, слушая его «stdin» и желая ответить на его «stdout».

(Вы могли бы использовать реализацию SSH Python, такие как Paramiko здесь)

На очень высоком уровне (где remote_command является вещь, чтобы работать на дальнем конце):

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

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

Это дает вам подпроцесс, с которым вы можете взаимодействовать через его stdin, stdout и stderr.

Получите этот бит в первую очередь.

Следующее, что вам нужно - это связать сокет, прослушать и принять соединение. Затем читайте с клиента и пишите на сервер. Я написал легкий 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) 

Вы должны прочитать на Sockets: this и this полезны здесь.

Вы можете использовать это, чтобы подключиться к remote_command:

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

Есть некоторые недостающие биты

Вы заметите socat класс занимает в stream (в отличие от моего примера), что имеет методы write и readline. Я фактически инкапсулировал соединение SSH в классе, но я оставлю это как упражнение для читателя!

(В основном class Stream, который содержит sproc и обеспечивает write и readline методы - эффективно sproc.stdin.write() и sproc.stdout.readline() - вы получите идею)

Кроме того, вам нужен протокол, чтобы remote_command сигнализировать конец передачи , Я использовал символ ASCII EOT, и моя удаленная программа отправляет его в конце ответа. socat использует это, чтобы закрыть клиентское соединение и принять новый. В зависимости от вашего прецедента вам понадобится протокол, который будет работать для вас.

Наконец

Все перечисленные выше, могут быть улучшены. То, что выше, - это мой первый разрез, потому что просто видеть внутреннюю работу. С тех пор я расширил его, чтобы запустить цикл принятия в потоке и изящно обрабатывать разъединения и так далее.

Но, надеюсь, что выше, является разумной отправной точкой.

Смежные вопросы