Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Does close() unset the descriptor variable?

Status
Not open for further replies.

marsd

IS-IT--Management
Apr 25, 2001
2,218
US
I have a situation where I am running a trace on a socket descriptor in order to keep state on the service: number of connections, state, time up, etc..

The problem is that the trace never fires when the socket is closed. Does the socket descriptor get unset at this time
or not, and if not, how else do people deal with trying to manage information like this when working with tcl sockets?
 
No, the close command doesn't unset any variable that might contain the corresponding file ID. It would actually be a bit difficult to do, as Tcl has no good way of knowing if you've copied the file ID to other variables.

The way to detect when a channel closes is to test for the end-of-file condition. If you have a readable fileevent handler registered for the channel, EOF is a readable event that triggers the handler. In fact, EOF is a persistent readable event, that will generate an unending series of readable events on the channel until you close the channel. That's why you should always test for the EOF condition in your fileevent handlers and close the channel if you detect it.

The way you actually detect the EOF condition (as opposed to simply a readable event generated by data arriving) depends on whether the channels is in blocking mode (the default) or non-blocking mode.

In blocking mode, a gets command will return "-1" as the number of characters read, and a read command will return an empty string. It's more complicated in non-blocking mode, as gets returns "-1" characters read either if it encounters EOF or it couldn't read a complete line. A non-blocking read returns an empty string either for EOF or if there's no data to read.

So how do you detect EOF if you're using non-blocking channel? With the eof command. It returns a boolean True (1) if the last attempt to read a channel encountered an EOF. So, eof doesn't return True when you read the last of the data from the channel, but only when you try to read and there is no more data to read. (By the way, eof also works with blocking channels, but there isn't nearly as much as a need for the command in that case.)

Here's a skeleton fileevent handler that I use for most of my blocking channels:

Code:
proc ReadLine {fid} {
    if {[catch {gets $fid line} len] 
        || $fid == -1} {

        # The last gets encountered EOF or
        # we had an abnormal termination

        catch {close $fid}
        HandleEof $fid

    } else {

        # We read a complete line from
        # the channel

        ProcessLine $fid $line
    }
}

set fid [open "|some command" r+]

# Or set fid [socket $host $port]

fconfigure $fid –buffering line
fileevent $fid readable [list ReadLine $fid]

In contrast, here's the skeleton handler that I use for non-blocking channels:

Code:
proc ReadLine {fid} {
    if {[catch {gets $fid line} len] 
        || [eof $fid]} {

        # The last gets encountered EOF or
        # we had an abnormal termination

        catch {close $fid}
        HandleEof $fid

    } elseif {$len >= 0} {
        ProcessLine $fid $line
    }

    # We reach this point if there wasn't a
    # complete line to read. Drop back into
    # the event loop and wait for more data.
}

set fid [open "|some command" r+]

# Or set fid [socket $host $port]

fconfigure $fid –buffering line -blocking 0
fileevent $fid readable [list ReadLine $fid]

The only other case you have to look out for is an error sending data down the channel using puts. Whenever I do a puts to a channel, I run it in a catch to detect that situation and close the channel. Actually, I usually use a procedure to take care of all of that for me. For example:

Code:
proc SendMessage {fid msg} {
    if {[catch {puts $fid $msg} error]} {
        puts stderr "Error writing to socket: $error"
        catch {close $fid}
    }
}

It's all a bit tricky because Tcl, like most applications, doesn't detect sockets "going down" until you actually try to read from or write to the socket. You really don't know if that long period of inactivity on a channel is because the other end is quiet or because someone accidentally kicked the network cable out of your machine unless you try to use it. - Ken Jones, President, ken@avia-training.com
Avia Training and Consulting, 866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Yes, I figured out most of this soon after I posted.
Instead I did this, and since I consolidated all channel
id info in a global it makes it easier to track things.


proc poll {t} {
global Gvar
foreach id $Gvar(sockids) {
if {[catch {eof $id}] || [eof $id]} {
lreplace $Gvar(sockids) [lsearch -exact $Gvar(sockids) $id] [lsearch -exact $Gvar(sockids) $id] ""
if {[string compare $id $Gvar(worksock)] == 0} {
set Gvar(worksock) "" ; incr Gvar(resets) ; incr Gvar(connections) -1
}
}
}
puts "###Junk collection### "
parray Gvar
after $t {poll $Gvar(junkc)}
}
 
Not a bad try, marsd, but it won't work. The eof command doesn't detect the EOF condition until you've actually tried to read from the channel using gets or read. So, even if the client has disconnected, eof won't detect it until you try to read from the channel.

I just verified this behavior with a couple of interactive wish shells. I started up a server socket in one that simply stored the connection information away in a global array. In the other, I established a socket connection to the first session. I then closed the client socket. Running eof in the server session continued to return False (0) until I actually performed a gets on the channel. - Ken Jones, President, ken@avia-training.com
Avia Training and Consulting, 866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
That doesn't seem to be the case.
Poll {} never closes an active socket
and always reaps the dead connections.

The interaction takes place in another procedure where a series of ipc like
things are done. Error checking is active at this level so that the poll process can go through and "cleanup" the global array. All sockets are closed
at the "ipc" level.
Poll{} does nothing but garbage collection. Thus, the first idea with a trace.

Here is a sample run:(client)
-----------------------
telnet localhost 1200
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Username: mars
Password: ******
Please enter the name and alias host of the user: mars freddy
Transaction Successful::code=0
Connection closed by foreign host.

Server startup:
------------------------------
Junk collection:
Gvar(Parent) = sock3
Gvar(connections) = 0
Gvar(junkc) = 60000
Gvar(logfile) = /tmp/AliasChangeLog.txt
Gvar(max) = 2
Gvar(pwfile) = /home/mars/ausers.txt
Gvar(raddr) =
Gvar(resets) = 0
Gvar(sockids) =
Gvar(target) = /home/ding.txt
Gvar(worksock) =
---------------------------

During connect:
Gvar(Parent) = sock3
Gvar(connections) = 1
Gvar(junkc) = 60000
Gvar(logfile) = /tmp/AliasChangeLog.txt
Gvar(max) = 2
Gvar(pwfile) = /home/mars/ausers.txt
Gvar(raddr) = 127.0.0.1:40851
Gvar(resets) = 0
Gvar(sockids) = sock4
Gvar(target) = /home/ding.txt
Gvar(worksock) = sock4

-------------------------
After cleanup:

Gvar(Parent) = sock3
Gvar(connections) = 0
Gvar(junkc) = 60000
Gvar(logfile) = /tmp/AliasChangeLog.txt
Gvar(max) = 2
Gvar(pwfile) = /home/mars/ausers.txt
Gvar(raddr) = 127.0.0.1:40851
Gvar(resets) = 1
Gvar(sockids) = sock4
Gvar(target) = /home/ding.txt
Gvar(worksock) =

You can see there are still some things to fix. The sockid list needs to be purged or sorted uniquely. The raddr needs to be reset.

Yes, what you say about the client side is true. However, the program is very simple.
The server dictates the course of action.
If a transaction is valid it is processed,acknowledged and then closed.
Poll{} comes along and cleans up.
If it is invalid it is closed with an error message.
Poll{} comes along and cleans up.
Otherwise I must rely on tcl and the os
to know about broken pipes and other
accidents of this sort unless you have a better suggestion?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top