#!/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 1999-02-14 13:38 EST by . # Source directory was `/var/home/jcej/projects/ACE_wrappers/docs/tutorials/015'. # # Existing files will *not* be overwritten unless `-c' is specified. # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 414 -rw-rw-r-- hdr # 419 -rw-rw-r-- bodies # 4228 -rw-rw-r-- page01.pre # 194 -rw-rw-r-- page02.pre # 318 -rw-rw-r-- page03.pre # 178 -rw-rw-r-- page04.pre # 304 -rw-rw-r-- page05.pre # 418 -rw-rw-r-- page06.pre # 321 -rw-rw-r-- page07.pre # 501 -rw-rw-r-- page08.pre # 640 -rw-rw-r-- page09.pre # 978 -rw-rw-r-- page10.pre # 334 -rw-rw-r-- page11.pre # 49 -rw-rw-r-- page12.pre # 326 -rw-rw-r-- page13.pre # 540 -rw-rw-r-- page14.pre # 770 -rw-rw-r-- page15.pre # 660 -rw-rw-r-- page16.pre # 213 -rw-rw-r-- page17.pre # 372 -rw-rw-r-- page18.pre # 281 -rw-rw-r-- page19.pre # 356 -rw-rw-r-- page20.pre # 151 -rw-rw-r-- page21.pre # 2567 -rw-rw-r-- page22.pre # 406 -rw-rw-r-- page04.pst # 617 -rw-rw-r-- page09.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 _sh31334; 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 ACE Tutorial 015 X
ACE Tutorial 015
X
Building a protocol stream
X


SHAR_EOF $shar_touch -am 1019173798 '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' 41322d388f7bb6c8eba031c4a6ab53ce hdr SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'hdr'`" test 414 -eq "$shar_count" || $echo 'hdr:' 'original size' '414,' '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' && # # The client application specific files # PAGE=2 client.cpp Client_i.h Client_i.cpp # # The server application specific files # server.cpp Server_i.h Server_i.cpp Handler.h Handler.cpp # # The basic protocol stream # Protocol_Stream.h Protocol_Stream.cpp Protocol_Task.h Protocol_Task.cpp # # Send/Receive objects # XXmit.h XXmit.cpp Recv.h Recv.cpp # # Protocol objects # Compressor.h Compressor.cpp Crypt.h Crypt.cpp SHAR_EOF $shar_touch -am 1022205498 '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' a6c99d6567b0640ad524b196dc43647e bodies SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'bodies'`" test 419 -eq "$shar_count" || $echo 'bodies:' 'original size' '419,' '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' && X In a typical client/server system you will be sending and receiving X data. That's the whole point after all.

In the client/server tutorials that we've done so far it was just a X matter of sending a buffer of data to the peer. This was done X with the send*() and recv*() methods of the ACE_SOCK* objects.

In a more robust system, one might want to process the data before X sending it to a peer and "unprocess" it after reading from a X peer. These processing steps might include encryption, X compression, applying checksums or any number of other actions.

In this tutorial a Protocol_Stream object is created to encrypt and X compress* data being sent between peers. Both client and server X applications are presented as well. I present the application level code first and then go into the details of the protocol stream and it's helper objects. If the stream stuff in the application logic is confusing then just read on by and come back to it after the later discussions.

Disclaimer:

Kirthika's abstract:

* Ok, I didn't really implement encryption and X compression objects. I'll leave that as a thought X exercise! SHAR_EOF $shar_touch -am 0214124599 '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' a09c3ff1f21a90aab991e0f38dc00458 page01.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page01.pre'`" test 4228 -eq "$shar_count" || $echo 'page01.pre:' 'original size' '4228,' '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' && We'll take a look first at the client application. As usual, our goal X is to keep the main() application as simple as possible and X delegate the tricky stuff to another object. X


SHAR_EOF $shar_touch -am 1019173798 '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' 6a2e64962c95b349625f418502c95952 page02.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page02.pre'`" test 194 -eq "$shar_count" || $echo 'page02.pre:' 'original size' '194,' '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' && The Client object is designed to hide all of the messy connection X logic from it's users. It also provides put/get methods for X sending data to the server and receiving the server's response. X Note the Protocol_Stream member that will take care of X converting and sending/receiving the data.
SHAR_EOF $shar_touch -am 1019173798 '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' 95326c064b10bbda428d3c967f285760 page03.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page03.pre'`" test 318 -eq "$shar_count" || $echo 'page03.pre:' 'original size' '318,' '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' && The implementation of the Client object. Only the open() method X really does any work. The other methods simply delegate their X function to the Protocol_Stream.
SHAR_EOF $shar_touch -am 1019173798 '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' 2955ca8d3b0fc6840f3d371aea528b8d page04.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pre'`" test 178 -eq "$shar_count" || $echo 'page04.pre:' 'original size' '178,' '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' && Like the client, we want to keep the main() part of our server code as X simple as possible. This is done by putting most of the work X into the Handler object that will deal with client connections. XFrom the looks of the code below, I think we've been successful in our simplification.
X SHAR_EOF $shar_touch -am 1019173798 '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' 8833b0d7f90a4d13f68b8cdb9147c29a page05.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page05.pre'`" test 304 -eq "$shar_count" || $echo 'page05.pre:' 'original size' '304,' 'current size' "$shar_count!" fi fi # ============= page06.pre ============== if test -f 'page06.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page06.pre' '(file already exists)' else $echo 'x -' extracting 'page06.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page06.pre' && The Server object exists in order simplify the main() application level. To that end, it hides the details of creating an acceptor and managing the reactor.

The static close() method available for a signal handler as you saw on the previous page. Of course the assumption here is that there would only be one Server instance but since you can't provide a TCP/IP port, that's probably a valid assumption!


SHAR_EOF $shar_touch -am 1019173798 'page06.pre' && chmod 0664 'page06.pre' || $echo 'restore of' 'page06.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 'page06.pre:' 'MD5 check failed' 06a0abefdf4e704b42147f433bd27e4d page06.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page06.pre'`" test 418 -eq "$shar_count" || $echo 'page06.pre:' 'original size' '418,' 'current size' "$shar_count!" fi fi # ============= page07.pre ============== if test -f 'page07.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page07.pre' '(file already exists)' else $echo 'x -' extracting 'page07.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page07.pre' && And now the implementation of Server. This is actually just the main() code from a previous tutorial broken into appropriate method calls. It may seem silly to do this rather than keeping the stuff in main() but you'll find that you have less trouble enhancing an application when you take this sort of approach.
X SHAR_EOF $shar_touch -am 1019173798 'page07.pre' && chmod 0664 'page07.pre' || $echo 'restore of' 'page07.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 'page07.pre:' 'MD5 check failed' 7dfb75884939c3c05ee1e1000956e9f4 page07.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page07.pre'`" test 321 -eq "$shar_count" || $echo 'page07.pre:' 'original size' '321,' 'current size' "$shar_count!" fi fi # ============= page08.pre ============== if test -f 'page08.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page08.pre' '(file already exists)' else $echo 'x -' extracting 'page08.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page08.pre' && The Handler object is our event handler. You can use either ACE_Event_Handler or ACE_Svc_Handler<> for the baseclass. I generally prefer the latter since it takes care of some housekeeping that I would otherwise be responsible for.

The class declaration is taken almost exactly from a previous tutorial. A good design will have a simple handler object that will collect data from the peer and pass it along to another object for processing. Again, keep it simple and delegate authority.


SHAR_EOF $shar_touch -am 1019173798 'page08.pre' && chmod 0664 'page08.pre' || $echo 'restore of' 'page08.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 'page08.pre:' 'MD5 check failed' 471889eb736a025fc46719549bca160a page08.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page08.pre'`" test 501 -eq "$shar_count" || $echo 'page08.pre:' 'original size' '501,' 'current size' "$shar_count!" fi fi # ============= page09.pre ============== if test -f 'page09.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page09.pre' '(file already exists)' else $echo 'x -' extracting 'page09.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page09.pre' && Like any other event handler, the handle_input() method will be responsible for getting data from the peer() and doing something with it. In this case, we have a Protocol_Stream to deal with. We'll use the stream for the actual I/O but we are ultimately responsible for processing the data from the peer. To do that, we've created a Handler_Task that fits within the Protocol_Stream framework to process data that has been received. Handler::handle_input() will tell the stream that it's time to read data and that data will eventually show up at Handler_Task::recv() where we'll process it as required by our application logic.
SHAR_EOF $shar_touch -am 1019173798 'page09.pre' && chmod 0664 'page09.pre' || $echo 'restore of' 'page09.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 'page09.pre:' 'MD5 check failed' da5c4b11d356dc9caf3a8e3b6b2527a6 page09.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page09.pre'`" test 640 -eq "$shar_count" || $echo 'page09.pre:' 'original size' '640,' 'current size' "$shar_count!" fi fi # ============= page10.pre ============== if test -f 'page10.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page10.pre' '(file already exists)' else $echo 'x -' extracting 'page10.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page10.pre' && And so finally we come to the Protocol_Stream. That, after all, is the focus of the entire tutorial but it took us half of the day to get here!

The Protocol_Stream uses an ACE_Stream to move an ACE_Message_Block through a series of tasks. Each task in the stream is responsible for performing some operation on the data in the message block. That is the nature of a protocol stream (or "stack" if you prefer). In this stream, the data is compressed and encrypted* on its way between peers. We also allow users of the stream to install a reader task to handle data that percolates up from the peer. As you saw a page or two ago, this is most useful for a server. X

*Again, I just pretend to do these things. It would take another day or two to go through any sort of reasonable encryption or compression!

Before we get into the code, here's a picture that's shows what's going on here.


SHAR_EOF $shar_touch -am 1019173798 'page10.pre' && chmod 0664 'page10.pre' || $echo 'restore of' 'page10.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 'page10.pre:' 'MD5 check failed' a74423390fb5217104c6d89d7f202e8b page10.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page10.pre'`" test 978 -eq "$shar_count" || $echo 'page10.pre:' 'original size' '978,' 'current size' "$shar_count!" fi fi # ============= page11.pre ============== if test -f 'page11.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page11.pre' '(file already exists)' else $echo 'x -' extracting 'page11.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page11.pre' && And now the implementation of the Protocol_Stream. There are more lines of code here than we've seen so far but it still isn't complicated. The basic idea is to construct the ACE_Stream with our set of protocol objects that will manipulate the data. Our primary concern in this file is to get everything in the correct order!
SHAR_EOF $shar_touch -am 1019173798 'page11.pre' && chmod 0664 'page11.pre' || $echo 'restore of' 'page11.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 'page11.pre:' 'MD5 check failed' b0e968102fb417b12710e99465f4e387 page11.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page11.pre'`" test 334 -eq "$shar_count" || $echo 'page11.pre:' 'original size' '334,' 'current size' "$shar_count!" fi fi # ============= page12.pre ============== if test -f 'page12.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page12.pre' '(file already exists)' else $echo 'x -' extracting 'page12.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page12.pre' && A quick look at the Protocol_Task header...
SHAR_EOF $shar_touch -am 0210223499 'page12.pre' && chmod 0664 'page12.pre' || $echo 'restore of' 'page12.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 'page12.pre:' 'MD5 check failed' 5258df32a7fddcecfe902aec8440f98f page12.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page12.pre'`" test 49 -eq "$shar_count" || $echo 'page12.pre:' 'original size' '49,' 'current size' "$shar_count!" fi fi # ============= page13.pre ============== if test -f 'page13.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page13.pre' '(file already exists)' else $echo 'x -' extracting 'page13.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page13.pre' && The Protocol_Task implementation takes care of the open(), close(), put() and svc() methods so that derivatives can concentrate on the send() and recv() methods. After a while you find that most ACE_Task<> derivatives look very similar in the four basic methods and only need one or two additional to do any real work.
SHAR_EOF $shar_touch -am 1019173798 'page13.pre' && chmod 0664 'page13.pre' || $echo 'restore of' 'page13.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 'page13.pre:' 'MD5 check failed' 8b74a6d79d158222928097a9bb1335db page13.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page13.pre'`" test 326 -eq "$shar_count" || $echo 'page13.pre:' 'original size' '326,' 'current size' "$shar_count!" fi fi # ============= page14.pre ============== if test -f 'page14.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page14.pre' '(file already exists)' else $echo 'x -' extracting 'page14.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page14.pre' && The Xmit object knows how to send data to the peer. It sits at the tail of the stream and gets everything that flows down from the head. In keeping with the spirit of things, this object does only one thing and doesn't concern itself with anyone else' details.

The only thing you might want to do is combine it with Recv. Why? As you'll realize in a page or two, the Xmit and Recv objects must interact if you're going to ensure a safe transit. By having a single object it's easier to coordinate and maintain the interaction.


SHAR_EOF $shar_touch -am 1019213498 'page14.pre' && chmod 0664 'page14.pre' || $echo 'restore of' 'page14.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 'page14.pre:' 'MD5 check failed' bfc300ca2da5b82a5e452713cbf2f544 page14.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page14.pre'`" test 540 -eq "$shar_count" || $echo 'page14.pre:' 'original size' '540,' 'current size' "$shar_count!" fi fi # ============= page15.pre ============== if test -f 'page15.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page15.pre' '(file already exists)' else $echo 'x -' extracting 'page15.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page15.pre' && The implementation of Xmit isn't too complicated. If we choose to combine it with the Recv task we simply lift the recv() method from that object and drop it into this one.

Note that close() must decide if it's being called when the stream is shutdown or when it's svc() method exits. Since we tell the baseclass not to use any threads it's a safe bet that flags will always be non-zero. Still, it's good practice to plan for the future by checking the value.

Note also that when we send the data we prefix it with the data size. This let's our sibling Recv ensure that an entire block is received together. This can be very important for compression and encryption processes which typically work better with blocks of data instead of streams of data.


SHAR_EOF $shar_touch -am 1019173798 'page15.pre' && chmod 0664 'page15.pre' || $echo 'restore of' 'page15.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 'page15.pre:' 'MD5 check failed' 0d79137eaedd73b820037fcafe6e16b6 page15.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page15.pre'`" test 770 -eq "$shar_count" || $echo 'page15.pre:' 'original size' '770,' 'current size' "$shar_count!" fi fi # ============= page16.pre ============== if test -f 'page16.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page16.pre' '(file already exists)' else $echo 'x -' extracting 'page16.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page16.pre' && Recv is the sibling to Xmit. Again, they could be combined into a single object if you want.

An ACE_Stream is designed to handle downstream traffic very well. You put() data into it and it flows along towards the tail. However, there doesn't seem to be a way to put data in such that it will travel upstream. To get around that, I've added a get() method to Recv that will trigger a read on the socket. Recv will then put the data to the next upstream module and we're on our way. As noted earlier, that data will eventually show up either in the reader (if installed on the stream open()) or the stream head reader task's message queue.


SHAR_EOF $shar_touch -am 1019173798 'page16.pre' && chmod 0664 'page16.pre' || $echo 'restore of' 'page16.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 'page16.pre:' 'MD5 check failed' 2d89b3c894cfcfdfef47ae506913cdad page16.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page16.pre'`" test 660 -eq "$shar_count" || $echo 'page16.pre:' 'original size' '660,' 'current size' "$shar_count!" fi fi # ============= page17.pre ============== if test -f 'page17.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page17.pre' '(file already exists)' else $echo 'x -' extracting 'page17.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page17.pre' && The Recv implementation is nearly as simple as Xmit. There's opportunity for error when we get the message size and we have to manage the lifetime of the tickler but other than that it's pretty basic stuff.
SHAR_EOF $shar_touch -am 1019173798 'page17.pre' && chmod 0664 'page17.pre' || $echo 'restore of' 'page17.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 'page17.pre:' 'MD5 check failed' 7db337f2c6ec74d75560534dec550b0e page17.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page17.pre'`" test 213 -eq "$shar_count" || $echo 'page17.pre:' 'original size' '213,' 'current size' "$shar_count!" fi fi # ============= page18.pre ============== if test -f 'page18.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page18.pre' '(file already exists)' else $echo 'x -' extracting 'page18.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page18.pre' && This and the next three pages present the protocol objects that provide compression and encryption. If you were hoping to learn the secrets of compression and encryption then I'm going to disappoint you. There are some really good libraries out there that do this stuff though and if anyone wants to integrate one of them into the tutorial I'll be glad to take it!
SHAR_EOF $shar_touch -am 1019213698 'page18.pre' && chmod 0664 'page18.pre' || $echo 'restore of' 'page18.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 'page18.pre:' 'MD5 check failed' dc5f706bd5a27009aed167c0b137648e page18.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page18.pre'`" test 372 -eq "$shar_count" || $echo 'page18.pre:' 'original size' '372,' 'current size' "$shar_count!" fi fi # ============= page19.pre ============== if test -f 'page19.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page19.pre' '(file already exists)' else $echo 'x -' extracting 'page19.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page19.pre' && Here we implement the details of our compression. By having both compression and decompression in one object it's easier to keep track of implementation details. Splitting Xmit/Recv like I did will make things more difficult if something has to change in their interaction.
SHAR_EOF $shar_touch -am 1019205798 'page19.pre' && chmod 0664 'page19.pre' || $echo 'restore of' 'page19.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 'page19.pre:' 'MD5 check failed' 4eb5dcd181f180d6c460971903efb288 page19.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page19.pre'`" test 281 -eq "$shar_count" || $echo 'page19.pre:' 'original size' '281,' 'current size' "$shar_count!" fi fi # ============= page20.pre ============== if test -f 'page20.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page20.pre' '(file already exists)' else $echo 'x -' extracting 'page20.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page20.pre' && While I might be able to come up with a competitive compressor, I don't have a snowball's chance to code up encryption. I'd be better off piping the data through the standard Unix crypt command.

So, while I was lazy with Compress, I'm realistic with Crypt. I'll show you the hooks and entry points and let someone else contribute an encryptor.


SHAR_EOF $shar_touch -am 1019213798 'page20.pre' && chmod 0664 'page20.pre' || $echo 'restore of' 'page20.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 'page20.pre:' 'MD5 check failed' 7f75130d385a34b2c421a8d7cae69cc3 page20.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page20.pre'`" test 356 -eq "$shar_count" || $echo 'page20.pre:' 'original size' '356,' 'current size' "$shar_count!" fi fi # ============= page21.pre ============== if test -f 'page21.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page21.pre' '(file already exists)' else $echo 'x -' extracting 'page21.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page21.pre' && The encryption implementation isn't any smarter than that of the compressor. Still, the hooks are there for you to insert your favorite library.
SHAR_EOF $shar_touch -am 1019213898 'page21.pre' && chmod 0664 'page21.pre' || $echo 'restore of' 'page21.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 'page21.pre:' 'MD5 check failed' 7f0f64452098cdef38c5496340a4b6c7 page21.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page21.pre'`" test 151 -eq "$shar_count" || $echo 'page21.pre:' 'original size' '151,' 'current size' "$shar_count!" fi fi # ============= page22.pre ============== if test -f 'page22.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page22.pre' '(file already exists)' else $echo 'x -' extracting 'page22.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page22.pre' && Well, this has certainly been one of the more verbose tutorials to date. I must say "thanks" to everyone who stuck it out this far!

A quick review of what we've done:

X It doesn't sound like much but it certainly took a bunch of files to get there. It's easy to get lost in the details when there's so much to cover so you're encouraged to go over things a couple of times. As always, enhancments of the tutorials is welcome!

Here's the complete file list:

SHAR_EOF $shar_touch -am 1022205898 'page22.pre' && chmod 0664 'page22.pre' || $echo 'restore of' 'page22.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 'page22.pre:' 'MD5 check failed' 9eae1b08c2e061a68bfc1f3cbc2f59de page22.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page22.pre'`" test 2567 -eq "$shar_count" || $echo 'page22.pre:' 'original size' '2567,' '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' &&

Ok, that's it for the client. We've seen a very simple main() X followed by an equally simple Client object.

For a quick look back:

Now we'll move on and examine the server counter-part of our client. SHAR_EOF $shar_touch -am 1022205698 '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' 00419a8ab9a3ddae3261840b62afdc4a page04.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pst'`" test 406 -eq "$shar_count" || $echo 'page04.pst:' 'original size' '406,' 'current size' "$shar_count!" fi fi # ============= page09.pst ============== if test -f 'page09.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page09.pst' '(file already exists)' else $echo 'x -' extracting 'page09.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page09.pst' &&


That's it for the server-specific code. I think I've been fairly successful in keeping it simple and to the point. There are a couple of places where the as-yet-undescribed Protocol_Stream pops up and may cause confusion. We're going to discuss that mystery now but before we do here's the list of server files if you want to review: X

SHAR_EOF $shar_touch -am 1022205698 'page09.pst' && chmod 0664 'page09.pst' || $echo 'restore of' 'page09.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 'page09.pst:' 'MD5 check failed' a96009f43a6fe8e6b52ffa923993a3e1 page09.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page09.pst'`" test 617 -eq "$shar_count" || $echo 'page09.pst:' 'original size' '617,' 'current size' "$shar_count!" fi fi rm -fr _sh31334 exit 0