2
votes

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.

1
Yes, there is a way; in fact, there are several ways. You'll have to provide more information about what B is doing in order for us to be able to help. If you've never coded asynchronous programs before, you're going to have to learn: it can be brain-bending to start with.Donal Fellows
@DonalFellows I have added more information on what I am trying to do. Please let me know if you need anything else. In the meantime I will try to dig into working with asynchronous programs.balaji kommineni
Donal after trying a few more things I found that when I use wish for both client and server they work fine independently. Looks like the problem is with the program I am using in conjunction with wish. Not sure how I can find the problem. Also the server is still working fine and I can get back command outputs. It's only the GUI which is not responding. My own tiny wish widget on the server seems to work fine. Thanks for your help.balaji kommineni
Donal, using open to run the program seems to work in this case. I am able to send commands to the program and get back the result with the program B running in the background. Will this be a right approach.balaji kommineni
Have a look at the tcllib comm package, especially the async parts, it might do what you want. (tcllib.sourceforge.net/doc/comm.html)schlenk

1 Answers

2
votes

The code that you have posted has two key problems:

  1. 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.

  2. 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.

Key Problem 1

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.

Key Problem 2

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 Problem

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…