Remote eval through Sockets

   

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 40.  Send


Remote eval through Sockets

Network sockets provide another communication mechanism you can use to evaluate Tcl commands in another application. The "name" of the application is just the host and port for the socket connection. There are a variety of schemes you can use to manage names. A crude, but effective way to manage host and ports for your servers is to record them in a file in your network file system. These examples ignore this problem. The server chooses a port and the client is expected to know what it is.

Example 40-4 implements Eval_Server that lets other applications connect and evaluate Tcl commands. The interp argument specifies the interpreter in which to evaluate the Tcl commands. If the caller of Eval_Server specifies {} for the interpreter, then the commands are evaluated in the current interpreter. The openCmd is called when the connection is made. It can do whatever setup or authentication is required. If it doesn't like the connection, it can close the socket:

Example 40-4 Remote eval using sockets.
 proc Eval_Server {port {interp {}} {openCmd EvalOpenProc}} {    socket -server [list EvalAccept $interp $openCmd] $port } proc EvalAccept {interp openCmd newsock addr port} {    global eval    set eval(cmdbuf,$newsock) {}    fileevent $newsock readable [list EvalRead $newsock $interp]    if [catch {       interp eval $interp $openCmd $newsock $addr $port    }] {       close $newsock    } } proc EvalOpenProc {sock addr port} {    # do authentication here    # close $sock to deny the connection } 

Example 40-5 shows EvalRead that reads commands and evaluates them in an interpreter. If the interp is {}, it causes the commands to execute in the current interpreter. In this case an uplevel #0 is necessary to ensure the command is executed in the global scope. If you use interp eval to execute something in yourself, it executes in the current scope:

Example 40-5 Reading commands from a socket.
 proc EvalRead {sock interp} {    global eval errorInfo errorCode    if [eof $sock] {       close $sock    } else {       gets $sock line       append eval(cmdbuf,$sock) $line\n       if {[string length $eval(cmdbuf,$sock)] && \              [info complete $eval(cmdbuf,$sock)]} {          set code [catch {            if {[string length $interp] == 0} {               uplevel #0 $eval(cmdbuf,$sock)            } else {               interp eval $interp $eval(cmdbuf,$sock)            }          } result]          set reply [list $code $result $errorInfo \             $errorCode]\n          # Use regsub to count newlines          set lines [regsub -all \n $reply {} junk]          # The reply is a line count followed          # by a Tcl list that occupies that number of lines          puts $sock $lines          puts -nonewline $sock $reply          flush $sock          set eval(cmdbuf,$sock) {}       }    } } 

Example 40-6 presents Eval_Open and Eval_Remote that implement the client side of the eval connection. Eval_Open connects to the server and returns a token, which is just the socket. The main task of Eval_Remote is to preserve the information generated when the remote command raises an error

The network protocol is line-oriented. The Eval_Remote command writes the command on the socket. The EvalRead procedure uses info complete to detect the end of the command. The reply is more arbitrary, so server sends a line count and that number of lines. The regsub command counts up all the newlines because it returns the number of matches it finds. The reply is a list of error codes, results, and trace information. These details of the return command are described on page 80.

Example 40-6 The client side of remote evaluation.
 proc Eval_Open {server port} {    global eval    set sock [socket $server $port]    # Save this info for error reporting    set eval(server,$sock) $server:$port    return $sock } proc Eval_Remote {sock args} {    global eval    # Preserve the concat semantics of eval    if {[llength $args] > 1} {       set cmd [concat $args]    } else {       set cmd [lindex $args 0]    }    puts $sock $cmd    flush $sock    # Read return line count and the result.    gets $sock lines    set result {}    while {$lines > 0} {       gets $sock x       append result $x\n       incr lines -1    }    set code [lindex $result 0]    set x [lindex $result 1]    # Cleanup the end of the stack    regsub "\[^\n]+$" [lindex $result 2] \       "*Remote Server $eval(server,$sock)*" stack    set ec [lindex $result 3]    return -code $code -errorinfo $stack -errorcode $ec $x } proc Eval_Close {sock} {    close $sock } 

If an error occurs in the remote command, then a stack trace is returned. This includes the command used inside EvalRead to invoke the command, which is either the uplevel or interp eval command. This is the very last line in the stack that is returned, and regsub is used to replace this with an indication of where control transferred to the remote server:

 catch [Eval_Remote sock6 set xx] => 1 set errorInfo => can't read "xx": no such variable     while executing "set xx "     ("uplevel" body line 1)     invoked from within *Remote Server sage:4000*     invoked from within "catch [Eval_Remote sock6 set xx]" 

       
    Top
     



    Practical Programming in Tcl and Tk
    Practical Programming in Tcl and Tk (4th Edition)
    ISBN: 0130385603
    EAN: 2147483647
    Year: 1999
    Pages: 478

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net