Reputation: 127
I am using TCL socket command to communicate between two TCL/Tk applications say A and B where both have a associated GUI. The server on B basically accepts commands from A and returns the result of the execution. The problem is that the GUI for B hangs as soon as the execution is completed. Is there a way to enable the two GUI's to work independently? I am using the following scripts to setup the connection between the two applications:
Start application A with the following TCL script client.tcl
proc execute { cmd } {
set cid [socket localhost 9900]
puts $cid $cmd
while { [gets $cid line] >= 0 } {
puts $line
}
close $cid
}
set pid [exec B server.tcl &]
execute {puts HelloWorld}
where server.tcl sets up the server through application B
proc server { cid addr port } {
set cmd [gets $cid]
catch $cmd result
puts $cid result
close $cid
}
socket -server server 9900
vwait forever
The goal is to let the GUI for B be active while user continues to work with GUI for A. That way the user can switch between the two GUI's on a need basis. Both A and B provide different functionality for working with the same data which need to be made available at the same time.
Upvotes: 2
Views: 1525
Reputation: 137567
The code that you have posted has two key problems:
The client doesn't flush it's socket after writing a piece of work (i.e., command to execute) to it because non-default channels (not stdin, stdout, stderr) are fully buffered by default for performance reasons. This can result in the server never actually receiving the command in the first place.
The server doesn't handle the accepted socket in non-blocking mode via a fileevent
-registered script.
Also, you have a slight problem in that if you ever want to send multi-line data, you'll find your protocol inadequate. You might or might not care about this, and there are a few different ways to fix it.
The best way of dealing with the client's big problem is to use
fconfigure $cid -buffering none
straight after opening the socket. That tells Tcl that it should always send data you puts
to the channel out on the socket immediately. (Alternatively, use line
instead of none
to get line buffering; that will work well enough in this case too.) You could also do an explicit flush:
flush $cid
That would go after the puts
.
Best practice would use a server script much more like this, with one procedure to set up the servicing of an accepted socket connection, and another procedure to handle the reading and writing of the messages. The accept procedure (server
) turns off blocking (and often adjusts buffering too) and the operation procedure (readLine
) uses eof
and fblocked
to work out if the gets
succeeded or failed.
proc server { cid addr port } {
fconfigure $cid -blocking 0
fileevent $cid readable "readLine $cid"
}
proc readLine { cid } {
set cmd [gets $cid]
if { [eof $cid] } {
close $cid
} elseif { ![fblocked $cid] } {
catch $cmd result
puts $cid $result
close $cid
}
}
socket -server server 9900
vwait forever
The other big problem if you're doing this for real is that multi-line messages are useful. Either you'll need to make readLine
above accumulate lines until it has a whole command (append
and info complete
help here) or you'll need some other mechanism to handle the data transfer. In production code, it gets easier to delegate to a package like comm…
Upvotes: 2