Chapter 43. Send

This chapter describes the send command that invokes Tcl commands in other applications. This chapter also presents an alternative to send that uses network sockets.

The send command lets Tk applications on the same display send each other Tcl commands and cooperate in very flexible ways. A large application can be structured as a set of smaller tools that cooperate instead of one large monolith. This encourages reuse, and it exploits your workstation's multiprogramming capabilities.

The send facility provides a name space for Tk applications. The winfo interps command returns the names of all the Tk applications reachable with send. The send communication mechanism is limited to applications running on one display. Multiple screens on one workstation still count as the same display on X. In UNIX, send uses properties on the X display for communication and to record the application names. As of Tk 8.0, send is not yet implemented on Macintosh or Windows. There is an extension for Windows that uses DDE to emulate send.

This chapter also describes an alternative to send that uses network sockets. The facility is not limited to a single display, and can be used in conjunction with safe interpreters to limit the capabilities of remote operations. A number of Tcl extensions provide similar functionality, including GroupKit and Tcl-DP. Of particular note is the comm package, which is a part of the Standard Tcl Library. The comm package was designed as a sockets-based replacement for send that would work on any platform. You can find more information about comm from the tcllib project page on SourceForge:

The send Command

The send command invokes a Tcl command in another application. The general form of the command is:

send options interp arg ?arg...?

The send command behaves like eval; if you give it extra arguments, it concatenates them to form a single command. If your argument structure is important, use list to build the command. Table 43-1 lists the options to send:

Table 43-1. Options to the send command

-async

Does not wait for the remote command to complete.

-displayof window

Sends to the application on the same display as window.

--

Delimits options from the interp argument. Useful if the interp begins with a dash.

The interp argument is the name of the other application. An application defines its own name when it creates its main window. The wish shell uses as its name the last component of the file name of the script. For example, when wish interprets /usr/local/bin/exmh, it sets its application name to exmh. However, if another instance of the exmh application is already running, wish chooses the name exmh #2, and so on. If wish is not executing from a file, its name is just wish. You may have noticed wish #2 or wish #3 in your window title bars, and this reflects the fact that multiple wish applications are running on your display.

A script can find out its own name, so you can pass names around or put them into files in order to set up communications. The tk appname command queries or changes the application name:

set myname [tk appname]
tk appname aNewName

Send and X Authority

The send command relies on the X authority mechanism for authorization. A command is rejected by the target interpreter if you do not have X authority set up. There are two ways around this problem. First, you can disable the access check by compiling the tkSend.c file with the -DTK_NO_SECURITY compile flag. If you must worry about malicious programs that send your programs commands, then you should not do this.

The second option is to start your X server with its -auth flag, which initializes the X authority mechanism. The details vary depending on your X server, and most modern X servers do this automatically. The general picture is that you generate a pseudo-random string and store it into a file, which is usually named ~/.Xauthority and must be readable only by your account. The -auth flag specifies the name of this file to the X server. Each X application reads this file and sends the contents to the X server when opening the connection to the server. If the contents match what the server read when it started, then the connection is allowed. The system is slightly more complicated than described here. The file actually contains a sequence of records to support multiple displays and client hosts. Consult your local X guru or the documentation for the details particular to your system.

Note

Send and X Authority

Your xhost list must be clear.

Tk also requires that the xhost list be empty. The xhost mechanism is the old, not-so-secure authentication mechanism in X. With xhost you allow all programs on a list of hosts to connect to your display. The problem with this is that multiuser workstations allow remote login, so essentially anybody could log in to a workstation on the xhost list and gain access to your display. The Xauthority mechanism is much stronger because it restricts access to your account, or to accounts that you explicitly give a secret token to. The problem is that even if Xauthority is set up, the user or a program can turn on xhosts and open up access to your display.

If you run the xhost program with no argument, it reports the status and what hosts are on the list. The following output is generated when access control is restricted, but programs running on sage are allowed to connect to the display:

exec xhost
=> Access control enabled: all hosts being restricted
sage

This is not good enough for Tk send. It will fail because sage is on the list. I work in an environment where old scripts and programs are constantly adding things to my xhost list for reasons that are no longer valid. I developed a version of send that checks for errors and then does the following to clean out the xhost list. You have to enable access control and then explicitly remove any hosts on the list. These are reported after an initial line that says whether or not hosts are restricted:

xhost - ;# enable access control in general
foreach host [lrange [split [exec xhost] 
] 1 end] {
    exec xhost -$host ;# clear out exceptions
}

The Sender Script

The following example is a general-purpose script that reads input and then sends it to another application. You can put this at the end of a pipeline to get a loopback effect to the main application, although you can also use fileevent for similar effects. One advantage of send over fileevent is that the sender and receiver can be more independent. A logging application, for example, can come and go independently of the applications that log error messages:

Example 43-1. The sender application

#!/usr/local/bin/wish
# sender takes up to four arguments:
# 1) the name of the application to send to.
# 2) a command prefix.
# 3) the name of another application to notify
#    after the end of the data.
# 4) the command to use in the notification.

# Hide the unneeded window
wm withdraw .
# Process command line arguments
if {$argc == 0} {
   puts stderr "Usage: send name ?cmd? ?uiName? ?uiCmd?"
   exit 1
} else {
   set app [lindex $argv 0]
}
if {$argc > 1} {
   set cmd [lindex $argv 1]
} else {
   set cmd Send_Insert
}
if {$argc > 2} {
   set ui [lindex $argv 2]
   set uiCmd Send_Done
}
if {$argc > 3} {
   set uiCmd [lindex $argv 3]
}
# Read input and send it to the logger
while {[gets stdin input] >= 0} {
   # Ignore errors with the logger
   catch {send $app [concat $cmd [list $input
]]}
}
# Notify the controller, if any
if [info exists ui] {
   if [catch {send $ui $uiCmd} msg] {
      puts stderr "send.tcl could not notify $ui
$msg"
   }
}
# This is necessary to force wish to exit.
exit

The sender application supports communication with two processes. It sends all its input to a primary "logging" application. When the input finishes, it can send a notification message to another "controller" application. The logger and the controller could be the same application.

Note

The sender applicationsender application

Use list to quote arguments to send.

Consider the send command used in the example:

send $app [concat $cmd [list $input
]]

The combination of concat and list is tricky. The list command quotes the value of the input line. This quoted value is then appended to the command, so it appears as a single extra argument. Without the quoting by list, the value of the input line will affect the way the remote interpreter parses the command. Consider these alternatives:

send $app [list $cmd $input]

This form is safe, except that it limits $cmd to a single word. If cmd contains a value like the ones given below, the remote interpreter will not parse it correctly. It will treat the whole multiword value as the name of a command:

.log insert end
.log see end ; .log insert end

This is the most common wrong answer:

send $app $cmd $input

The send command concatenates $cmd and $input together, and the result will be parsed again by the remote interpreter. The success or failure of the remote command depends on the value of the input data. If the input included Tcl syntax like $ or [ ], errors or other unexpected behavior would result.

Communicating Processes

Chapter 24 presented two examples: a browser for the examples in this book, and a simple shell in which to try out Tcl commands. In that chapter they are put into the same application. The two examples shown below hook these two applications together using the send command. Example 43-2 changes the Run and Reset procedures of the browser to send EvalEcho commands to the shell.

Example 43-2. Hooking the browser to an eval server

# Replace the Run and Reset procedures of the browser in
# Example 24–3 on page 384 with these procedures

# Start up the evalsrv.tcl script.
proc StartEvalServer {} {
   global browse
   # Start the shell and pass it our name.
   exec evalsrv.tcl [tk appname] &
   # Wait for evalsrv.tcl to send us its name
   tkwait variable browse(evalInterp)
}
proc Run {} {
   global browse
   set apps [winfo interps]
   set ix [lsearch -glob $apps evalsrv.tcl*]
   if {$ix < 0} {
      # No evalsrv.tcl application running
      StartEvalServer
   }
   if {![info exists browse(evalInterp)]} {
      # Hook up to already running eval server
      set browse(evalInterp) [lindex $apps $ix]
   }
   if [catch {send $browse(evalInterp) {info vars}} err] {
      # It probably died - restart it.
      StartEvalServer
   }
   # Send the command asynchronously. The two
   # list commands foil the concat done by send and
   # the uplevel in EvalEcho
   send -async $browse(evalInterp) 
      [list EvalEcho [list source $browse(current)]]
}
# Reset the shell interpreter in the eval server
proc Reset {} {
   global browse
   send $browse(evalInterp) {EvalEcho reset}
}

The number of lists created before the send command may seem excessive, but they are all necessary. The send command concatenates its arguments, so instead of letting it do that, we pass it a single list. Similarly, EvalEcho expects a single argument that is a valid command, so list is used to construct that.

The StartEvalServer procedure starts up the shell. Command-line arguments are used to pass the application name of the browser to the shell. The shell completes the connection by sending its own application name back to the browser. The browser stores the name of the shell application in browser(evalInterp). The code that the shell uses is shown in Example 43-3:

Example 43-3. Making the shell into an eval server

# Add this to the shell application shown
# in Example 24–4 on page 389
if {$argc > 0} {
   # Send our application name to the browser
   send [lindex $argv 0] 
      [list set browse(evalInterp) [tk appname]]
}

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 43-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 43-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 43-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 43-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

      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]

         # Use regsub to count newlines
         set lines [regsub -all 
 $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 43-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 86.

Example 43-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

      incr lines -1
   }
   set code [lindex $result 0]
   set x [lindex $result 1]
   # Cleanup the end of the stack
   regsub "[^
]+$" [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]"
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.70.163