#!/bin/sh # This is a shell archive (produced by GNU sharutils 4.2). # To extract the files from this archive, save it to some FILE, remove # everything before the `!/bin/sh' line above, then type `sh FILE'. # # Made on 2000-03-19 15:00 EST by . # Source directory was `/home/jcej/projects/ACE_wrappers/docs/tutorials/008'. # # Existing files will *not* be overwritten unless `-c' is specified. # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 515 -rw-rw-r-- hdr # 59 -rw-rw-r-- bodies # 2767 -rw-rw-r-- page01.pre # 416 -rw-rw-r-- page02.pre # 345 -rw-rw-r-- page03.pre # 481 -rw-rw-r-- page04.pre # 578 -rw-rw-r-- page05.pre # 952 -rw-rw-r-- page02.pst # 367 -rw-rw-r-- page03.pst # 1173 -rw-rw-r-- page04.pst # save_IFS="${IFS}" IFS="${IFS}:" gettext_dir=FAILED locale_dir=FAILED first_param="$1" for dir in $PATH do if test "$gettext_dir" = FAILED && test -f $dir/gettext \ && ($dir/gettext --version >/dev/null 2>&1) then set `$dir/gettext --version 2>&1` if test "$3" = GNU then gettext_dir=$dir fi fi if test "$locale_dir" = FAILED && test -f $dir/shar \ && ($dir/shar --print-text-domain-dir >/dev/null 2>&1) then locale_dir=`$dir/shar --print-text-domain-dir` fi done IFS="$save_IFS" if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED then echo=echo else TEXTDOMAINDIR=$locale_dir export TEXTDOMAINDIR TEXTDOMAIN=sharutils export TEXTDOMAIN echo="$gettext_dir/gettext -s" fi touch -am 1231235999 $$.touch >/dev/null 2>&1 if test ! -f 1231235999 && test -f $$.touch; then shar_touch=touch else shar_touch=: echo $echo 'WARNING: not restoring timestamps. Consider getting and' $echo "installing GNU \`touch', distributed in GNU File Utilities..." echo fi rm -f 1231235999 $$.touch # if mkdir _sh32494; then $echo 'x -' 'creating lock directory' else $echo 'failed to create lock directory' exit 1 fi # ============= hdr ============== if test -f 'hdr' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'hdr' '(file already exists)' else $echo 'x -' extracting 'hdr' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'hdr' && X X X X ACE Tutorial 008 X
ACE Tutorial 008
X
Sending and receiving datagrams
X X


X SHAR_EOF $shar_touch -am 03191459100 'hdr' && chmod 0664 'hdr' || $echo 'restore of' 'hdr' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'hdr:' 'MD5 check failed' e74ecd3335da844c263f961a8ba5f867 hdr SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'hdr'`" test 515 -eq "$shar_count" || $echo 'hdr:' 'original size' '515,' 'current size' "$shar_count!" fi fi # ============= bodies ============== if test -f 'bodies' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'bodies' '(file already exists)' else $echo 'x -' extracting 'bodies' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'bodies' && PAGE=2 server.cpp directed_client.cpp broadcast_client.cpp SHAR_EOF $shar_touch -am 0121153799 'bodies' && chmod 0664 'bodies' || $echo 'restore of' 'bodies' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'bodies:' 'MD5 check failed' 707d1735ca25694e2b5fddc1f6e7e124 bodies SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'bodies'`" test 59 -eq "$shar_count" || $echo 'bodies:' 'original size' '59,' 'current size' "$shar_count!" fi fi # ============= page01.pre ============== if test -f 'page01.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page01.pre' '(file already exists)' else $echo 'x -' extracting 'page01.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page01.pre' &&

In a lot of IPC programming, the clients know where the servers are.  A mail client, for instance, has a configuration file that says where the mail host is.  Your web browser has a "location" field that you type into to give it a destination. X

What if you have written a server application and then you execute it on several systems in your network?  All of the instances are probably more or less equal to the client's point of view, so you don't want to "configure" the clients to a single server each.  Likewise, you want the ability to add and remove servers at any time so you can't just give the clients a list to choose from. X

So... how do the clients know where the servers are? X

Let 'em ask! X

Datagrams are great for this.  You can toss a datagram out onto the network and any servers listening at the correct port will* hear it.  Like ACE_SOCK_Stream that we've seen before, you can get the peer address from a datagram.  With that, the server can  send a response back to the client.  The client, in turn, can pull the peer address out and know exactly where the server lives. X

In this tutorial we'll develop three applications:  a server listening for datagrams, a client that can send to a known host and a client that can send to the entire (sub)network.  In the next tutorial, we'll expand on this to make the server a bit more prudish.

Kirthika's abstract:

* Actually, the servers might hear the datagram.  Datagrams are rather unreliable.  (Sort of like some operating systems I know.)  Still, if the network traffic isn't too bad, they generally get through.  Your clients can always send out more queries if there aren't any responses in a timely fashion. X SHAR_EOF $shar_touch -am 03191459100 'page01.pre' && chmod 0664 'page01.pre' || $echo 'restore of' 'page01.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page01.pre:' 'MD5 check failed' 0773df98f89130f72767715ed27516b6 page01.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page01.pre'`" test 2767 -eq "$shar_count" || $echo 'page01.pre:' 'original size' '2767,' 'current size' "$shar_count!" fi fi # ============= page02.pre ============== if test -f 'page02.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page02.pre' '(file already exists)' else $echo 'x -' extracting 'page02.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page02.pre' && X

The first thing we want to look at is server.cpp.  This is a pretty simple application that listens for datagrams at a known port and sends back a response.  In order to implement a true "discovery"  mechanism, the server will have to be a little bit more picky about who it responds to.  We'll tackle that issue in the next tutorial though... X


SHAR_EOF $shar_touch -am 03191459100 'page02.pre' && chmod 0664 'page02.pre' || $echo 'restore of' 'page02.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page02.pre:' 'MD5 check failed' 6b27ef52d53a13d2d9e5a5ad16e9be4d page02.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page02.pre'`" test 416 -eq "$shar_count" || $echo 'page02.pre:' 'original size' '416,' 'current size' "$shar_count!" fi fi # ============= page03.pre ============== if test -f 'page03.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page03.pre' '(file already exists)' else $echo 'x -' extracting 'page03.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page03.pre' && X

In directed_client.cpp we create a client that knows how to send a datagram to a server on a known host.  This is a good thing if you know where the server lives and want to have a conversation.   The Unix talk utilitiy, for instance, could be written this way. X


SHAR_EOF $shar_touch -am 03191459100 'page03.pre' && chmod 0664 'page03.pre' || $echo 'restore of' 'page03.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page03.pre:' 'MD5 check failed' aa0724ca0a09f5b5e6c7e3f355646111 page03.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page03.pre'`" test 345 -eq "$shar_count" || $echo 'page03.pre:' 'original size' '345,' 'current size' "$shar_count!" fi fi # ============= page04.pre ============== if test -f 'page04.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page04.pre' '(file already exists)' else $echo 'x -' extracting 'page04.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page04.pre' && In broadcast_client.cpp we find out how to send a single datagram to every host on our (sub)network.  I have to say (sub)network because broadcast datagrams typically are not passed through routers.  So, if your network admin has divided up your network into subnets, your broadcasts will likey stay on the subnet you're a part of. X

I've only commented the parts that are different from the directed_client.


SHAR_EOF $shar_touch -am 03191459100 'page04.pre' && chmod 0664 'page04.pre' || $echo 'restore of' 'page04.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page04.pre:' 'MD5 check failed' 8811bded669a7a7be85a4878d5076190 page04.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pre'`" test 481 -eq "$shar_count" || $echo 'page04.pre:' 'original size' '481,' 'current size' "$shar_count!" fi fi # ============= page05.pre ============== if test -f 'page05.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page05.pre' '(file already exists)' else $echo 'x -' extracting 'page05.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page05.pre' &&
That's it for this tutorial.  In the next one we'll add some intelligence to the data put into the datagrams.  By doing so, we'll be able to classify our clients and servers into groups.  By combining the data content and the server's port we can get fairly fine-grained control over who talks to who. X

For you convenience:

SHAR_EOF $shar_touch -am 03191459100 'page05.pre' && chmod 0664 'page05.pre' || $echo 'restore of' 'page05.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page05.pre:' 'MD5 check failed' b05fdac8c7eb81813c74eb99525cf601 page05.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page05.pre'`" test 578 -eq "$shar_count" || $echo 'page05.pre:' 'original size' '578,' 'current size' "$shar_count!" fi fi # ============= page02.pst ============== if test -f 'page02.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page02.pst' '(file already exists)' else $echo 'x -' extracting 'page02.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page02.pst' &&
X

And that's really all there is to it.  Obviously there is some room for improvement.  The most blatant is the somewhat small buffer size for receiving the datagram.  I've never been able to get a solid answer on datagram sizes.  The theoretical limit is just under 64k but you have to deal with fragmentation.  Some readings indicate that 8k is a reasonable size, others go much smaller.  My general rule of thumb is to keep datagrams relatively small (eg -- under 8k or so) and test a lot.  If you find that your routers are fragmenting your larger datagrams, back off to something smaller.  Of course, if you must send 100k and can only do so 1k at a time, you'll have to worry about retransmissions & reordering.  At that point, you might consider going to TCP.  Remember:  datagrams are unreliable!  Don't try to make 'em do something they werent' designed for! SHAR_EOF $shar_touch -am 03191459100 'page02.pst' && chmod 0664 'page02.pst' || $echo 'restore of' 'page02.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page02.pst:' 'MD5 check failed' 3cb7da5f75a40616f6cc498a731f4a16 page02.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page02.pst'`" test 952 -eq "$shar_count" || $echo 'page02.pst:' 'original size' '952,' 'current size' "$shar_count!" fi fi # ============= page03.pst ============== if test -f 'page03.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page03.pst' '(file already exists)' else $echo 'x -' extracting 'page03.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page03.pst' &&


X

That's all neat and good but the point of what we're doing here is not to talk to a server we know about but to discover servers we don't know about.  Now, you could send a directed datagram to every possible host address on your network but that's not a very nice thing to do.  On the next page, we'll find out the right approach... X SHAR_EOF $shar_touch -am 03191459100 'page03.pst' && chmod 0664 'page03.pst' || $echo 'restore of' 'page03.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page03.pst:' 'MD5 check failed' 0fcbc10be47175a0d42590fb4adab43b page03.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page03.pst'`" test 367 -eq "$shar_count" || $echo 'page03.pst:' 'original size' '367,' 'current size' "$shar_count!" fi fi # ============= page04.pst ============== if test -f 'page04.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page04.pst' '(file already exists)' else $echo 'x -' extracting 'page04.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page04.pst' &&


X

 About that subnet thing:

If you run this client on a host that has multiple network interfaces, the broadcast will go to all of those (sub)networks.  What do you do, though, if you need to get past a router?  My advice is to write a server that will run on hosts on both sides of your router.  When a server on one side of the router receives a broadcast, it would send a directed datagram to it's counterpart on the other side of the router.  The counterpart would then re-broadcast the original datagram on that sub-net.  Cheap, simple and effective.
One final word of warning:
When creating your broadcast datagrams you may see something like this:  ACE_SOCK_Dgram_Bcast::mk_broadcast: Broadcast is not enable for this interface.: Unknown error.  There are some interfaces (ppp, slip) that don't support broadcast datagrams.  That's what you're seeing here.
Ok, one more warning:
If you happen to have multiple servers running on your network when you invoke this client, the response could come from any one of them.
X SHAR_EOF $shar_touch -am 03191459100 'page04.pst' && chmod 0664 'page04.pst' || $echo 'restore of' 'page04.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page04.pst:' 'MD5 check failed' 2e01fc6b6638dfa77ed629c0e3e77e21 page04.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pst'`" test 1173 -eq "$shar_count" || $echo 'page04.pst:' 'original size' '1173,' 'current size' "$shar_count!" fi fi rm -fr _sh32494 exit 0