client/server with fileevent

This is an example how to use fileevent.

 # usage: tclsh clisrv.tcl [secs]
 # Andy Tannenbaum, June 2001

 # all lines prepended with one space, for the wiki.
 
 # a parent/client and child/server both in this file,
 # communicating with fileevent.
 # should run on tcl 7.5 and later.
 
 # the two sides share names of things, but they will
 # never be invoked in the same address space.
 
 # the child ticks every second,
 # and accepts commands from the parent.
 
 # if invoked with no command line args, run parent.
 # with args, run child.
 
 if [string match "" $argv] {
 
 # parent/client process
 # calls clisrv.tcl with args to invoke child/server
 # opening a read/write pipe
 # using fileevent.
 #
 
 
 # done gets set when the child exits.
 # cfd is the child file descriptor
 
 global done cfd
 
 # gotline is the fileevent callback,
 # called when this proc receives input.
 
 proc gotline f {
        global done
 
        if {[gets $f line]<0} {
                catch {close $f} ret
                if ![string match "" $ret] {
                        puts "parent: gotline: child exited with \
                                error, ret = $ret, errorCode = $::errorCode"
                } else {
                        puts "parent: gotline: child exited ok"
                }
                set done 1
                return
        }
 
        puts "parent: got ==> $line"
 }
 
 proc fputs {f str} {
        puts $f $str
        flush $f
 }
 
 # prints:
 
 # parent: got ==> tick: 10
 # parent: got ==> tick: 9
 # parent: got ==> child: got ==> hello 1
 # parent: got ==> tick: 8
 # parent: got ==> child: got ==> hello 2
 # parent: got ==> tick: 7
 # parent: got ==> child: got ==> hello 3
 # parent: got ==> tick: 6
 # parent: got ==> tick: 5
 # parent: got ==> tick: 4
 # parent: got ==> child: got quit - q
 # parent: gotline: child exited ok
 
 proc pa {} {
        global cfd
 
        # run this script with arg, tick for 10 seconds
        set cfd [open "|tclsh [info script] 10 2>@ stderr" r+]
        fileevent $cfd readable "gotline $cfd"
 }
 
 # send commands to child process.
 # q means quit, others get echoed back to parent.
 
 after 1500 {
        global cfd
        fputs $cfd "hello 1"
 }
 
 after 2500 {
        global cfd
        fputs $cfd "hello 2"
 }
 
 after 3500 {
        global cfd
        fputs $cfd "hello 3"
 }
 
 after 6500 {
        global cfd
        fputs $cfd q
 }
 
 pa
 
 # vwait gives us an event loop
 # it returns when the child exits
 
 vwait done
 
 
 
 # end of parent/client
 # <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
 
 } else {
 
 # <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
 # beginning of child/server
 
 
 
 # if invoked with args.
 # child process, ticks every second.
 # waits for stdin with fileevent.
 # echoes stdin back to stdout.
 
 
 # done gets set when the parent exits
 
 global done
 
 # gotline gets called on each line of input
 
 proc gotline f {
        global done
        if {[gets $f line]<0} {
                # it died !
                catch {close $f}
                set done 1
                return
        }
 
        # if parent sends quit, then quit.
        if [string match q* $line] {
                puts "child: got quit - $line"
                set done 1
                return
        }
        # else echo the input
        puts "child: got ==> $line"
 }
 
 fileevent stdin readable "gotline stdin"
 
 # print a tick every second, for n seconds, counting down.
 
 proc tick {n} {
        global done
        if {$n <= 0} {
                set done 1
                return
        }
        puts "tick: $n"
        incr n -1
        after 1000 tick $n
 }
 
 set arg1 [lindex $argv 0]
 
 tick $arg1
 vwait done
 }

Andrew Tannenbaum

Also see Simple Server/Client Sockets and telnet.