From ba066c34dde204aa192d03a23a81356374d93731 Mon Sep 17 00:00:00 2001 From: chatty Date: Wed, 7 Apr 1993 11:50:31 +0000 Subject: Initial revision --- comm/Multiplexer.cc | 463 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 comm/Multiplexer.cc (limited to 'comm/Multiplexer.cc') diff --git a/comm/Multiplexer.cc b/comm/Multiplexer.cc new file mode 100644 index 0000000..497e041 --- /dev/null +++ b/comm/Multiplexer.cc @@ -0,0 +1,463 @@ +/* + * The Unix Channel + * + * by Michel Beaudouin-Lafon + * + * Copyright 1990-1993 + * Laboratoire de Recherche en Informatique (LRI) + * + * Channel sets, or multiplexers + * + * $Id$ + * $CurLog$ + */ + +#include "Multiplexer.h" +#include "TimeOut.h" + +#include +#include +#include + +#include +#include +#include + +#if defined(__hpux) && !defined(__GNUG__) +#define FD_SET_TYPE(x) ((int*) x) +#else +#define FD_SET_TYPE(x) (x) +#endif + +extern int errno; + +#ifndef FD_SET +#ifndef NFDBITS +#define FD_SET(n, p) ((p)->fds_bits[0] |= (1 << (n))) +#define FD_CLR(n, p) ((p)->fds_bits[0] &= ~(1 << (n))) +#define FD_ISSET(n, p) ((p)->fds_bits[0] & (1 << (n))) +#define FD_ZERO(p) ((p)->fds_bits[0] = 0) +#else +#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS))) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS))) +#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS))) +#define FD_ZERO(p) memset((char *)(p), 0, sizeof(*(p))) +#endif +#endif + + +/*?class UchMultiplexer +An object of class \typ{UchMultiplexer} is a set of channels. +Some of the channels in the set are active, others can be inactive. +A channel set can be scanned so that the functions \fun{HandleRead} +and \fun{HandleWrite} are called for each active channel that is ready to read or write. +The functions \fun{HandleSelect} of each channel is called before actually scanning +so that each channel can perform a background task or buffer input/output. +This provides a level of abstraction over the Unix \fun{select} system call. +Note that the functions that take an integer file descriptor as argument can be passed +a \typ{FILDES} or a \typ{UchChannel} because the corresponding conversion operators are defined. + +The class \typ{^{pCHAN_SET}} implements smart pointers to channel sets. +Smart pointers behave like pointers but they manage a reference count on the +pointed to objects for an automatic reclaim of dynamically allocated objects. +?*/ + +// constructor: initialize states +// + +/*? +Create an empty channel set. +?*/ +UchMultiplexer :: UchMultiplexer () +: Channels (new pUchChannel [NFILE]), + Timers (), + ReadCount (0), + WriteCount (0), + SelectCount (0), + Looping (FALSE) +{ + FD_ZERO (&ReadMask); + FD_ZERO (&WriteMask); + FD_ZERO (&SelectMask); +} + +/*?nodoc?*/ +UchMultiplexer :: ~UchMultiplexer () +{ +#ifdef CPLUS_BUG4 + delete [NFILE] Channels; +#else + delete [] Channels; +#endif + Channels = 0; +} + +/*? +Add a channel to the set. +Note that the argument is a pointer to the channel; no copy is made by this function. +If a channel with the same file descriptor as the one being added is already in the set, +the old channel is first removed, thus calling \fun{RemoveNotify} for that channel. +The virtual function \fun{AddNotify} is called before actually adding the channel +to the set. +?*/ +void +UchMultiplexer :: Add (UchChannel* chan) +{ + int fd = chan->FilDes (); + if (fd < 0) + return; + pUchChannel ochan = Channels [fd]; + if (ochan) + Remove (fd); + chan->AddNotify (*this); + AddNotify (chan); + Channels [fd] = chan; + SetMasks (fd, chan->IOMode ()); +} + +pUchChannel NIL_CHAN (0); + +/*? +Remove a channel from the set. +The virtual function \fun{RemoveNotify} is called before actually removing the channel +from the set. +?*/ +void +UchMultiplexer :: Remove (int fd) +{ + if (fd < 0) + return; + UchChannel* ch = Channels [fd]; + if (ch) { + ch->RemoveNotify (*this); + RemoveNotify (ch); + SetMasks (fd, IONone); + Channels [fd] = 0; + } +} + +/*? +Remove all channels from this channel set. +This function calls \fun{Remove} for all channels of the set. +This is a way of exiting from \fun{LoopScan}. +?*/ +void +UchMultiplexer :: RemoveAll () +{ + for (int fd = 0; fd < NFILE; fd++) + Remove (fd); +} + +/*? +Change the mode of a channel in the set. +Mode \var{IONone} makes the channel inactive (without removing it). +The virtual function \fun{SetModeNotify} is called before actually changing +the mode of the channel. +?*/ +void +UchMultiplexer :: SetMode (int fd, IOMODE mode) +{ + if (fd < 0) + return; + + SetModeNotify (Channels [fd], mode); + SetMasks (fd, mode); + Channels [fd] -> SetMode (mode); +} + + +// update the masks when channel fd changes its mode +// +/*?hidden?*/ +void +UchMultiplexer :: SetMasks (int fd, IOMODE mode) +{ + if (mode & IORead) { + if (! FD_ISSET (fd, &ReadMask)) { + ReadCount ++; + FD_SET (fd, &ReadMask); + } + } else { + if (FD_ISSET (fd, &ReadMask)) { + FD_CLR (fd, &ReadMask); + ReadCount --; + } + } + + if (mode & IOWrite) { + if (! FD_ISSET (fd, &WriteMask)) { + WriteCount ++; + FD_SET (fd, &WriteMask); + } + } else { + if (FD_ISSET (fd, &WriteMask)) { + FD_CLR (fd, &WriteMask); + WriteCount --; + } + } + + if (mode & IOSelect) { + if (! FD_ISSET (fd, &SelectMask)) { + SelectCount ++; + FD_SET (fd, &SelectMask); + } + } else { + if (FD_ISSET (fd, &SelectMask)) { + FD_CLR (fd, &SelectMask); + SelectCount --; + } + } + +} + +/*?nextdoc?*/ +void +UchMultiplexer :: AddNotify (UchChannel*) +{ + // nothing +} + +/*?nextdoc?*/ +void +UchMultiplexer :: RemoveNotify (UchChannel*) +{ + // nothing +} + +/*? +This function calls the select handler of each channel of the set in select mode. +It returns TRUE as soon as one select handler returns true, +else it returns FALSE when the select handlers have been called. +?*/ +bool +UchMultiplexer :: HandleSelect () +{ + int fd, nfd; + + if (fd0 >= NFILE) + fd0 = 0; + for (fd = fd0++, nfd = SelectCount; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + if (Channels [fd] -> HandleSelect ()) + return TRUE; + nfd--; + if (! Looping) + return FALSE; + } + return FALSE; +} + +/*? +These virtual functions are called by the functions \fun{Add}, \fun{Remove} +(and thus \fun{RemoveAll}) and \fun{SetMode}. +They provide hooks for application that need to take special action when the +set changes. They are called before the corresponding action is actually +carried out. +By default these functions do nothing. +?*/ +void +UchMultiplexer :: SetModeNotify (UchChannel*, IOMODE) +{ + // nothing +} + +/*?nextdoc?*/ +int +UchMultiplexer :: Scan (bool nointr, bool poll) +{ + fd_set rmsk, wmsk; + int nfd, ret = -1; + register int fd; + struct timeval tout; + struct timeval* timeout = 0; + + if (poll) { + timeout = &tout; + tout.tv_sec = tout.tv_usec = 0; + } + + while (ret <= 0) { + CcuCoreTimer::Fire (&Timers); + + if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) + return 0; + + for (fd = 0, nfd = SelectCount; nfd; fd++) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + UchChannel* ch = Channels [fd]; + if (ch && ch->HandleSelect ()) + return -2; + nfd--; + } + +//printf ("Scan: read %x, write %x, TimeOut %d", ReadMask, WriteMask, TimeOut); + rmsk = ReadMask; + wmsk = WriteMask; + if (!poll && TimeOut != -1) { + CcuTimeStamp now; + Millisecond delay = TimeOut - now; +//printf (", timeout in %d ms", delay); + tout.tv_sec = delay / 1000; + tout.tv_usec = 1000 * (delay % 1000); + timeout = &tout; + } +//printf ("\n"); + + ret = select (NFILE, FD_SET_TYPE(&rmsk) /*read*/, FD_SET_TYPE(&wmsk) /*write*/, 0 /*except*/, timeout); + if (ret < 0 || ret == 0 && poll) { +//printf ("select failed: %d, errno %d\n", ret, errno); + if (nointr && ret == -1 && errno == EINTR) + continue; + return ret; + } + } + + for (fd = 0, nfd = ret; nfd; fd++) { +//printf ("Scan: handle %d: r %d, w %d\n", fd, FD_ISSET (fd, &rmsk), FD_ISSET (fd, &wmsk)); + if (FD_ISSET (fd, &wmsk)) { + nfd --; + UchChannel* ch = Channels [fd]; + if (ch) + ch->HandleWrite (); + } + if (FD_ISSET (fd, &rmsk)) { + nfd--; + UchChannel* ch = Channels [fd]; + if (ch) + ch->HandleRead (); + } + } + + return ret; +} + +/*? +Scan the channels in the set and call the channel's handlers. +First the select handler (\fun{UchChannel::HandleSelect}) of each channel with mode +\var{IOSelect} is called. +If it returns TRUE, \fun{Scan} exits immediately, while \fun{LoopScan} loops immediately. +If no select handler returns TRUE, the system call \fun{select} is used to poll or +wait for the channels that are ready. +When the select returns normally, the write handlers \fun{HandleWrite} of the +channels in mode \var{IOWrite} that are ready to write are called, and +the read handlers (\fun{HandleRead}) of the channels in mode \fun{IORead} +that are ready to read are called. +If \var{nointr} is TRUE, ignore the interrupted system calls, else return an error +whenever the \fun{select} system call is interrupted. +If \var{poll} is TRUE, the call is non-blocking, else it is blocking. +\fun{Scan} calls \fun{select} only once; +it returns -2 if a select handler returns, it returns 0 if the channel set has no active channels, +else it returns the return code of \fun{select}. +\fun{LoopScan} calls \fun{select} repeatedly until \var{LoopEnd} is called, or +an error occurs, the channel set has no more active channel +This can occur because the select, read or write handler of a channel may remove +the channel or change its mode. +This is usually done when the read handler detects an end of file. +\fun{LoopScan} returns 0 when there are no more active channel in the set, +it returns -1 when an error occured (the code is in \var{errno}), +and it returns 1 if \fun{LoopEnd} was called. +?*/ +int +UchMultiplexer :: LoopScan (bool nointr) +{ + fd_set rmsk, wmsk; + register int nfd, fd; + Looping = TRUE; + + for (fd0 = 0; Looping; fd0 < NFILE ? fd0++ : (fd0 = 0)) { + CcuCoreTimer::Fire (&Timers); + + if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) + return 0; + + for (fd = fd0, nfd = SelectCount; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (! FD_ISSET (fd, &SelectMask)) + continue; + UchChannel* ch = Channels [fd]; + if (ch && ch->HandleSelect ()) + break; + nfd--; + if (! Looping) + return 1; + } + if (nfd) // a handler returned TRUE. + continue; + + rmsk = ReadMask; + wmsk = WriteMask; + struct timeval tv; + struct timeval* timeout = 0; + if (TimeOut != -1) { + CcuTimeStamp now; + Millisecond delay = TimeOut - now; + tv.tv_sec = delay / 1000; + tv.tv_usec = 1000 * (delay % 1000); + timeout = &tv; + } + nfd = select (NFILE, FD_SET_TYPE(&rmsk) /*read*/, FD_SET_TYPE(&wmsk) /*write*/, 0 /*except*/, timeout); + if (nfd < 0) { + if (nointr && nfd == -1 && errno == EINTR) + continue; + return nfd; + } + + for (fd = fd0; nfd; (fd < NFILE) ? fd++ : (fd = 0)) { + if (FD_ISSET (fd, &wmsk)) { + nfd--; + Channels [fd] -> HandleWrite (); + if (! Looping) + return 1; + } + if (FD_ISSET (fd, &rmsk)) { + nfd--; + Channels [fd] -> HandleRead (); + if (! Looping) + return 1; + } + } + } + return 1; +} + +// string repr +// +/*?nodoc?*/ +char* +UchMultiplexer :: StrRepr (char* buf) +{ + sprintf (buf, "R:%ux W:%ux", ReadMask, WriteMask); + return buf; +} + +#ifdef DOC +// fake entries for inline functions + +/*? +This array operator returns a (smart) pointer to the channel corresponding to file descriptor \var{fd}. +If there is no such channel in this channel set, the operator returns NIL. +?*/ +pUchChannel +UchMultiplexer :: operator [] (int fd) +{ } + +/*? +Add a channel to the set. +Here the argument is a reference, and a dynamically allocated copy of it is actually added. +Compare with previous function. +?*/ +void +UchMultiplexer :: Add (const UchChannel& ch) +{ } + +/*? +This function makes \fun{LoopScan} exit immediately. +Thus it must be called from within a channel's handler. +?*/ +void +UchMultiplexer :: LoopEnd () +{ } + +#endif /* DOC */ + -- cgit v1.1