WVPART(streams, The Streams Library, WVCHAPTER(wvstream, WvStream - communications fundamentals, WvStream is the base class from which (not surprisingly) most of the WvStreams library is derived. It defines the basic properties of a stream: a sequence of bytes that may or may not be ready for reading or writing at any given time. WvStream is a perfectly useful class all by itself; it can read, write, and wait on a Unix fd (file descriptor), which is sufficient for nearly all communications in Unix-like systems. The other WvStream classes do basically the same thing, but create and manipulate their fds automatically instead of requiring you to do it. WVSECT1(wvstreamsimple, Basic WvStream Examples, The following example program reads from stdin and echos the results back to stdout with a bit of added commentary. Remember that in Unix, fd 0 is stdin, and fd 1 is stdout. WVEXAMPLE(wvstreamex.cc) isok() ("is okay") returns true as long as the stream is still alive. If you press CTRL-D as the input to a program, this sends an end-of-file message and the stream becomes "not okay." (isok() == false). The getline() function reads a character string of arbitrary length using WvBuffer, up to but not including the first newline. The (-1) tells getline() to wait forever for the newline if necessary. Try changing it to 5000 (5 seconds) and see how that affects the operation of the program. Whenever possible, WvStreams tries to shelter you from weird Unix-specific information like fd 0 and fd 1. There is a special WvStream pointer called wvcon which points to both stdin and stdout. (Actually, since it points at both fd 0 and fd 1, wvcon needs to be a WvSplitStream object, but don't worry about that just yet.) You can reassign wvcon to any other stream if you like -- this will let you essentially redirect stdin and stdout for the WvStreams classes that use it. Here's an example of how you can use wvcon to simplify the above sample program. WVEXAMPLE(wvstreamex2.cc) ) WVSECT1(wvstreamrw, Reading and Writing, Here's a sample program that uses the read() and write() member functions. Usually, these functions are used more often than getline() because WvStreams are commonly used to manipulate binary data rather than line-by-line data. WVEXAMPLE(wvstreamex3.cc) Unlike getline(), read() isn't very smart. You must supply it with a buffer and tell it how large the buffer is. However, there's no guarantee that read() will fill the entire buffer or any of it -- you should _always_ check the return value (numread, in this case) to find out how many bytes were read. You'll find out later (when we talk about select()) that this is actually the right thing to do -- the last thing you want in most WvStreams programs is for read() to sit doing nothing until a buffer fills up. On the other hand, the write() function has a built-in buffer, so as long as the stream is alive, the return value of write() should always be the same as the length you provide as the second parameter. The print() function works sort of like printf(). It uses the complex constructor for WvString to type-safely format strings, and then the write() function to send them out to the stream. ) WVSECT1(wvstreamselect, Waiting on a stream with select(), The previous example was actually not completely correct -- it works, but depending on the wvcon implementation, it could accidentally use 100% CPU time. (The default wvcon implementation doesn't actually have this "problem," but that's a coincidence and might change in the future.) Remember that read() is not guaranteed to fill the input buffer. In fact, it's not even guaranteed to read any bytes at all. So in theory, the previous example might find itself endlessly looping, calling isok() and read() over and over until some input is received. That will work, of course, but it wastes a lot of CPU time. What we should really do is tell WvStream to wait until some data is available, and only then call read(). Here's how. WVEXAMPLE(wvstreamex4.cc) Note again that the (-1) parameter to select() means "wait forever." In this case, it doesn't really matter if you set it to 5000 (5 seconds), for example, because we'll simply wait five seconds and restart the loop. If we do select(0), select() doesn't wait at all. This isn't as pointless as it seems: select() returns true or false depending whether the stream was ready or not, so select(0) is a good way to test if a stream is ready without actually stopping your program. You can also select() to see if a stream is writable, but this is used much less often since WvStream now has write buffers anyway (so you can always write to a stream without delaying). Sometimes it can be useful, though: for example, a TCP connection is not "writable" until it's connected to the remote host. That makes sense, if you think about it. It's not readable either, of course, but it doesn't necessarily become readable as soon as it connects. A connected TCP stream is always writable. You can check for (or wait for) readability and writability at the same time. There is also a test for a stream "exception", but no one has ever used that, so I don't know what it does. The prototype for select() actually looks like this: WVCMD(bool select(time_t msec_timeout, bool readable=true, bool writable=false, bool isexception=false);) That means normally, we want to check whether a stream is readable, and we don't care if it's writable or has an exception. To check whether a stream is writable, for example, use a line like this: WVCMD(wvcon->select(0, false, true, false);) To wait up to 5 seconds until the stream is either readable or writable, try this: WVCMD(wvcon->select(5000, true, true, false);) Now, you're probably wondering why read() can't just wait for data and write() can't just send it out, saving us all a lot of trouble. Well, that's all fine and good for single-tasking one-stream programs, but WvStreams was designed to handle lots of simultaneous connections -- that's when select() really gets useful. We'll talk about that later, in the section about WvStreamList. ) WVSECT1(wvstreamcallbacks, Callback Functions, Unless you've skipped ahead to the WvStreamList section, this section will seem even more useless than the one on select(). But trust us, when you have lots of different streams talking to each other later on, you'll be really glad to have callback functions around. Here's a quick example, using one of the predefined stream callbacks called autoforward(). WVEXAMPLE(wvstreamex5.cc) The above program does almost exactly what the previous ones did, except that instead of manually reading and writing strings, we tell the stream that its "default operation" is to auto-forward its data to wvcon... which is, in this case, itself. The result is that we forward stdin to stdout as before, only without the extra text surrounding it. Almost all WvStreams programs end up using callbacks for most of their work. That's because with callbacks, each stream knows what to do with its own data, and you don't have to say it in the main program. That's what object-oriented programming is all about, after all. In fact, the main loop in most WvStreams programs ends up looking a lot like the main loop in the program above: wait until the stream is ready, then run its callback function. What does autoforward() do, exactly? Let's write a callback function ourselves and demonstrate one possibility. WVEXAMPLE(wvstreamex6.cc) Note that mycallback() calls getline() with a (0) delay parameter; you almost never want a callback function to wait for something (although it certainly can, if you really want). Instead, if getline can't find a whole line of text to read, we simply return from the callback and wait until next time. This brings up an interesting point about select() and getline(): select() might say yes, the stream is ready for reading, but getline() might still return NULL. Why? Because getline() looks for a terminating newline. If the only input is the letters "abcdefg" (without a trailing newline character) then the stream really is ready for reading, but getline hasn't retrieved a whole line of input. Don't worry, though, running getline() once clears the input-ready condition, so the callback will only get called again when more data is received. But whatever you do, don't be fooled into thinking that just because you're in the callback function, that data will always be returned on a read() or getline(). Always, _always_ check the return values first. ) WVSECT1(wvstreamdelay, Delays and Timeouts, We should mention that you can use the select() function for millisecond-resolution delays, or to timeout when no data is received for a certain amount of time. The following example babbles something at you after every second of no input, and exits if you don't say anything for ten seconds. WVEXAMPLE(wvstreamex7.cc) ) ) WVCHAPTER(somesimplestreams, Some Simple Streams, Let's look at a few streams that are only slightly more complicated than WvStream itself: WvFile, WvFileWatcher, and WvPipe. WVSECT1(wvfile, WvFile - accessing Unix files, WvFile is the simplest WvStream-derivative around. All it does is open a Unix file for you given a filename, so you don't have to supply the fd yourself. WVEXAMPLE(wvfileex.cc) Need we say more? Oh, since Unix devices are just files, you can use them with WvFile just as easily. For example, if your modem is /dev/ttyS2, you can connect to it by creating a WvFile that refers to /dev/ttyS2. Of course, the WvModem class is probably more useful for that particular job. ) WVSECT1(wvwatcher, WvFileWatcher - waiting for a file to change, WvFileWatcher is a bit more interesting than WvFile: it reads to the end of a file, and then if the file grows, select() returns true and you can read more data; if the file shrinks (which presumably means it was rewritten from scratch), it starts again at the top of the file. To think of it another way: WvFileWatcher makes select() make sense on regular files. Normally, select() is always true for a file, since all the data is always immediately available. (Note that this isn't true if your WvFile is connected to a Unix device; then select() makes sense already, and WvFileWatcher will just mess you up.) WvFileWatcher is very seldom used except as a WvStreams test, so we won't bother to give an example here. ) WVSECT1(wvpipe, WvPipe - talking to subtasks, WvPipe will certainly be very interesting to anyone who writes programs that need to spawn off sub-programs, and read and possibly write to that programs input and output. WvPipe makes this rediculously easy, by making the application call look exactly like any other stream. No more messy exec() calls, and best yet, no more blocking - if you just need to send a program off to do something, then you don't need to fork at all, you can just create a WvPipe stream which ignores any input, output, or error messages, and add it to your WvStreamList. If you do need to access input or output, you can easily just select() on this pipe, which will return true when the program has something interesting for you. WvPipes allow you to create a new process, and attaching its stdin/stdout to a WvStream. Insert Example Here... ) WVSECT1(wvmodem, WvModem - baud rates and terminal modes, Definition of the WvModemBase and WvModem classes. Inherit from WvFile, but do various important details related to modems, like setting baud rates and dropping DTR and the like. ) ) WVCHAPTER(logs, Dealing with Log Messages, WVSECT1(wvlog, WvLog - printing log messages, A generic data-logger class with support for multiple receivers. If no WvLogRcv objects have been created (see wvlogrcv.h) the default is to log to stderr. WvLog supports partial- and multiple-line log messages. For example, WVCMD(log.print("test "); log.print("string\nfoo");) will print: WVCMD(appname(lvl): test string appname(lvl): foo) See also, WvLogRcv. ) WVSECT1(wvlogrcv, WvLogRcv - receiving and disposing of log messages, WvLogRcv is an enhanced "Log Receiver" classes for WvLog. WvLogRcv-derived classes support automatic formatting of log lines with a prefix, removing control characters, and so on, to keep track of line-prefix-printing and other formatting information. WvLogRcv supports partial- and multiple-line log messages, like WvLog. An example: WVEXAMPLE(wvlogex.cc) ) WVSECT1(wvlogbuffer, WvLogBuffer - saving log messages to a buffer, WvLogBuffer is a descendant of WvLogRcv that buffers log messages for later use. It only keeps up to max_lines log entries for every source/debug level, s.t. debug level <= max_level WVEXAMPLE(wvlogbufex.cc) ) WVSECT1(wvlogfile, WvLogFile - sending log messages to a file, A "Log Receiver" that logs messages to a file. WVEXAMPLE(wvlogfileex.cc) ) WVSECT1(wvsyslog, WvSyslog - sending log messages to syslog, WvSyslog is a descendant of WvLogRcv that sends messages to the syslogd daemon. ) WVSECT1(pipelogex, An example of WvPipe and WvLog together, WVEXAMPLE(wvpipelogex.cc) ) ) WVCHAPTER(wvstreamlist, WvStreamList - dealing with multiple streams, WvStreamList is one of the most important parts of the WvStreams library. Almost every real WvStreams program (in contrast with all the preceding examples) involves at least one WvStreamList. We would have mentioned it much sooner, but we thought it would be nice to have a few different kinds of streams to play with before we started creating lots of them at a time. Fundamentally, what WvStreamList does is allow you to select() on more than one stream at a time. That way you can, for example, wait for multiple TCP connections to send you data, and wake up immediately as soon as any one of them is available. Without the magic of WvStreamList, there is no good way to wait for multiple streams simultaneously. There are, however, a couple of bad ways, which we will mention shortly just so you don't try them. WVSECT1(wvstreamlistbadidea, Don't do this, Don't try this: WVEXAMPLE(wvstreamlistex.cc) It will fail in three different ways depending on the value of X. If X==-1, each select() statement waits forever for data. That means in order to print a snorkle (which should appear once per second), you must first print a foo (which only happens once every three seconds). That's bad enough, but some streams might be almost completely idle, so you certainly can't get away with doing that. If X==0, we never wait at all. This will appear to work properly -- if you run the program, the snorkles and foos will show up at the right time. But unfortunately, your program will spin in the main loop, and take 100% of the CPU unnecessarily. We can compromise by making X==1000 instead. That way, each select() statement will give up after 1 second. Since the snorkles only happen once per second, that should be okay. However, it's not perfect -- the snorkles should appear _exactly_ one second apart, but you can't guarantee that using this technique. It'll be somewhere between zero and one seconds, depending on random luck. Besides, the whole point of this chapter is there's a better way, so don't write programs like the one above. ) WVSECT1(wvstreamlistgood, Do this instead, Here's a better way to choose between two lists, using WvStreamList. As you might have guessed, WvStreamList is a type of WvLinkList that contains WvStreams. It's also a stream itself, so (among other things) you can select() on a WvStreamList and include one WvStreamList inside another. But let's keep it simple for now: WVEXAMPLE(wvstreamlistex2.cc) A streamlist is always okay (isok() == true) because you can always add and remove streams from it. That's why our main loop is checking for "okayness" of the two main streams, rather of the list. But also notice the contents of the main loop. We select() on the list, and if it returns true, we then callback() the list. What's happening there? What happens is this. WvStreamList::select() will return true if the select() of any of its member streams would return true. (It doesn't actually _call_ the select() function of each of its member streams, but the idea is the same. That's why WvStreamList is magic.) When you run WvStreamList::callback(), it calls back all the member streams that would have returned true to a select() call. If you give select() a delay (in this case, infinity), it will wait until any one of the member streams is ready. This mechanism increases fairness between streams, in case you have a lot of high-bandwidth streams and you're having trouble keeping up. If more than one stream is ready, callback() will call each one sequentially before it returns. That way, no one stream will get priority over the others. ) WVSECT1(wvstreamlistfun, An Interesting Example, Now that we've introduced all the pieces, we can have our first interesting WvStream example by throwing several pieces together. Here's a program that prints messages every once in a while through a WvPipe and using timeouts, while you talk to your modem device via the console. WVEXAMPLE(wvstreamfunex.cc) ) ) WVCHAPTER(magicstreams, Some Magical Streams, WVSECT1(wvstreamclone, WvStreamClone - a stream within a stream, WvStreamclone is probably one of the coolest, and most confusing parts within the WvStream library. Almost everyone who has ever encountered this stream the first time has had to stop and go over it a few times before they can wrap their head around what is going on. Ok.. now that I've scared you... I'm going to tell you that it is really quite easy. Most of the time, when you are using a WvStreamClone, you will be doing something like the following: 1. Start a Stream on a TCP Connection 2. Change this Stream into something else (like an SSL Stream) 3. And then talk some sort of high level protocol (like HTTP) So you have one stream (a TCP Connection), that morphs into another stream TYPE (an SSL Stream), that then becomes another stream (talking HTTP). All the while, not changing the stream that was started and added to the original WvStreamList. WvStreamClone simply forwards all requests to the "cloned" stream. A class derived from WvStreamClone can contain a WvStream as a dynamically allocated data member, but act like the stream itself. This is useful for classes that need to create/destroy WvPipes while they run, for example, yet do not want users to know about the member variable. WvStreamClone _does_ attempt to close the cloned stream in the destructor. Insert Example here... ) WVSECT1(wvsplitstream, WvSplitStream - separating read and write streams, x) WVSECT1(wvloopback, WvLoopback - talking to yourself across fork(), WvLoopback uses a socketpair() to create a stream that allows you to read() everything written to it, even (especially) across a fork() call. ) ) WVCHAPTER(unusualstreams, Some Unusual Streams, WVSECT1(wvtimestream, WvTimeStream - timed events, WvTimeStream causes select() to be true after a configurable number of milliseconds. Because programs using WvStream make no guarantees about how often select() will be called, WvTimeStream tries to adjust its timing to a correct _average_ number of milliseconds per tick. For example, if ms_per_tick=100, WvTimeStream will tick 10 times in one second. However, there may be a few milliseconds of difference ("jitter") for each individual tick, due to random system delays. WVEXAMPLE(wvtimeex.cc) ) WVSECT1(wvtimeoutstream, WvTimeOutStream - timed out, WvTimeoutStream is a stream that becomes !isok() after a configurable number of milliseconds. It will wake up a select(). It will return true if select()ed and that the timeout has expired. But using it in a WvStreamList will not have it call the callback/execute because the WvStreamList checks whether isok() is true before doing the select(). WVEXAMPLE(wvtimeoutex.cc) ) WVSECT1(wvprotostream, WvProtoStream - a protocol state machine, WvProtoStream is a framework that makes it easy to communicate using common command-response driven protocols. This is supposed to be flexible enough to handle FTP, HTTP, SMTP, tunnelv, Weaver rcmd, and many others. ) ) WVCHAPTER(wvcallback, The Magic of WvCallBack, WVSECT1(wvcallback2, WvCallback - What does it do?, WvCallback calls a member function inside a class when certain event has happened or when certain task has been finished. The key point of WvCallback is that it can be used in a function that is not in the same class as the function that will be invoked after the event has occurred. WvCallback can operate as: 1. a function pointer when the invoked function is not part of any class or as 2. a member function pointer when the invoked function belongs to a class ) WVSECT1(wvcallback3, WvCallback - How to create your own WvCallback?, First, you have to declare a new type of WvCallback using DeclareWvCallback. DeclareWvCallback is a macro that helps to create a new type of WvCallback. Note: The macro of DeclareWvCallback is written in ~src/niti/src/wvstreams/include/wvcallback.h The syntax of DeclareWvCallback is as follows: DeclareWvCallback (n, ret, type, parms...) WVSECT2(declarewvcallback, The input parameters of DeclareWvCallback are explained as follows:, Reminder: WvCallback = function pointer or member function pointer n - the number of parameters that the function pointed to by the WvCallback takes. ret - the return type of the function pointed to by the WvCallback (e.g.: int, void, ...) type - the name of the callback type (e.g.: WvConfCallback). This is the type with which you will declare your WvCallback with. parms... - the types of the parameters that the function pointed to by the WvCallback. To create your own WvCallback, you need to include wvcallback.h. Then you need to declare your own WvCallback type at the top level. You cannot run DeclareWvCallback inside a class; otherwise, it causes an internal compiler error (g++ 2.95.4). See the following simple example: WVSECT3(funcptr, WvCallback - As a function pointer - function not inside any class, WVEXAMPLE(wvcallbackex.cc) ) WVSECT3(functptr2, WvCallback - As a member function pointer - function inside a class, WVEXAMPLE(wvcallbackex2.cc) ) ) ) ) )