OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / tcl8.6.12 / pkgs / thread2.8.7 / tcl / cmdsrv / cmdsrv.tcl
1 #
2 # cmdsrv.tcl --
3 #
4 # Simple socket command server. Supports many simultaneous sessions.
5 # Works in thread mode with each new connection receiving a new thread.
6 #
7 # Usage:
8 #    cmdsrv::create port ?-idletime value? ?-initcmd cmd?
9 #
10 #    port         Tcp port where the server listens
11 #    -idletime    # of sec to idle before tearing down socket (def: 300 sec)
12 #    -initcmd     script to initialize new worker thread (def: empty)
13 #
14 # Example:
15 #
16 #    # tclsh8.6
17 #    % source cmdsrv.tcl
18 #    % cmdsrv::create 5000 -idletime 60
19 #    % vwait forever
20 #
21 #    Starts the server on the port 5000, sets idle timer to 1 minute.
22 #    You can now use "telnet" utility to connect.
23 #
24 # Copyright (c) 2002 by Zoran Vasiljevic.
25 #
26 # See the file "license.terms" for information on usage and
27 # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
28 # -----------------------------------------------------------------------------
29
30 package require Tcl    8.4
31 package require Thread 2.5
32
33 namespace eval cmdsrv {
34     variable data; # Stores global configuration options
35 }
36
37 #
38 # cmdsrv::create --
39 #
40 #       Start the server on the given Tcp port.
41 #
42 # Arguments:
43 #   port   Port where the server is listening
44 #   args   Variable number of arguments
45 #
46 # Side Effects:
47 #       None.
48 #
49 # Results:
50 #       None.
51 #
52
53 proc cmdsrv::create {port args} {
54
55     variable data
56
57     if {[llength $args] % 2} {
58         error "wrong \# arguments, should be: key1 val1 key2 val2..."
59     }
60
61     #
62     # Setup default pool data.
63     #
64
65     array set data {
66         -idletime 300000
67         -initcmd  {source cmdsrv.tcl}
68     }
69
70     #
71     # Override with user-supplied data
72     #
73
74     foreach {arg val} $args {
75         switch -- $arg {
76             -idletime {set data($arg) [expr {$val*1000}]}
77             -initcmd  {append data($arg) \n $val}
78             default {
79                 error "unsupported pool option \"$arg\""
80             }
81         }
82     }
83
84     #
85     # Start the server on the given port. Note that we wrap
86     # the actual accept with a helper after/idle callback.
87     # This is a workaround for a well-known Tcl bug.
88     #
89
90     socket -server [namespace current]::_Accept -myaddr 127.0.0.1 $port
91 }
92 \f
93 #
94 # cmdsrv::_Accept --
95 #
96 #       Helper procedure to solve Tcl shared channel bug when responding
97 #   to incoming socket connection and transfering the channel to other
98 #   thread(s).
99 #
100 # Arguments:
101 #   s      incoming socket
102 #   ipaddr IP address of the remote peer
103 #   port   Tcp port used for this connection
104 #
105 # Side Effects:
106 #       None.
107 #
108 # Results:
109 #       None.
110 #
111
112 proc cmdsrv::_Accept {s ipaddr port} {
113     after idle [list [namespace current]::Accept $s $ipaddr $port]
114 }
115 \f
116 #
117 # cmdsrv::Accept --
118 #
119 #       Accepts the incoming socket connection, creates the worker thread.
120 #
121 # Arguments:
122 #   s      incoming socket
123 #   ipaddr IP address of the remote peer
124 #   port   Tcp port used for this connection
125 #
126 # Side Effects:
127 #       Creates new worker thread.
128 #
129 # Results:
130 #       None.
131 #
132
133 proc cmdsrv::Accept {s ipaddr port} {
134
135     variable data
136
137     #
138     # Configure socket for sane operation
139     #
140
141     fconfigure $s -blocking 0 -buffering none -translation {auto crlf}
142
143     #
144     # Emit the prompt
145     #
146
147     puts -nonewline $s "% "
148
149     #
150     # Create worker thread and transfer socket ownership
151     #
152
153     set tid [thread::create [append data(-initcmd) \n thread::wait]]
154     thread::transfer $tid $s ; # This flushes the socket as well
155
156     #
157     # Start event-loop processing in the remote thread
158     #
159
160     thread::send -async $tid [subst {
161         array set [namespace current]::data {[array get data]}
162         fileevent $s readable {[namespace current]::Read $s}
163         proc exit args {[namespace current]::SockDone $s}
164         [namespace current]::StartIdleTimer $s
165     }]
166 }
167 \f
168 #
169 # cmdsrv::Read --
170 #
171 #       Event loop procedure to read data from socket and collect the
172 #   command to execute. If the command read from socket is complete
173 #   it executes the command are prints the result back.
174 #
175 # Arguments:
176 #   s      incoming socket
177 #
178 # Side Effects:
179 #       None.
180 #
181 # Results:
182 #       None.
183 #
184
185 proc cmdsrv::Read {s} {
186
187     variable data
188
189     StopIdleTimer $s
190
191     #
192     # Cover client closing connection
193     #
194
195     if {[eof $s] || [catch {read $s} line]} {
196         return [SockDone $s]
197     }
198     if {$line == "\n" || $line == ""} {
199         if {[catch {puts -nonewline $s "% "}]} {
200             return [SockDone $s]
201         }
202         return [StartIdleTimer $s]
203     }
204
205     #
206     # Construct command line to eval
207     #
208
209     append data(cmd) $line
210     if {[info complete $data(cmd)] == 0} {
211         if {[catch {puts -nonewline $s "> "}]} {
212             return [SockDone $s]
213         }
214         return [StartIdleTimer $s]
215     }
216
217     #
218     # Run the command
219     #
220
221     catch {uplevel \#0 $data(cmd)} ret
222     if {[catch {puts $s $ret}]} {
223         return [SockDone $s]
224     }
225     set data(cmd) ""
226     if {[catch {puts -nonewline $s "% "}]} {
227         return [SockDone $s]
228     }
229     StartIdleTimer $s
230 }
231 \f
232 #
233 # cmdsrv::SockDone --
234 #
235 #       Tears down the thread and closes the socket if the remote peer has
236 #   closed his side of the comm channel.
237 #
238 # Arguments:
239 #   s      incoming socket
240 #
241 # Side Effects:
242 #       Worker thread gets released.
243 #
244 # Results:
245 #       None.
246 #
247
248 proc cmdsrv::SockDone {s} {
249
250     catch {close $s}
251     thread::release
252 }
253 \f
254 #
255 # cmdsrv::StopIdleTimer --
256 #
257 #       Cancel the connection idle timer.
258 #
259 # Arguments:
260 #   s      incoming socket
261 #
262 # Side Effects:
263 #       After event gets cancelled.
264 #
265 # Results:
266 #       None.
267 #
268
269 proc cmdsrv::StopIdleTimer {s} {
270
271     variable data
272
273     if {[info exists data(idleevent)]} {
274         after cancel $data(idleevent)
275         unset data(idleevent)
276     }
277 }
278 \f
279 #
280 # cmdsrv::StartIdleTimer --
281 #
282 #       Initiates the connection idle timer.
283 #
284 # Arguments:
285 #   s      incoming socket
286 #
287 # Side Effects:
288 #       After event gets posted.
289 #
290 # Results:
291 #       None.
292 #
293
294 proc cmdsrv::StartIdleTimer {s} {
295
296     variable data
297
298     set data(idleevent) \
299         [after $data(-idletime) [list [namespace current]::SockDone $s]]
300 }
301
302 # EOF $RCSfile: cmdsrv.tcl,v $
303
304 # Emacs Setup Variables
305 # Local Variables:
306 # mode: Tcl
307 # indent-tabs-mode: nil
308 # tcl-basic-offset: 4
309 # End:
310