1

ソケットを使用した Python の基本的なクライアント サーバー スクリプトがあります。サーバーは特定のポートにバインドし、クライアント接続を待ちます。クライアントが接続すると、raw_input プロンプトが表示され、入力されたコマンドがサーバー上のサブプロセスに送信され、出力がパイプでクライアントに戻されます。クライアントからコマンドを実行すると、出力がハングし、[enter] キーを押すまで raw_input プロンプトが表示されないことがあります。最初はこれはバッファの問題かもしれないと思っていましたが、「clear」や「ls」などの出力が小さいコマンドを使用すると発生します。

クライアントコード:

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()
4

1 に答える 1

1

私が見る問題は、クライアントの最終ループが 1 つだけを実行しserver.recv(socksize)、その後 を呼び出すことraw_input()です。そのrecv()呼び出しが、その 1 回の呼び出しでサーバーから送信されたすべてのデータを取得しない場合、コマンド出力に続くプロンプトも収集されないため、次のプロンプトは表示されません。収集されていない入力は、次のコマンドを入力するまでソケットに留まり、その後収集されて表示されます。(原則としてrecv()、ソケットを空にし、追加されたプロンプトに到達するには、2 回の呼び出しだけでなく、多くの呼び出しが必要になる可能性があります。)

これが発生している場合、コマンドが複数のバッファー (4KB) に相当するデータを送り返した場合、またはサーバー側がそのデータを複数のバッファーに分散できるように、時間間隔を空けて小さなチャンクで出力を生成した場合に、問題が発生します。クライアントがそれらすべてを 1 つのrecv().

これを修正するにはrecv()、ソケットを完全に空にするのに必要な数の呼び出しをクライアントに実行させる必要があります。したがって、クライアントが、サーバーがこの対話で送信しようとしているすべてのものがソケットから排出されたことを知る方法を考え出す必要があります。

これを行う最も簡単な方法は、サーバーに境界マーカーをデータ ストリームに追加させ、クライアントにそれらのマーカーを検査させて、現在のインタラクションからの最終データがいつ収集されたかを検出することです。これを行うにはさまざまな方法がありますが、サーバーが送信するすべてのチャンクの前に「これは次のデータチャンクの長さです」マーカーを挿入し、最終的な後に長さゼロのマーカーを送信するようにします。かたまり。

クライアント側のメイン ループは次のようになります。

forever: 
    read a marker; 
    if the length carried in the marker is zero then 
        break; 
    else 
        read exactly that many bytes;.  

recv()クライアントが動作する前に、完全なマーカーを確認する必要があることに注意してください。送信者側のソケットにその内容を送信した書き込みのサイズとはまったく関係なく、任意のサイズの塊でストリーム ソケットから出力される可能性があります。

マーカーを可変長テキスト (固有の区切り文字を使用) として送信するか、固定長バイナリ (この場合、クライアントとサーバーが異なるシステム上にある可能性がある場合はエンディアンの問題を心配する必要があります) として送信するかを決定できます。また、クライアントが到着するたびに各チャンクを表示する必要があるかどうか (明らかに、それraw_input()を行うために使用することはできません)、またはすべてのチャンクを収集して、最後のチャンクが収集された後にすべてを 1 回のブラストで表示する必要があるかどうかを決定することもできます。 .

于 2012-04-15T19:12:08.617 に答える