TclUDP

Difference between version 76 and 77 - Previous - Next
'''[https://sourceforge.net/projects/tcludp/%|%tcludp]''', by Xiataow, is a Tcl
[extension] that provides [UDP] [socket%|%sockets] on [Windows] and [Unix].



** Attributes **

   website:   https://core.tcl.tk/tcludp

   old website:   https://sourceforge.net/projects/tcludp/


** See Also **

   [https://github.com/flightaware/tcl-udp-examples%|%Tcl UDP examples], by [COMPANY: FlightAware%|%FlightAware]:   



** Description **

Tcludp sockets support `[fileevent]` and also support multicasting and binary data. 
The package has recently been restructured as a [TEA3] compliant package with build testing done 
for [Linux], Windows, Solaris and [OpenBSD].

Tcludp has only two commands to create and configure udp sockets.
There is no 'recv' or 'send', rather, you read from a TclUDP socket with
'read' and write to it using 'puts'.

Also http://students.cs.tamu.edu/mmiller/tcl/channel.html which is the original base for tcludp.

The latest version is 1.0.11 as of 2014-08-24. Unfortunately, there is only a 32-bit version for Windows available. 

[ysk]: 1.0.9 is available in teapot. What's the difference?



** Usage **

First, make sure you have loaded the UDP package by using the command

======
load udp1011.dll
======

The name of the dll file may be different dependent on the package version. 



Second, bring the TclUDP package into your Tcl interpreter with 

======
package require udp
======

Then, you first create a socket on the local system:

======
set sock [udp_open]
======

You can also specify a port number if that is required. You could set the characteristics of the
socket with

======
fconfigure $sock -buffering none -translation binary
======

You specify the other end-point for the UDP datagram with 'fconfigure' (in old versions 'udp_conf'):

======
fconfigure $sock -remote [list $serverName $servicePort]
##deprecated## udp_conf $sock $serverName $servicePort
======

You can send datagrams (of up to 4096 bytes) with 'puts' and read incoming datagrams with 'gets'. Do not forget to
configure the translation mode of the socket for your needs.

======
puts -nonewline $sock "Pack my box with four dozen liquor jugs."
======

You should use [fileevent] to specify a callback that should be invoked when a
UDP datagram arrives:

======
proc OnUdpDataReceived {channel} {
    set packet [read $channel]
    set peer [fconfigure $channel -peer]
    # Do something with the data and/or the peer address information
    return
}
fileevent $sock readable [list OnUdpDataReceived $sock]
======

See the ''demos/'' folder in the source distribution for a number of examples including the use of multicast and broadcast UDP sample programs.



** Examples **

[PT] 2003-06-13: This is a simple demo server that I use in testing:

======
package require udp

proc udpEventHandler sock {
    set pkt [read $sock]
    set peer [udp_conf $sock -peer]
    puts "$peer: [string length $pkt] {$pkt}"
    return
}

proc udp_listen {port} {
    set srv [udp_open $port]
    fconfigure $srv -buffering none -translation binary
    fileevent $srv readable [list ::udpEventHandler $srv]
    puts "Listening on udp port: [udp_conf $srv -myport]"
    return $srv
}

if {$tcl_interactive} {
    puts "call udp_listen portnum to begin"
} else {
    eval [list udp_listen] $argv
    vwait forever
}
======

And a corresponding server socket can be obtained using:

======
proc udp_create {host port} {
    set s [udp_open]
    udp_conf $s $host $port
    fconfigure $s -buffering none -translation binary
    return $s
}
======

then use 

======
set sock [udp_create $server $portnum];# eg localhost 9876
puts -nonewline $sock "MyData - including binary \0\1\2\3"
======

----

[Mike Tuxford] 2003-01-30: It was mentioned in c.l.t that there aren't many examples of tcludp usage around so I'll add this simple one that listens on port 1434 which is the MSSQL port and has been in the news a lot lately due to major exploits.

======
package require udp

proc udpEventHandler {} {
    global fd
    puts "event triggered..."
    puts "Data: [gets $fd(udp)]"
    puts "Peer: [udp_conf $fd(udp) -peer]"
    return
}

set fd(udp) [udp_open 1434]
fileevent $fd(udp) readable udpEventHandler
puts "Listening on udp port: [udp_conf $fd(udp) -myport]"

vwait __forever__
======

----

[shedi] 2003-12-10: Wake On Lan Example (the magic packet).
Usage: WakeOnLan 10.255.255.255 000783104R83

======
package require udp
proc WakeOnLan {broadcastAddr macAddr} {
    set net [binary format H* [join [split $macAddr -:] {}]]
    set pkt [binary format c* {0xff 0xff 0xff 0xff 0xff 0xff}]

    for {set i 0} {$i < 16} {incr i} {
       append pkt $net
    }

    # Open UDP and Send the Magic Paket.
    set udpSock [udp_open];
    udp_conf $udpSock $broadcastAddr 4580;
    fconfigure $udpSock -translation binary;
    puts $udpSock $pkt
    flush $udpSock;
    close $udpSock
}
======

[sbron]: In my experiments this works on windows, but not on linux. On linux the code has to specifically allow broadcasting using something like the following at an appropriate spot in the c code:

======none
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int));
======

[PT] 2004-11-19: The in-development version 2.x tcludp supports setting the broadcast option on the socket.
If you want to try it out, checkout the ''dev-2'' branch from the tcludp sourceforge CVS.

2006-06-06: There is no ''dev-2'' branch at tcludp.sourceforge.net (anymore?), can someone provide a link where to get tcludp 2.x ?

Here is a sample of a simplistic chat using broadcast UDP packets using the tcludp 2.0.0 api.

======
# udpmsg.tcl - Copyright (C) 2004 Pat Thoyts <[email protected]>
#
# Demo chat application.
#
# This uses broadcast UDP packets to send chat messages to the network.
#
# $Id: 16733,v 1.2 2006-09-14 06:01:04 jcw Exp $

package require Tk  8.4
package require udp 2

variable Port     7531
variable Network  172.16.255.255
variable server
variable client

# handle incoming packets on the udp server socket.
proc udpEventHandler {sock} {
    set pkt [read $sock]
    set peer [fconfigure $sock -peer]
    AddMessage "$peer $pkt"
    return
}

# create a udp server socket
proc udp_listen {port} {
    set srv [udp]
    fconfigure $srv \
        -sockname [list {} $port] \
        -blocking 0 \
        -buffering none \
        -translation binary \
        -broadcast 1 \
        -reuseaddr 1
    fileevent $srv readable [list ::udpEventHandler $srv]
    AddMessage "Listening on udp port: [fconfigure $srv -sockname]"
    return $srv
}

# create a udp client socket
proc udp_create {host port} {
    set s [udp]
    fconfigure $s -remote [list $host $port]
    fconfigure $s -buffering none -translation binary
    return $s
}

proc CreateGui {} {
    text .t -yscrollcommand {.s set}
    scrollbar .s -command {.t yview}
    entry .e -textvariable ::msg
    button .ok -text Send -underline 0 -command {SendMessage $::msg}
    button .ex -text Exit -underline 1 -command {destroy .}
    grid .t - .s -sticky news
    grid .e .ok .ex -sticky ew
    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 0 -weight 1
    bind .e <Return> {.ok invoke}
}

proc SendMessage {msg} {
    variable client
    puts $client $msg
}

proc AddMessage {msg} {
    if {[string length $msg] > 0} {
        .t insert end $msg
    }
}

if {!$tcl_interactive} {
    CreateGui
    set server [udp_listen $Port]
    set client [udp_create $Network $Port]
    tkwait window .
    close $server
    close $client
    exit 0
}
======


** Future directions **

The following does only indirectly pertain to TclUDP.
These are some speculations on what a UDP package for tcl '''could''' look like.

[PT]: There is some work on a version 2 udp package which will drop the udp_open and udp_conf commands in favour of a single [[udp]] command for creating the channel instance and then use the [[fconfigure]] command for everything else. As of udp 1.0.6 the fconfigure functionality is already in place.

`[udp]` accepts `-myaddr` to specify the local interface,  also acceptss `-reuseaddr` to set the `SO_REUSEADDR` socket option so that multiple sockets can bind an address.

I also want to sort out the IPv6 side of things so that the same library can handle both ipv4 and ipv6 connections.

----

[PT] 2003-03-11:

The current interface to this package isn't quite what we'd like to see for Tcl. I'm currently implementing the bits required to support the stock [fconfigure] command. For the future I think that I'd like to see a [udp] command very much like [socket]. This could then be used as

======
set u [udp $remote $port]         ; # create a udp socket and set the target
set u [udp]                       ; # create a udp client socket that requires configuring later
set u [udp -server handler $port] ; # create a udp server socket listening on $port
======

UDP sockets are not normally connected to anyone. So fconfigure can be used to redirect outbound traffic. The server handler can use `[fconfigure] $u -peer` to obtain information about who sent the current packet. So the same socket could be used to fire off packets to a list of hosts.

======
proc handler {sock} { do stuff }
set u [udp]
fconfigure $u -buffering none -translation binary
fileevent $u readable [list ::handler $u]
foreach {host port} $targetlist {
    fconfigure $u -remote [list $host $port]
    puts -nonewline $u $data
}
======

Note that to read data from a udp socket, you will always have to set up a fileevent handler.

Seems reasonable - until [aku] invents his Tcl Event System :)

----

[Jacob Levy] 2003-03-11: This all seems very reasonable. 
Then a small step forward would be to convert it to using the [socket] command like this:

======
set u [socket -udp $remote $port]         ; # create a udp socket and set the target
set u [socket -udp]                       ; # this one needs to be fconfigured later
set u [socket -udp -server handler $port] ; # create a udp server listening on $port
======

Note that this is backwards compatible with the regular [socket] command.

Question: in the readable handler, is there any way to find out the name/IP address of who made it readable? Is the
read blocking, i.e. will it block if you try to read more than what's there?

[PT]: As for the [socket] command, fconfigure -peername will return the senders details for the current packet. As for blocking - the udp socket cannot be blocking. What happens is that read reads data from a single packet until it's complete. If you try to read too much, an end of data gets returned once the packet is exhausted. 
So you can safely use `set d [[read $sock]]` and be sure to only get the data from one packet. 
With udp it usually makes sense to have buffering off - so this generally means you read the entire packet.

----

Pat has written, "Multicast is a little different to standard unicast 
or broadcast UDP. 
Your operating system ignores multicast packets unless an
application has explicitly joined a multicast group.
In the case of TclUDP we do this by using

======
fconfigure $socket -mcastadd $mcastaddress
======

This actually results in a system call that causes the system to start
listening to packets addresses to this group. In C this would be:

======c
setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, ....);
======

Once the system is no longer ignoring such packets, normal UDP                  
addressing takes effect. This means that you need to send to this               
multicast address and in order to receive packets you need to have a            
listening socket open and bound to the correct address and port.                
                                                                                
It is not necessary to join the multicast group to send but it is               
necessary if you want to receive.                                               
                                                                                
There is a minimal demo called '''multicast.tcl''' in the tcludp '''demos/'''                
folder (as distributed in the source package). If you run that as               
======none
tclsh multicast.tcl
======

it listens to group 224.5.1.21 port 7771. You can send to it using              
'''netcat''' (or the '''chat.tcl''' demo) eg:                                               
======
echo "testing " | nc -u 224.5.1.21 7771
======

or using bash

======
echo "testing " >/dev/udp/224.5.1.21/7771                              
======
or                                                                              
======
wish demos/chat.tcl
======

----

TclUDP is [stubs]-enabled to the extent of not being bound to a specific
version of Tcl.  Unlike, say, [memchan], it does ''not'' '''provide''' stubs
(nor should it ...).

[RLE] 2014-01-09: Quoting above: 

    :   "You can send datagrams (of up to 4096 bytes) with 'puts'"

Is there a reason for the 4,096 byte limitation.  The maximum ipv4 UDP packet is 65,507 bytes [http://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure] so why limit to 4,096 in the extension?
[APE] 2020-01-14: it seems that the buffer size can be modified to 65,507 bytes, at least it compiles and run when the limit is modified in the udp_tcl.c file.
----

HFE 2014-03-02: a new version 1.0.10 of TclUdp is available with the following changes:

   * Added support for IPv6
   * The reuse keyword option now also works on MacOSx
   * Added option to add a multicast group on a specific network interface

Version can be found at https://sourceforge.net/projects/tcludp/

Example how to create a reusable IPv6 socket and add an (IPv6) multicast group for a specific network interface:

======
set groupv6 ff02::666
set portv6 9010

set sock [udp_open $portv6 reuse ipv6]

# On Linux, the network interface name (e.g. eth0) must be used
fconfigure $sock -mcastadd "$groupv6 eth0" -remote [list $groupv6 $portv6]

# On Windows, the network interface index must be used. 
# Index can be determined using the 'ipconfig' doc command. First interface has index 1.
fconfigure $sock -mcastadd "$groupv6 1" -remote [list $groupv6 $portv6]
======

Please note that there is one open issue on the 1.0.10 version:
'Cannot send IPv6 UDP multicast on MacOSX platform' :http://sourceforge.net/p/tcludp/bugs/40/
I do not know it this is a platform issue or tcludp code issue.

[FrBa]: The sourceforge page shows no activity beyond v1.0.8. Does your 1.0.10 version also fix bug #39 [http://sourceforge.net/p/tcludp/bugs/39/] that I submitted over three years ago? Many of my TCL projects rely on udp.

[HFE] 2014-03-03: The source forge download page is unfortunately outdated. But the code it holds (version 1.0.9 and now 1.0.10) is actually used by ActiveState for their releases. As far as I can tell, bug # 39 has not been solved (yet).

[HFE] 2014-05-11: The source forge download page is now updated and contains the latest 1.0.10 version.


[Napier / Dash Automation] 03/17/2016 -

I use multi-casting similar to the nameserv::cluster package that is available.  I ran into a situation where I wanted to multi-cast but only to the localhost.  This proved easy in the end by simply setting the ttl to 0.  The benefit here is the quick and simple ability to have a multi-cast group which you can use to coordinate tasks on the LAN Level and on the localhost when multiple services/scripts might be apart of the group on each given machine.  In the example below, when sending global we do so temporarily and immediately set it back to local to prevent leaking data over the wire we may only want locally.

In our context "Global" refers to any member on the LAN and "Local" refers to any member within the localhost.

You would, of course, most likely want to make sure you configure a method of determining if a packet is arriving from the LAN or Locally so you may act accordingly. 

======
package require udp

proc Receive {sock} {
  set packet [read $sock]
  set peer   [udp_conf $sock -peer]
  puts "Receive From $peer"
  puts $packet
}

proc SendGlobal {socketID what} {
     chan configure $socketID -ttl 1
     puts $socketID $what
     chan configure $socketID -ttl 0
}

proc SendLocal {socketID what} {
     chan configure $socketID -ttl 0
     puts $socketID $what
}

proc join_cluster {} {
  set socketID [udp_open 46000 reuse]

  chan configure $socketID \
    -buffering none \
    -blocking 0 \
    -translation binary \
    -mcastadd [list 239.239.239.239] \
    -remote [list 239.239.239.239 46000] \
    -ttl 0
  chan event $socketID readable [list Receive $socketID]
  puts "Sock: $socketID - My Port is [chan configure $socketID -myport]"
  return $socketID
}

set socketID [join_cluster]

SendGlobal $socketID "Hey LAN!"
SendLocal $socketID "Hey localhost!"
======
<<categories>> Package | Internet | Networking | Channel