Python 客户端-服务器脚本挂起,直到我按下 [enter]

Posted

技术标签:

【中文标题】Python 客户端-服务器脚本挂起,直到我按下 [enter]【英文标题】:Python client-server script hangs until I press [enter] 【发布时间】:2012-04-14 18:17:05 【问题描述】:

我在 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()

【问题讨论】:

问题是我在这里缺少什么?似乎客户端应该能够从打印输出转换到 shell 提示,而不是无缝。有什么想法吗? 您可能想查看argparse 以获取命令行参数 - 当它与您的代码一样简单时,它并不是真正需要的,但如果它变得更复杂,则值得一看。 整个代码实际上比这复杂得多,这只是一个客户端服务器sn-ps。我只是稍微改了一下,所以我的例子不是 5 页长。不过谢谢你的建议:) 【参考方案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() 来执行此操作),或者它是否应该收集所有块并在最后一个块之后一次性显示整个内容已收集。

【讨论】:

太好了,感谢您的意见!我刚刚实施了您提到的一些更改,并且看起来效果很好。我的循环也有点乱。不管怎样,我很感激你的回应!到目前为止工作良好!

以上是关于Python 客户端-服务器脚本挂起,直到我按下 [enter]的主要内容,如果未能解决你的问题,请参考以下文章

Endles循环(while循环)运行,直到我按下一个键。编程C

Pip 安装挂起

C# - 批处理文件输出不显示“按任意键继续...”直到我按下一个键

调用通过 ssh 创建子进程的 Python 脚本挂起

如何编写 Flash 动作脚本来进行此鼠标操作?

使用 asyncio 的 Python 网络