This technique is from code posted by DGP on news:comp.lang.tcl [L1 ]. 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 [L2 ], in his tips for building high-performance network code in Tcl.