2012-04-14 8 views
0

У меня есть базовый сценарий клиент-сервер в Python с использованием сокетов. Сервер привязывается к определенному порту и ждет клиентского соединения. Когда клиент подключается, ему предоставляется приглашение raw_input, которое отправляет введенные команды подпрограмме на сервер и передает результат обратно клиенту. Иногда, когда я выполняю команды от клиента, вывод будет зависать и не показывать мне запрос raw_input до тех пор, пока я не нажму клавишу [enter]. Сначала я думал, что это может быть проблема буфера, но это происходит, когда я использую команды с небольшим выходом, как «ясный» или «LS» и т.д.Скрипт клиент-сервер Python зависает, пока я не нажимаю [enter]

код клиента:

import os, sys 
import socket 
from base64 import * 
import time 


try: 
    HOST = sys.argv[1] 
    PORT = int(sys.argv[2]) 
except IndexError: 
    print("You must specify a host IP address and port number!") 
    print("usage: ./handler_client.py 192.168.1.4 4444") 
    sys.exit() 

socksize = 4096 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
try: 
    server.connect((HOST, PORT)) 
    print("[+] Connection established!") 
    print("[+] Type ':help' to view commands.") 
except: 
    print("[!] Connection error!") 
    sys.exit(2) 


while True: 
    data = server.recv(socksize) 
    cmd = raw_input(data) 
    server.sendall(str(cmd)) 

server.close() 

Серверный код:

import os,sys 
import socket 
import time 
from subprocess import Popen,PIPE,STDOUT,call 

HOST = ''        
PORT = 4444 
socksize = 4096        
activePID = [] 

conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
conn.bind((HOST, PORT)) 
conn.listen(5) 
print("Listening on TCP port %s" % PORT) 

def reaper():        
    while activePID:       
     pid,stat = os.waitpid(0, os.WNOHANG)  
     if not pid: break 
     activePID.remove(pid) 


def handler(connection):      
    time.sleep(3)  

    while True:          
     cmd = connection.recv(socksize) 
     proc = Popen(cmd, 
       shell=True, 
      stdout=PIPE, 
      stderr=PIPE, 
       stdin=PIPE, 
      ) 
     stdout, stderr = proc.communicate() 

     if cmd == ":killme": 
      connection.close() 
      sys.exit(0) 

     elif proc: 
      connection.send(stdout) 
      connection.send("\nshell => ") 

    connection.close() 
    os._exit(0) 


def accept():         
    while 1: 
     global connection         
     connection, address = conn.accept() 
     print "[!] New connection!" 
     connection.send("\nshell => ") 
     reaper() 
     childPid = os.fork()      # forks the incoming connection and sends to conn handler 
     if childPid == 0: 
      handler(connection) 
     else: 
      activePID.append(childPid) 

accept() 
+0

Вопрос в том, что мне здесь не хватает? Кажется, что клиент должен иметь возможность перейти от вывода на печать к командной строке скорее без видимых на вид. Есть предположения? – ohdae

+0

Возможно, вы захотите проверить [argparse] (http://docs.python.org/library/argparse.html#module-argparse) аргументы командной строки - когда это так просто, как ваш код в настоящее время, на самом деле он не нужен , но если он становится более сложным, стоит посмотреть. –

+0

Весь код на самом деле немного сложнее, чем на самом деле, это всего лишь фрагменты клиентского сервера.Я просто немного перетасовал его, поэтому мои примеры не были на 5 страниц. Спасибо за совет, хотя :) – ohdae

ответ

0

проблема, которую я вижу в том, что конечный цикл в клиенте делает только один «server.recv (socksize)», а затем вызывает raw_input(). Если этот вызов recv() не получает все данные, отправленные сервером в этом одиночном вызове, то он также не будет собирать приглашение, которое следует за выходом команды, и поэтому не будет отображать это следующее приглашение. Неблокированный вход будет находиться в сокете, пока вы не введете следующую команду, а затем она будет собрана и показана. (В принципе, может потребоваться много вызовов recv(), чтобы слить сокет и перейти к прилагаемому запросу, а не только к двум вызовам.)

Если это то, что происходит, тогда вы столкнетесь с проблемой, если команда отправит больше, чем один буфер (4 Кбайт) данных, или если он сгенерировал вывод в небольших кусках, разнесенных во времени, чтобы серверная сторона могла распространять эти данные по нескольким путям, которые недостаточно объединены, чтобы клиент мог собрать их все в одном recv().

Чтобы исправить это, необходимо, чтобы клиент выполнял столько вызовов recv(), сколько требуется, чтобы полностью разрядить сокет. Поэтому вам нужно придумать, как клиент узнает, что сокет был исчерпан всем, что сервер собирается отправить в этом взаимодействии. Самый простой способ сделать это состоит в том, чтобы сервер добавлял пограничные маркеры в поток данных, а затем клиент проверял эти маркеры, чтобы узнать, когда были собраны окончательные данные из текущего взаимодействия. Существуют различные способы сделать это, но я бы, вероятно, добавил, что сервер вставляет маркер «это длина следующего блока данных» перед каждым отправленным им куском и отправляет маркер с длиной нуля после окончательного кусок. Основной цикл на стороне клиента затем становится «навсегда: читайте маркер, если длина, переносимая маркером, равна нулю, тогда перерыв, иначе прочитайте точно столько байтов». Обратите внимание, что клиент должен обязательно указать recv() полный маркер, прежде чем он начнет действовать; материал может выходить из потокового сокета в кусках любого размера, полностью не связанного с размером записей, которые отправили этот материал в сокет со стороны отправителя.

Вы должны решить, следует ли отправлять маркер в виде текста переменной длины (с отличительным разделителем) или как двоичный файл фиксированной длины (в этом случае вам придется беспокоиться о проблемах с endian, если клиент и сервер могут быть на разных системы). Вы также можете решить, должен ли клиент показывать каждый кусок по мере его поступления (очевидно, вы не можете использовать raw_input() для этого), или он должен собрать все куски и показать все это в один взрыв после последнего фрагмента был собран.

+0

Отлично, спасибо за ваш вклад! Я только что внедрил некоторые из упомянутых вами изменений и, похоже, работает отлично. Моя петля тоже была не в порядке. В любом случае, я ценю ответ! Работать до сих пор! – ohdae

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