2012-04-14 11 views
0

我有一個基本的客戶端服務器腳本在Python中使用套接字。服務器綁定到特定的端口並等待客戶端連接。當客戶端連接時,它們會顯示一個raw_input提示符,用於將輸入的命令發送到服務器上的一個子進程並將輸出返回給客戶端。 有時,當我執行來自客戶端的命令時,輸出將掛起,直到按[Enter]鍵時纔會顯示raw_input提示符。 起初我以爲這可能是一個緩衝問題,但它發生在我使用的命令用小的輸出,像「清除」或「LS」等Python客戶端 - 服務器腳本掛起,直到我按[輸入]

客戶端代碼:

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

問題是我在這裏錯過了什麼?看起來,客戶端應該能夠從打印輸出轉換到shell提示符,而不是毫無意義。有什麼想法嗎? – ohdae 2012-04-14 18:19:04

+0

您可能想查看[argparse](http://docs.python.org/library/argparse.html#module-argparse)的命令行參數 - 當它與您當前的代碼一樣簡單時,並非真正需要,但如果它變得更復雜,那麼值得一看。 – 2012-04-14 18:21:53

+0

整個代碼比實際的代碼複雜得多,這只是一個客戶端服務器代碼片段。我只是把它打亂了一點,所以我的例子不是5頁長。感謝您的建議,雖然:) – ohdae 2012-04-14 18:35:08

回答

0

我看到的問題是,在客戶端的最後循環只做一個「server.recv(socksize)」,然後它調用的raw_input()。如果recv()調用沒有獲得服務器在該單個調用中發送的所有數據,那麼它也不會收集命令輸出後面的提示,因此不會顯示下一個提示。未收集的輸入將位於套接字中,直到您輸入下一個命令,然後收集並顯示它。 (原則上,它可能需要很多recv()調用來排空套接字並獲得附加提示,而不僅僅是兩個調用。)

如果這是發生了什麼,那麼如果發送的命令超過了一個緩衝區的價值(4KB)的數據,或者如果它產生的輸出時間間隔很小的小塊,以便服務器端可以通過多個發送傳播數據,這些發送不足以快速合併,以便客戶端將它們全部收集到一個recv ()。

要解決這個問題,您需要讓客戶端執行儘可能多的recv()調用,以完全耗盡套接字。所以你需要想出一種方法讓客戶端知道在這個交互中服務器將發送的所有東西都被套接字排空了。最簡單的方法是讓服務器將邊界標記添加到數據流中,然後讓客戶端檢查這些標記以發現何時收集了當前交互的最終數據。有很多種方法可以做到這一點,但我可能會在服務器發送的每個數據塊之前插入一個「這是以下數據塊的長度」標記,並在最後發送一個長度爲零的標記塊。然後,客戶端主循環變爲「永遠:讀取標記;如果標記中攜帶的長度爲零,則中斷;否則讀取多個字節;」。請注意,客戶必須確保在完成標記之前recv();東西可以以任意大小的塊形式從流套接字中出來,與發送給發送端側套接字的寫入大小完全無關。

您可以決定是將標記作爲可變長度文本(帶有明確的分隔符)還是作爲固定長度二進制文件發送(在這種情況下,如果客戶端和服務器可以不同系統)。您還可以決定客戶端是否應該顯示每個塊(當然,您不能使用raw_input()來做到這一點),還是應該收集所有塊並在最後一塊之後顯示整個事件已被收集。

+0

太好了,謝謝你的輸入! 我剛剛實施了一些你提到的改變,它似乎工作得很好。我的循環也失靈了一點。無論哪種方式,我都很欣賞這種迴應!目前運作良好! – ohdae 2012-04-16 23:23:10

相關問題