server script hangs until I press [enter]

I have a basic client-server script in Python using sockets. The server binds to a specific port and waits for a client connection. When a client connects, they are presented with a raw_input prompt that sends the entered commands to a subproc on the server and pipes the output back to the client. Sometimes when I execute commands from the client, the output will hang and not present me with the raw_input prompt until I press the [enter] key. At first I thought this might have been a buffer problem but it happens when I use commands with a small output, like 'clear' or 'ls', etc.

The client code:

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

Server code:

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

The problem I see is that the final loop in the client only does one 'server.recv(socksize)', and then it calls raw_input(). If that recv() call does not obtain all of the data sent by the server in that single call, then it also won't collect the prompt that follows the command output and therefore won't show that next prompt. The uncollected input will sit in the socket until you enter the next command, and then it will be collected and shown. (In principle it could take many recv() calls to drain the socket and get to the appended prompt, not just two calls.)

If this is what's happening then you would hit the problem if the command sent back more than one buffer's worth (4KB) of data, or if it generated output in small chunks spaced out in time so that the server side could spread that data over multiple sends that are not coalesced quickly enough for the client to collect them all in a single recv().

To fix this, you need have the client do as many recv() calls as it takes to completely drain the socket. So you need to come up with a way for the client to know that the socket has been drained of everything that the server is going to send in this interaction. The easiest way to do this is to have the server add boundary markers into the data stream and then have the client inspect those markers to discover when the final data from the current interaction has been collected. There are various ways to do this, but I'd probably have the server insert a "this is the length of the following chunk of data" marker ahead of every chunk it sends, and send a marker with a length of zero after the final chunk. The client-side main loop then becomes "forever: read a marker; if the length carried in the marker is zero then break; else read exactly that many bytes;". Note that the client must be sure to recv() the complete marker before it acts on it; stuff can come out of a stream socket in lumps of any size, completely unrelated to the size of the writes that sent that stuff into the socket at the sender's side.

You get to decide whether to send the marker as variable-length text (with a distinctive delimiter) or as fixed-length binary (in which case you have to worry about endian issues if the client and server can be on different systems). You also get to decide whether the client should show each chunk as it arrives (obviously you can't use raw_input() to do that) or whether it should collect all of the chunks and show the whole thing in one blast after the final chunk has been collected.

链接地址: http://www.djcxy.com/p/9656.html

上一篇: input()和sys.stdin在CTRL上行为不当

下一篇: 服务器脚本挂起,直到我按[enter]