我使用套接字在Python中有一个基本的客户端 - 服务器脚本。服务器绑定到特定端口并等待客户端连接。当客户端连接时,它们会显示一个raw_input提示符,该提示符将输入的命令发送到服务器上的子proc并将输出传递回客户端。 有时当我从客户端执行命令时,输出将挂起,并且不会显示raw_input提示符,直到我按下[enter]键。 起初我认为这可能是一个缓冲问题,但是当我使用输出较小的命令时会发生这种情况,比如'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()
答案 0 :(得分:1)
我看到的问题是客户端中的最后一个循环只执行一个server.recv(socksize)
,然后调用raw_input()
。如果recv()
调用未获得服务器在该单个调用中发送的所有数据,则它也不会收集命令输出后面的提示,因此不会显示下一个提示。未收集的输入将位于套接字中,直到您输入下一个命令,然后它将被收集并显示。 (原则上,它可能需要多次recv()
次调用才能耗尽套接字并进入后面的提示,而不仅仅是两次调用。)
如果发生了这种情况,那么如果命令发送回多个缓冲区值(4KB)的数据,或者如果它以及时间隔的小块生成输出,那么服务器端可以传播多个发送的数据没有足够快地合并,客户端可以在一个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()
来执行此操作)或者是否应该收集所有块并在一次爆炸后显示整个块已经收集了最后一块。