multiplexer.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package provide multiplexer 0.2
00013 package require logger
00014
00015 namespace ::multiplexer {
00016 variable Unique 0
00017 }
00018
00019 ret ::multiplexer::create () {
00020 variable Unique
00021 set ns ::multiplexer::mp$Unique
00022
00023 namespace eval $ns {
00024 # Use the namespace as the logger name.
00025 set log [logger::init [string trimleft [namespace current] ::]]
00026 # list of connected clients
00027 array set clients {}
00028
00029 # filters to run at access (socket accept) time
00030 set accessfilters {}
00031
00032 # filters to run on data
00033 set filters {}
00034
00035 # hook to run at exit time
00036 set exitfilters {}
00037
00038 # config options
00039 array set config {}
00040 set config(sendtoorigin) 0
00041 set config(debuglevel) warn
00042 ${log}::disable $config(debuglevel)
00043 ${log}::enable $config(debuglevel)
00044
00045 # AddAccessFilter --
00046 #
00047 # Command to add an access filter that will be called like so:
00048 #
00049 # AccessFilter chan clientaddress clientport
00050 #
00051 # Arguments:
00052 #
00053 # function: proc to filter access to the multiplexer. Takes chan,
00054 # clientaddress and clientport arguments. Returns 0 on success, -1 on
00055 # failure.
00056
00057 proc AddAccessFilter { function } {
00058 variable accessfilters
00059 lappend accessfilters $function
00060 }
00061
00062 # AddFilter --
00063
00064 # Command to add a filter for data that passes through the
00065 # multiplexer. The filter proc is called like this:
00066
00067 # Filter data chan clientaddress clientport
00068
00069 # Arguments:
00070
00071 # function: proc to filter data that arrives to the
00072 # multiplexer.
00073 # Takes data, chan, clientaddress, and clientport arguments. Returns
00074 # filtered version of data.
00075
00076 proc AddFilter { function } {
00077 variable filters
00078 lappend filters $function
00079 }
00080
00081 # AddExitFilter --
00082
00083 # Adds filter to be run when client socket generates an EOF condition.
00084 # ExitFilter functions look like the following:
00085
00086 # ExitFilter chan clientaddress clientport
00087
00088 # Arguments:
00089
00090 # function: hook to be run when clients exit by generating an EOF.
00091 # Takes chan, clientaddress and clientport arguments, and returns
00092 # nothing.
00093
00094 proc AddExitFilter { function } {
00095 variable exitfilters
00096 lappend exitfilters $function
00097 }
00098
00099 # DelClient --
00100
00101 # Deletes a client from the client list, and runs exit filters.
00102
00103 # Arguments:
00104
00105 # chan: channel that is closed.
00106
00107 # client: address of client
00108
00109 # clientport: port number of client.
00110
00111 proc DelClient { chan client clientport } {
00112 variable clients
00113 variable exitfilters
00114 variable config
00115 variable log
00116 foreach ef $exitfilters {
00117 catch {
00118 $ef $chan $client $clientport
00119 } err
00120 ${log}::debug "Error in DelClient: $err"
00121 }
00122 unset clients($chan)
00123 close $chan
00124 }
00125
00126
00127 # MultiPlex --
00128
00129 # Multiplex data
00130
00131 # Arguments:
00132
00133 # data - data to multiplex
00134
00135 proc MultiPlex { data {chan ""} } {
00136 variable clients
00137 variable config
00138 variable log
00139
00140 foreach c [array names clients] {
00141 if { $config(sendtoorigin) } {
00142 puts -nonewline $c "$data"
00143 } else {
00144 if { $chan != $c } {
00145 ${log}::debug "Sending '$data' to $c"
00146 puts -nonewline $c "$data"
00147 }
00148 }
00149 }
00150 }
00151
00152
00153 # GetData --
00154
00155 # Get data from clients, filter it, redistribute it.
00156
00157 # Arguments:
00158
00159 # chan: open channel
00160
00161 # client: client address
00162
00163 # clientport: port number of client
00164
00165 proc GetData { chan client clientport } {
00166 variable filters
00167 variable clients
00168 variable config
00169 variable log
00170 if { ! [eof $chan] } {
00171 set data [read $chan]
00172 # gets $chan data
00173 ${log}::debug "Tcl chan $chan from host $client and port $clientport sends: $data"
00174 # do data filters
00175 foreach f $filters {
00176 catch {
00177 set data [$f $data $chan $client $clientport]
00178 } err
00179 ${log}::debug "GetData filter: $err"
00180 }
00181 set chans [array names clients]
00182 MultiPlex $data $chan
00183 } else {
00184 ${log}::debug "Deleting client $chan from host $client and port $clientport."
00185 DelClient $chan $client $clientport
00186 }
00187 }
00188
00189 # NewClient --
00190
00191 # Sets up newly created connection after running access filters
00192
00193 # Arguments:
00194
00195 # chan: open channel
00196
00197 # client: client address
00198
00199 # clientport: port number of client
00200
00201 proc NewClient { chan client clientport } {
00202 variable clients
00203 variable config
00204 variable accessfilters
00205 variable log
00206 # run through access filters
00207 foreach af $accessfilters {
00208 if { [$af $chan $client $clientport] == -1 } {
00209 ${log}::debug "Access denied to $chan $client $clientport"
00210 close $chan
00211 return
00212 }
00213 }
00214 set clients($chan) $client
00215
00216 # We want to read data and immediately send it out again.
00217 fconfigure $chan -blocking 0
00218 fconfigure $chan -buffering none
00219 fconfigure $chan -translation binary
00220 fileevent $chan readable [list [namespace current]::GetData $chan $client $clientport]
00221 ${log}::debug "Tcl channel $chan is host $client and port $clientport."
00222 }
00223
00224 # Config --
00225 #
00226 # Configure global options, which currently include the
00227 # following:
00228 #
00229 # sendtoorigin: if 1, resend the data to all clients, including the
00230 # sender. Defaults to 0
00231 #
00232 # debuglevel: a debug level understood by logger.
00233 #
00234 # Arguments:
00235 #
00236 # key: name of option to configure
00237 #
00238 # value: value for option.
00239
00240 proc Config { key value } {
00241 variable config
00242 variable log
00243 if { $key == "debuglevel" } {
00244 ${log}::disable $config(debuglevel)
00245 ${log}::enable $value
00246 }
00247 set config($key) $value
00248 }
00249
00250 # Init --
00251 #
00252 # Start the server
00253 #
00254 # Arguments:
00255 #
00256 # port: port to listen on.
00257
00258 proc Init { port } {
00259 variable serversock
00260 set serversock [socket -server [namespace current]::NewClient $port]
00261 }
00262
00263 # destroy --
00264 #
00265 # Destroy multiplexer instance. It is important to do
00266 # this, to free the resources used.
00267 #
00268 # Side Effects:
00269 # Deletes namespace associated with multiplexer
00270 # instance.
00271
00272
00273 proc destroy { } {
00274 variable serversock
00275 foreach c [array names clients] {
00276 catch { close $c }
00277 }
00278 catch {
00279 close $serversock
00280 }
00281 namespace delete [namespace current]
00282 }
00283
00284 }
00285 incr Unique
00286 return $ns
00287 }
00288
00289 namespace multiplexer {
00290 namespace export create destroy
00291 }
00292