/* * The Unix Channel * * by Michel Beaudouin-Lafon * * Copyright 1990-1993 * Laboratoire de Recherche en Informatique (LRI) * * Text-oriented servers * * $Id$ * $CurLog$ */ #include "TextServer.h" #include "Multiplexer.h" #include "TimeOut.h" #include "error.h" #include "PortServer.h" #include #ifdef __osf__ extern "C" { int accept (int, struct sockaddr*, int*); } #endif /* every REG_TIME milliseconds, the server registers to the port server we want this time to be shorter than the REG_TIME of the port server. half the value of the port server's REG_TIME seems reasonable */ #define REG_TIME 30000 class UchTextServerTimer : public UchBaseTimeOut { protected: UchTextServer* Server; public: UchTextServerTimer (UchTextServer*); ~UchTextServerTimer (); void Handle (Millisecond); }; UchTextServerTimer :: UchTextServerTimer (UchTextServer* s) : UchBaseTimeOut (*s->GetMultiplexer (), REG_TIME), Server (s) { Handle (0); } UchTextServerTimer :: ~UchTextServerTimer () { PortServerRemove ("agent", Server->service, Server->BoundTo ()); } void UchTextServerTimer :: Handle (Millisecond) { PortServerRegister ("agent", Server->service, Server->BoundTo ()); } /*? Create a \var{UchTextServer} registered with the portserver under the name \var{serv}. \var{newcl} is the function that instantiates clients. It takes a pointer to this server as argument, and must return a dynamically allocated object of a derived class of \var{UchTextClient}. In most cases, the function's body is \com{return new MY_CLIENT (s);}, if \typ{MY_CLIENT} is the derived class of \var{UchTextClient} defined by the application and \var{s} is this server. ?*/ UchTextServer :: UchTextServer (const char* serv, fNEW_CLIENT newcl) : UchStream (new UchInetAddress (ANYADDR, 0), 0), Timer (0), ok (false), Clients () { service = serv; newClient = newcl; } /*? Remove the service name from the port server. This destructor does not close down the server. Use \fun{Quit} to terminate the server properly. ?*/ UchTextServer :: ~UchTextServer () { if (Timer) delete Timer; } /*? Initialize the server, register it with the multiplexer, and register its address with the port server. ?*/ bool UchTextServer :: Init (UchBaseMultiplexer& mpx) { if (Listen () < 0) { // calls Setup SysError (ErrWarn, "UchTextServer::Setup"); return false; } mpx.Add (*this); ok = true; char buf [128]; sprintf (buf, "listening on port #%d", (void*) ((UchInetAddress*) BoundTo ())->Port ()); ::Error (ErrLog, "UchTextServer::Init", buf); Timer = new UchTextServerTimer (this); return true; } /*? Terminate the server. First each client is notified that the server is about to quit by calling its \var{Quitting} virtual member and removing it from the multiplexer. This should normally delete the client. Finally, the server itself is removed from the multiplexer, which should delete it in a similar way. ?*/ void UchTextServer :: Quit () { if (! ok) return; ok = false; // a bit tricky because of the smart pointers: // each tstream probably is referenced only through the multiplexer // removing it from the multiplexer hence deletes it. // The same thing holds for the UchTextServer itself // this is why we delete it last. // Finally, this should make the multiplexer loop exit, // if there are no other channels in the multiplexer. #ifndef CPLUS_BUG19 CcuListIterOf ci = Clients; while (++ci) { UchTextClient* c = *ci; #else CcuListIter ci = Clients; while (++ci) { UchTextClient* c = (UchTextClient*) *ci; #endif c->Quitting ();// signal the client we're leaving if (Mpx) Mpx->Remove (*c); } Mpx->Remove (*this); } /*? Remove a client from the client list and unregister it from the multiplexer. This should delete the client, since the multiplexer probably holds the last smart pointer to the client. This protected function must be called by a client that is about to disappear. ?*/ void UchTextServer :: Remove (UchTextClient* s) { if (! ok) return; int found = Clients.Remove (s); if (!found) ::Error (ErrWarn, "UchTextServer::Remove", "stream not in client list"); if (Mpx) Mpx->Remove (*s); } /*? The implementation of \fun{UchChannel::HandleRead}. The server accepts the incoming conncetion and calls \fun{CreateClient} to instantiate the new client. ?*/ void UchTextServer :: HandleRead () { if (! ok) return; // can't use UchChannel::Accept or SOCKET::SockAccept // (should change their interface) int fd = accept (FilDes (), 0, 0); if (fd < 0) { SysError (ErrWarn, "UchTextServer::Accept"); return; } CreateClient (fd); } /*? This protected function creates a new client on file descriptor \var{fd}. The client is instantiated by calling the function registered with the server when it was created (see the constructor). The new client is registered with the multiplexer, it is added to the client list, and its \fun{Starting} virtual member is called so that it can performed its initialization. ?*/ UchTextClient* UchTextServer :: CreateClient (int fd) { UchTextClient* cl = (*newClient) (this); if (! cl) return 0; cl->UchChannel::Open (fd); cl->SetMode (IORead); if (Mpx) Mpx->Add (*cl); // link client list Clients.Append (cl); // tell the client that it is running cl->Starting (); return cl; } //---------------- UchTextClient /*? The constructor for a new client of server \var{s}. ?*/ UchTextClient :: UchTextClient (UchTextServer* s) : UchTextStream (), MyServer (s) { sendToOut = false; out = 0; } /*? The destructor of a client does nothing. It is protected. Clients are deleted automatically when nobody references them. Normally, calling \fun{Close} should delete a client. ?*/ UchTextClient :: ~UchTextClient () { } /*? This protected virtual function is called by the server when the client has been instantiated and successfully registered. By default it does nothing. ?*/ void UchTextClient :: Starting () { // nothing } /*? This protected virtual function is called by \fun{HandleRead} when an end-of-file is read (the argument is then true) or when an error occured while reading (the argument is then false). It calls the server's \fun{Remove} function for this client, which should result in the destruction of the client. This virtual function can be redefined in derived classes, but the redefinition should call the default implementation. ?*/ void UchTextClient :: Closing (bool) { MyServer->Remove (this); } /*? This protected virtual function is called by the server when it is closing down. The default implementation does nothing. ?*/ void UchTextClient :: Quitting () { // nothing } /*? This is the implementation of the virtual function \fun{UchTextStream::DoSend}. If the output of the client has been redirected with \var{SetOutput}, the output buffer is written to this output, or is discarded if no output channel is defined. If the output has not been redirected, the output buffer is written on the client's channel. ?*/ void UchTextClient :: DoSend () { if (sendToOut) { if (out) out->Write (OutBuffer); else OutBuffer.Flush (); } else Write (OutBuffer); } /*? This is the implementation of the virtual function \fun{UchTextStream::Quit}, which is called when a \com{Quit} request is received. It calls the server's \fun{Quit} function. ?*/ void UchTextClient :: Quit () { if (MyServer) MyServer->Quit (); } /*? This is the implementation of the virtual function \fun{UchTextStream::Close}, which is called when a \com{Close} request is received. It calls the server's \fun{Remove} function for this client. ?*/ void UchTextClient :: Close () { if (MyServer) MyServer->Remove (this); } /*? Redirect the output to channel \var{ch}. If \var{ch} is 0, output is simply discarded. ?*/ void UchTextClient :: SetOutput (UchChannel* ch) { sendToOut = true; out = ch; } /*? Reset the output to the client's channel. ?*/ void UchTextClient :: ResetOutput () { out = 0; sendToOut = false; }