This technique is from code posted by
DGP on
news:comp.lang.tcl [
1]. The basic idea is to avoid using global variables in a
fileevent handler by rewriting the handler callback with accumulated state, in a similar way to using an accumulator parameter for recursive calls. Here's a version of the code, which provides a little tclsh-like prompt server:
# Port to accept requests on
set PORT 9000
proc Accept {sock addr port} {
set interp [interp create -safe]
interp alias $interp exit {} close $sock
fileevent $sock readable [list Read $sock $interp {}]
}
proc Prompt {sock interp} {
fileevent $sock writable {}
puts -nonewline $sock {% }
flush $sock
fileevent $sock readable [list Read $sock $interp {}]
}
proc Read {sock interp script} {
fileevent $sock readable {}
if {[catch {gets $sock line} numChars]} {
close $sock
interp delete $interp
return
}
if {$numChars < 0} {
if {[fblocked $sock]} {
fileevent $sock readable [list Read $sock $interp $script]
return
}
if {[eof $sock]} {
close $sock
interp delete $interp
return
}
}
append script $line\n
if {[info complete $script]} {
catch [list $interp eval $script] result
# channel may have closed...
if {[llength [file channels $sock]]} {
fileevent $sock writable [list Write $sock $interp $result]
}
return
}
# **** THIS IS THE MAGIC: *****
# Here we reschedule the fileevent handler in a similar way to
# making a recursive call, with the updated $script argument
# which is used as an accumulator. This relies on the fact that
# scheduling a new fileevent handler overwrites any existing ones.
fileevent $sock readable [list Read $sock $interp $script]
}
proc Write {sock interp result} {
fileevent $sock writable {}
puts $sock $result
fileevent $sock writable [list Prompt $sock $interp]
}
# Start a demo server
socket -server Accept $PORT
vwait forever
Todd Coram discusses the same technique and others at [
2], in his tips for building high-performance network code in Tcl.