summaryrefslogtreecommitdiffstats
path: root/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl')
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl310
1 files changed, 310 insertions, 0 deletions
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl b/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl
new file mode 100644
index 0000000..01ec508
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl
@@ -0,0 +1,310 @@
+#
+# cmdsrv.tcl --
+#
+# Simple socket command server. Supports many simultaneous sessions.
+# Works in thread mode with each new connection receiving a new thread.
+#
+# Usage:
+# cmdsrv::create port ?-idletime value? ?-initcmd cmd?
+#
+# port Tcp port where the server listens
+# -idletime # of sec to idle before tearing down socket (def: 300 sec)
+# -initcmd script to initialize new worker thread (def: empty)
+#
+# Example:
+#
+# # tclsh8.4
+# % source cmdsrv.tcl
+# % cmdsrv::create 5000 -idletime 60
+# % vwait forever
+#
+# Starts the server on the port 5000, sets idle timer to 1 minute.
+# You can now use "telnet" utility to connect.
+#
+# Copyright (c) 2002 by Zoran Vasiljevic.
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -----------------------------------------------------------------------------
+
+package require Tcl 8.4
+package require Thread 2.5
+
+namespace eval cmdsrv {
+ variable data; # Stores global configuration options
+}
+
+#
+# cmdsrv::create --
+#
+# Start the server on the given Tcp port.
+#
+# Arguments:
+# port Port where the server is listening
+# args Variable number of arguments
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::create {port args} {
+
+ variable data
+
+ if {[llength $args] % 2} {
+ error "wrong \# arguments, should be: key1 val1 key2 val2..."
+ }
+
+ #
+ # Setup default pool data.
+ #
+
+ array set data {
+ -idletime 300000
+ -initcmd {source cmdsrv.tcl}
+ }
+
+ #
+ # Override with user-supplied data
+ #
+
+ foreach {arg val} $args {
+ switch -- $arg {
+ -idletime {set data($arg) [expr {$val*1000}]}
+ -initcmd {append data($arg) \n $val}
+ default {
+ error "unsupported pool option \"$arg\""
+ }
+ }
+ }
+
+ #
+ # Start the server on the given port. Note that we wrap
+ # the actual accept with a helper after/idle callback.
+ # This is a workaround for a well-known Tcl bug.
+ #
+
+ socket -server [namespace current]::_Accept -myaddr 127.0.0.1 $port
+}
+
+#
+# cmdsrv::_Accept --
+#
+# Helper procedure to solve Tcl shared channel bug when responding
+# to incoming socket connection and transfering the channel to other
+# thread(s).
+#
+# Arguments:
+# s incoming socket
+# ipaddr IP address of the remote peer
+# port Tcp port used for this connection
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::_Accept {s ipaddr port} {
+ after idle [list [namespace current]::Accept $s $ipaddr $port]
+}
+
+#
+# cmdsrv::Accept --
+#
+# Accepts the incoming socket connection, creates the worker thread.
+#
+# Arguments:
+# s incoming socket
+# ipaddr IP address of the remote peer
+# port Tcp port used for this connection
+#
+# Side Effects:
+# Creates new worker thread.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::Accept {s ipaddr port} {
+
+ variable data
+
+ #
+ # Configure socket for sane operation
+ #
+
+ fconfigure $s -blocking 0 -buffering none -translation {auto crlf}
+
+ #
+ # Emit the prompt
+ #
+
+ puts -nonewline $s "% "
+
+ #
+ # Create worker thread and transfer socket ownership
+ #
+
+ set tid [thread::create [append data(-initcmd) \n thread::wait]]
+ thread::transfer $tid $s ; # This flushes the socket as well
+
+ #
+ # Start event-loop processing in the remote thread
+ #
+
+ thread::send -async $tid [subst {
+ array set [namespace current]::data {[array get data]}
+ fileevent $s readable {[namespace current]::Read $s}
+ proc exit args {[namespace current]::SockDone $s}
+ [namespace current]::StartIdleTimer $s
+ }]
+}
+
+#
+# cmdsrv::Read --
+#
+# Event loop procedure to read data from socket and collect the
+# command to execute. If the command read from socket is complete
+# it executes the command are prints the result back.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::Read {s} {
+
+ variable data
+
+ StopIdleTimer $s
+
+ #
+ # Cover client closing connection
+ #
+
+ if {[eof $s] || [catch {read $s} line]} {
+ return [SockDone $s]
+ }
+ if {$line == "\n" || $line == ""} {
+ if {[catch {puts -nonewline $s "% "}]} {
+ return [SockDone $s]
+ }
+ return [StartIdleTimer $s]
+ }
+
+ #
+ # Construct command line to eval
+ #
+
+ append data(cmd) $line
+ if {[info complete $data(cmd)] == 0} {
+ if {[catch {puts -nonewline $s "> "}]} {
+ return [SockDone $s]
+ }
+ return [StartIdleTimer $s]
+ }
+
+ #
+ # Run the command
+ #
+
+ catch {uplevel \#0 $data(cmd)} ret
+ if {[catch {puts $s $ret}]} {
+ return [SockDone $s]
+ }
+ set data(cmd) ""
+ if {[catch {puts -nonewline $s "% "}]} {
+ return [SockDone $s]
+ }
+ StartIdleTimer $s
+}
+
+#
+# cmdsrv::SockDone --
+#
+# Tears down the thread and closes the socket if the remote peer has
+# closed his side of the comm channel.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# Worker thread gets released.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::SockDone {s} {
+
+ catch {close $s}
+ thread::release
+}
+
+#
+# cmdsrv::StopIdleTimer --
+#
+# Cancel the connection idle timer.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# After event gets cancelled.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::StopIdleTimer {s} {
+
+ variable data
+
+ if {[info exists data(idleevent)]} {
+ after cancel $data(idleevent)
+ unset data(idleevent)
+ }
+}
+
+#
+# cmdsrv::StartIdleTimer --
+#
+# Initiates the connection idle timer.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# After event gets posted.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::StartIdleTimer {s} {
+
+ variable data
+
+ set data(idleevent) \
+ [after $data(-idletime) [list [namespace current]::SockDone $s]]
+}
+
+# EOF $RCSfile: cmdsrv.tcl,v $
+
+# Emacs Setup Variables
+# Local Variables:
+# mode: Tcl
+# indent-tabs-mode: nil
+# tcl-basic-offset: 4
+# End:
+