views:

35

answers:

2

Dear Friends!

I am beginner in socket programming and need your help. I have implemented simple echo server using ThreadedTCPServer example from the Python documentation. It works fine, but I have the following problems: 1) Server hangs (in socket.recv) when Client tries to send zero-length data. 2) Server hangs (in socket.recv) when the data sent by Client is multiple of BUF_SIZE. 3) I don't know the right way to stop the server from the outside. I'd like to have a script, for example, stopServer.py which can be started from the server's host when need to stop the server. I've implemented "STOP" command which is send to the server's port. This approach looks good for me, but has security risk. Note that I need a cross-platform solution. So, probably, signals are not suitable. I will appreciate if you have any ideas how to fix the issues listed above. The example's code is listed below.

Have a nice day! Zakhar

client.py

import sys
import socket
from common import recv

if len (sys.argv) == 1 :
    print "The file to be sent is not specified"
    sys.exit (1)
fname = sys.argv [1]

print "Reading '%s' file..." % fname,
f = open (sys.argv [1])
msg = f.read ()
f.close()
print "OK"

if len (msg) == 0 :
    print "Nothing to send. Exit."
    sys.exit (0)

print "Client is sending:"
print msg
print "len (msg)=%d" % len (msg)

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.connect ( ('localhost', 9997) )
sock.sendall (msg)
print "Sending has been finished"
print "Waiting for response..."
response = recv (sock)
print "Client received:"
print response
print "len (response)=%d\n" % len (response)
sock.close ()

server.py

import threading
import SocketServer
from common import recv

class ThreadedTCPRequestHandler (SocketServer.BaseRequestHandler):

    def handle(self) :
        recvData = recv (self.request)
        print "NEW MSG RECEIVED from %s" % self.client_address [0]
        print "Data:"
        print recvData
        print "dataSize=%d" % len (recvData)
        if recvData == "!!!exit!!!" :
            print 'Server has received exit command. Exit.'
            self.server.shutdown()
        else :
            curThread = threading.currentThread ()
            response = "%s: %s" % (curThread.getName (), recvData)
            self.request.sendall (response)

class ThreadedTCPServer (SocketServer.ThreadingMixIn, SocketServer.TCPServer) :
    pass

if __name__ == "__main__":
    HOST, PORT = "localhost", 9997

    server = ThreadedTCPServer ((HOST, PORT), ThreadedTCPRequestHandler)
    server.serve_forever ()

common.py

'''
Common code for both: client and server.
'''

def recv (sock) :
    '''
    Reads data from the specified socket and returns them to the caller.
    IMPORTANT:
    This method hangs in socket.recv () in the following situations (at least
    on Windows):
    1. If client sends zero-length data.
    2. If data sent by client is multiple of BUF_SIZE.
    '''
    BUF_SIZE = 4096
    recvData = ""
    while 1 :
        buf = sock.recv (BUF_SIZE)
        if not buf :
            break
        recvData += buf
        if len (buf) < BUF_SIZE : # all data have been read
            break
    return recvData
+1  A: 

I don't know python but I will try and answer based on my socket programming knowledge.

A zero byte message will NOT cause "sock.recv" to come out. Refer here for an explanation. If sock.recv returns with length 0, it is an indication that the other side has disconnected the TCP connection.

if len (buf) < BUF_SIZE

How does this line determine that ALL data has been read. TCP recv call is not a 1-1 mapping between TCP send call i.e. what client sends in 1 send call can be received in multiple recv calls.

When you send multiple of BUF_SIZE i.e lets say 2 * BUF_SIZE, it is possible that you might receive all of it in one go. At that point, your server would hang as len(buf) > BUF_SIZE and you would get stuck in sock.recv. Try & figure out how to use non-blocking sockets.

Aditya Sehgal
Thanks for reply. About "if len (buf) < BUF_SIZE"... looks like you didn't catch the idea. BUF_SIZE is the maximum amount of data to be received at once. So, I am receiving an incoming message by parts (every part can't be larger than BUF_SIZE), one after another. If received part is less than BUF_SIZE, it means that we have reached the end of message. So, the situation when "len(buf) > BUF_SIZE" is impossible. See socket.recv python doc for details.
Zakhar
recv calls generally return ANY data available on socket limited by BUF_SIZE (in your case). As i said before, in TCP, if you send BUF_SIZE data in one recv, there is NO guarantee that you will recv them via ONE single recv call.
Aditya Sehgal
A: 

After some experiments I found the following: 1) Need to rename common.recv to common.recvall 2) Need to comment the following 2 lines in common.recv (bug): if len (buf) < BUF_SIZE : # all data have been read break 3) This example will work only if the data sent to the server can be read by 1 socket.recv call on the server side. So, the sent data must be less than BUF_SIZE. Otherwise, the client and server are in deadlock, both in their sock.recv call. This occurs because of the concurrent access to the shared resource (socket). To solve this the client must listen for reply on different port.

Zakhar