san802k
san802k

Reputation: 23

TCL Client hangs when I send "invalid command" to python server

I have a python server and a TCL client in my setup both running on the same machine using port 8020.

I basically have some functions implemented in python server to perform some operations.

And I am trying to add some exception handling in the python server especially when a TCL client tries to call some function in python which is not yet available. The python server should alert the TCL client that the functionality is not available as yet.

I have copy pasted my code below:-

PYTHON SERVER CODE

import socket
import sys
import os

DUT_Browser = ""
host = 'localhost'
port = 8020
print 'Current working Directory:',os.getcwd()


def LogError(message):
    try:
        logfile = open("log.txt", "a")
        try:
            logfile.write(message)
        finally:
            logfile.close()
    except IOError:
        pass

def GetParam(sParams, i):
    params = sParams.split(",")
    p = params[i].strip()
    p = p.strip("\"")

    return p

def Login(sParams):
    print "In method Login with parameters:", sParams

    return 0


def ExecuteKeyword(sCommand):
    vals = sCommand.partition("(")
    sKeyword = vals[0].strip()
    sParams = vals[2].strip()
    # Remove the trailing ")" from the parameters string
    sParams = sParams[:-1]
    #print "KEYWORD: ", sKeyword, "PARAMETERS: ", sParams
    func = getattr(sys.modules[__name__],sKeyword)
    failed = 1
    failed=func(sParams)
    if failed:
        return 1
    else:
        return 0

def clientthread(conn):
    while True:
        keyword = conn.recv(1024)
        errorMsg = 'none'
        failed = 1
        try:
           failed=ExecuteKeyword(keyword)
        except (Exception, FindFailed), err:
        #except Exception, err:
            errorMsg=str(err)
            LogError(errorMsg)
        if failed:
            reply = 'FAIL START' + errorMsg + 'FAIL END'
            print 'ERROR MSG:',reply
        else:
            reply = 'PASS: ' + keyword
        if not keyword:
           break
        print 'SERVER REPLY',reply
        conn.sendall(reply)
        print 'SERVER REPLY SENT'
    conn.close()

sdServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'TCP (SOCK_STREAM) Socket created'

try:
    sdServer.bind((host, port))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind to port:',port,'on host:',host,'is complete'

#Start listening on socket
sdServer.listen(10)
print 'Socket now listening for 10 connections'
while 1:
    #wait to accept a connection - blocking call
    conn, addr = sdServer.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])
    #try:
    clientthread(conn)
    #except socket.error , err:
    #    print str(err)

sdServer.close()

TCL Client code

set port 8020
set host localhost
set ntoIP "http://10.218.164.58/"
set sdClient [socket $host $port]
fconfigure $sdClient -buffering line
set ntoGuiCmd start
while {$ntoGuiCmd != "end"} {
    puts "Enter some command for $sdClient"
    set ntoGuiCmd [gets stdin]
    puts $sdClient $ntoGuiCmd
    if {[eof $sdClient]} {close $sdClient}
    gets $sdClient serverMessage
    puts "From Server : $serverMessage"
}

When I enter following command from the client it passes and behaves as expected and the message from server is printed.

Login("10.218.164.58",admin,admin,\"FALSE\")

However when I use the following command which is not yet implemented the client simply hangs and does not print the exception from the server indicating that its not yet implemented.

Logout("10.218.164.58")

I even tried using wireshark and noticed that the server did send out the message and also the TCL client seems to have sent out an ACK in response. So not really sure why "gets" should hang.

Upvotes: 2

Views: 813

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137567

I'm not quite sure what's going wrong, but I suspect that the problem is in the Tcl code. There are a number of issues possible (is there a newline being passed back?) but the key thing is that the code is actually not especially idiomatic. Here's a more idiomatic version:

set port 8020
set host localhost
set sdClient [socket $host $port]
fconfigure $sdClient -buffering line
while true {
    puts "Enter some command for $sdClient"
    gets stdin ntoGuiCmd
    if {[eof stdin] || $ntoGuiCmd eq "end"} {
        break
    }
    if {[catch {puts $sdClient $ntoGuiCmd}]} {
        break
    }
    ### Replacement discussed below starts here
    gets $sdClient serverMessage
    if {[eof $sdClient]} {
        break
    }
    puts "From Server: $serverMessage"
    # If multiline messages are possible, extra work is required here!
    ### Replacement ends here
}
close $sdClient

If you've got multiline responses, you need to take considerably more care. The best way is to read them until you know you've reached the end of the response (e.g., because the server told you the number of bytes or the number of lines) but that's not always possible. If you assume that responses always fit in a single packet, you can use this to read the lines that are available (it works by reading from the socket in non-blocking mode while there are unread bytes):

fconfigure $sdClient -blocking 0
while {[gets $sdClient serverMessage] >= 0} {
    puts "From Server: $serverMessage"
}
fconfigure $sdClient -blocking 1
if {[eof $sdClient]} {
    break
}

If a response is large enough (which depends on OS buffers, etc) then it will not get written in one packet. That's when you really need a clear marker in your messaging protocol of where the end of the response is. (The same is also true in the reverse direction, except there it's large requests that cause problems.)


If this was all my own code, I'd be tempted to do a fully asynchronous nonblocking client:

proc transfer {from to} {
    if {[gets $from line] >= 0} {
        puts $to $line
    } elseif {[eof $from]} {
        exit
    }
}

set sock [socket "localhost" 8020]
fconfigure stdin -blocking 0
fconfigure $sock -blocking 0
fileevent stdin readable [list transfer stdin $sock]
fileevent $sock readable [list transfer $sock stdout]
vwait forever

However, this is a very different style of client (and one that isn't easy to wrap into a front end API).

Upvotes: 1

Related Questions