Pragana's Tcl Guide


Old notes


Using UDP to communicate

When we open a socket in tcl, we get a channel to a tcp channel which deliver a safe communication with our peer, which will give us only correct messages in the same order that we send them, but also which may not have a  limit timing for each message sent or received. That's the nature of tcp. If we want something faster, but not with the same guarantee of order in delivery (but which only deliver correct datagrams), we must choose upd.  Tcl don't have a builtin command for udp channels, so we must get an extension to do this. For the present experiments, we are going to use tcludp (see also in sourceforge's page). Some examples of its usage may be found in the web links above, and also in Tcler's wiki tcludp page, so we are not going to discuss that here.

Tcludp create true tcl channels, that may be handled mostly with the same commands we use for files and socket channels. In our experiment, only udp_open (to create a new udp channel) and udp_conf (to configure udp channel options) are out of this rule. Let's first show the full source of our experiment and then see what it does:

package require Tk
package require udp

array set opts {
    -sock ""
    -server ""
    -port 28374
    -host localhost
    -data "Teste com cliente udp (enviando mensagem)"
}

array set opts $argv

# Send data to a remote UDP socket
proc udp_puts {host port msg} {
    global opts
    udp_conf $opts(-sock) $host $port
    puts -nonewline $opts(-sock) $msg
}  

proc send_msg {} {
    global opts
    udp_puts $opts(-host) $opts(-port) $opts(-data)
    tputs $opts(-data) blue
}  

proc send_sync {} {
    global opts
    udp_puts $opts(-host) $opts(-port) "*SYNC*"
    after 1000 send_sync
}  
# receive data from peer
proc udpEventHandler {sock} {
    global conn
    if {[catch {read $sock} pkt]} return
    set peer [udp_conf $sock -peer]
    #tputs "$peer: [string length $pkt] {$pkt}"
    set conn "CONNECTED"
    if {$pkt != "*SYNC*"} {
        tputs $pkt red
    }
}
proc udp_listen {port} {
    global opts
    set srv [udp_open $port]
    fconfigure $srv -buffering none -translation binary
    fileevent $srv readable [list ::udpEventHandler $srv]
    #tputs "Listening on udp port: [udp_conf $srv -myport]"
    return $srv
}
########### start up
proc start_server {} {
    global opts conn
    if {$opts(-sock) != ""} {
        close $opts(-sock)
    }
    set opts(-sock) [udp_listen $opts(-port)]
    set conn "connecting"
    after cancel send_sync
    send_sync
    .b1 configure -text disconnect -command stop_server
}  

proc stop_server {} {
    after cancel send_sync
    .b1 configure -text connect -command start_server
    set ::conn "disconnected"
}   proc tputs {msg {tag ""}} {
    .t1 insert end $msg\n $tag
    .t1 see end
}

set conn "disconnected"

### GUI
label .lb1 -text host
entry .e1 -width 20 -textvariable opts(-host)
button .b1 -text connect -command start_server -width 10
label .lb2 -text port
entry .e2 -width 20 -textvariable opts(-port)
label .lbconn -textvariable conn
label .lb3 -text mensagem
entry .e3 -width 50 -textvariable opts(-data)
button .b3 -text envia -command send_msg -width 10
button .b4 -text exit -command exit
text .t1 -width 50 -height 10 -yscrollcommand {.sb1 set}
scrollbar .sb1 -orient vertical -command {.t1 yview} -width 10 -bd 1
.t1 tag config black -foreground #000000
.t1 tag config red -foreground #800000
.t1 tag config blue -foreground #000080
bind .e3 <Return> send_msg

grid .lb1 .e1 .b1 x -pady 5 -padx 3
grid .lb2 .e2 .lbconn x -pady 5 -padx 3
grid .lb3 .e3 .b3 x -pady 5 -padx 3
grid .t1 - - .sb1 -pady 5 -padx 1 -sticky nswe
grid x .b4 x -pady 5 -padx 3



Now, we explain the code a little. First, the array opts is set with some defaults, that may be overriden by command-line arguments.  The proc udp_puts is similar to tcl's puts with the additional arguments host and port before the string to be sent. It selects the host/port before doing the actual puts with udp_conf. To operate in higher level, we have send_msg, which is just a wrapper to udp_puts above, retrieving the configured host/port from our global array opts. To keep the connetions alive (not really needed, but...) we send each second a specialized SYNC message, with send_sync, which reschedules itself for more sync messages. All begins when the user fills in .e1 (variable opts(-host))
and .e2 (variable opts(-port)), and then press the button .b1, which is configured first to execute start_server. Then a listening upd socket is created (upd_listen to setup the event handler, and udpEventHandler to actually receive the datagrams), and .b1's command is changed to send_message.  Each message to be sent is stored in the entry .e3 variable (opts(-data)), and both the sent and received messages are added to the text widget .t1, with colored tags, with sent messages in blue and received messages in red.
I have made a simple starkit with both the libraries and the script, which you may retrieve here.

That's all fellows. Happy hacking!


Back Home