paradigm_3/html/
/*! \file server.cpp
  This is the Server class implementation.
  It implements a standard non-blocking select() server.

  \author Jon A. Lambert
  \date 05/02/2003
  \version 0.30
 */
#include "sysconfig.h"
#include "server.h"
#include "event.h"

/*!
  Constructor for Server
 */
Server::Server(EventQueue& r_inque, EventQueue& r_outque, Log& r_lgfile)
  : mShutdown(false), mrLog(r_lgfile),
    mrInQueue(r_inque), mrOutQueue(r_outque) {
}

/*!
  Destructor for Server

  \note
  If we shutdown normally we should not have any connections in our list
  but we will attempt to delete them anyways.  Maybe we should assert this.
 */
Server::~Server() {
  ConnList::iterator i;
  for (i = mConns.begin(); i != mConns.end(); i++) {
    delete (*i);
  }
}

/*!
  Boot initializes the Server and gets it ready to accept incoming
  Connections.

  \return true if server boots correctly, false if an error occurs.
 */
bool Server::Boot(unsigned short port) {
  // Get a TCP protocol socket for the server to listen on.
  mAcceptor = socket (AF_INET, SOCK_STREAM, 0);
  if (mAcceptor == INVALID_SOCKET) {
    ServerLog().Write("ERROR-Server(socket): %d", WSAGetLastError());
    return false;
  }

  // Set the listening socket to be non-blocking
  if (!SetNonBlocking(mAcceptor))
    return false;

  // Set up listening socket to allow local address reuse
  int sockopt_val = 1;         // Non-zero means turn on for boolean options,
                               //  some options require other types and values.
  if (setsockopt(mAcceptor, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt_val,
                 sizeof(sockopt_val)) == SOCKET_ERROR) {
    ServerLog().Write("ERROR-Server(setsockopt): %d",WSAGetLastError());
    closesocket(mAcceptor);
    return false;
  }

  // Associate our local address with this socket
  mTo.sin_family = AF_INET;    // protocol - internet
  mTo.sin_addr.s_addr = INADDR_ANY; // accept on whatever IP we're set up as
  mTo.sin_port = htons(port);   // port must be in network byte order
  if (bind(mAcceptor, (struct sockaddr *)&mTo, sizeof(mTo)) == SOCKET_ERROR) {
    ServerLog().Write("ERROR-Server(bind): %d",WSAGetLastError());
    closesocket(mAcceptor);
    return false;
  }
  // Let's start listening for incoming connections
  if (listen(mAcceptor, 5) == SOCKET_ERROR) { // Connection backlog of 5.
    ServerLog().Write("ERROR-Server(listen): %d",WSAGetLastError());
    closesocket(mAcceptor);
    return false;
  }
  /*
    mNumFds is calculated for the select() function - Windows ignores
    it as the underlying FD sets are constructed differently than Unixes
    On Unixes FDs are include all files, pipes and sockets open.
    We need to calculate the highest FD number for the Unix select.

    In bootup the highest FD is the listening socket so...
  */
  mNumFds = mAcceptor;
  return true;
}

/*!
  Run starts the Server running to process incoming connection, input and
  output requests.  It also executes commands from input requests.

  /param parms Not used.

  /note
  This should be where we begin threading.  Not in Boot.

  /todo
  Polling time should runtime reconfigurable.
 */
#pragma argsused
void Server::Run(void * parms) {
  ServerLog().Write("INFO-StartServer(): Server thread entered.");
  struct timeval timeout = { 0, 1 };  // Will poll for 1 millisecond

  while (!mShutdown) {  // Loops until a SHUTDOWN_E message is received.
    ConnList::iterator i, curr;

    // Reset our socket interest set
    InitFDs();

    // Poll our socket interest set for about a millisec.
    if (select(mNumFds + 1, &mInputFDs, &mOutputFDs, NULL, &timeout) == SOCKET_ERROR) {
      ServerLog().Write("ERROR-Server(select): %d",WSAGetLastError());
      break;
    }

    // If we have incoming connections on our listening port handle them.
    if (FD_ISSET(mAcceptor, &mInputFDs)) {
      AcceptConnection();
    }

    // If we have incoming data on any connections handle it.
    for(i = mConns.begin(); i != mConns.end(); i++) {
      if (FD_ISSET((*i)->GetSocket(), &mInputFDs)) {
        (*i)->HandleInput();

        // Loop through fully formed messages (CRLF).
        string* msg = (*i)->ReadMsg();
        while(msg) {
          // notify the chat driver.
          mrOutQueue.Push(new Event(MESSAGE_E,
            (*i)->GetSocket(), msg->length(), msg->c_str()));
          delete msg; // ReadMsg creates a new string - we must delete it.
          msg = (*i)->ReadMsg();
        } // while
      } // if
    } // for

    // Process the event queue.
    ProcessQueue();

    // If we have outgoing data ready on any connections handle it.
    for(i = mConns.begin(); i != mConns.end(); i++) {
      if (FD_ISSET((*i)->GetSocket(), &mOutputFDs))
        (*i)->HandleOutput();
    }

    // Cleanup sockets that have been scheduled for disconnection.
    for(i = mConns.begin(); i != mConns.end();) {
      curr = i++;
      if ((*curr)->CanBeDisconnected()) {
        (*curr)->Disconnect();
        // Tell the chat driver about it.
        mrOutQueue.Push(new Event(DISCONNECT_E, (*curr)->GetSocket(),
          0, NULL));
        delete (*curr);
        mConns.erase(curr);
      }
    }
  } // while
  ShutdownServer();
  ServerLog().Write("INFO-StartServer(): Server thread exited.");
}

/*!
  AcceptConnection handles an incoming connection request on the server's
  listening port by creating a Connection object for the socket.

  \remarks
  Dang this code looks dense.
 */
void Server::AcceptConnection() {
  struct sockaddr_in addr; // An address structure for our incoming connection.
  int addrlen = sizeof(addr);

  // Try to accept the connection
  SOCKET psock = accept(mAcceptor, (struct sockaddr *)&addr, &addrlen);
  if (psock != INVALID_SOCKET) {
    // Set the socket to non-blocking.
    if (SetNonBlocking(psock)) {
      // The socket set up correctly.  Let us add a new Connection.
      Connection *conn = new Connection(psock, this);
      ServerLog().Write("INFO-Server(accept): Connection %d accepted", psock);
      mConns.push_back(conn);  // Put it on the connection list.
      // Tell the chat server we have a new connection
      mrOutQueue.Push(new Event(CONNECT_E, conn->GetSocket(), 0, NULL));
    }
  } else {
    // Weve encountered an error during accept.
    ServerLog().Write("ERROR-ServerAccept(accept): %d", WSAGetLastError());
  }
}

/*!
  InitFDs initializes the server's select queue and then re-registers
  interest in all the connection's sockets in the server's user list
  including the server's listening port itself.

  \note
  It may well be more efficient to register or unregister interest
  on a connection by connection basis in the various connection
  handling routines.

  \remarks
  [1] On Windows, setting interest in all connected sockets for output
  will cause our select loop to spin.  So we only set it if we know there
  is output waiting.
 */
void Server::InitFDs () {
  FD_ZERO (&mInputFDs);           // Clears our interest sets out.
  FD_ZERO (&mOutputFDs);

  FD_SET (mAcceptor, &mInputFDs); // Register interest in listening socket.
  for(ConnList::iterator i = mConns.begin(); i != mConns.end(); i++) {
    /*
      mNumFds is calculated for the select() function - Windows ignores
      it as the underlying FD sets are constructed differently than Unixes
      On Unixes FDs are include all files, pipes and sockets open.
      We need to calculate the highest FD number for the Unix select.
    */
    mNumFds = max(mNumFds, (*i)->GetSocket());
    FD_SET((*i)->GetSocket(), &mInputFDs);  // Always interested in input.
    if ((*i)->HasOutput())        // See [1] above.
      FD_SET((*i)->GetSocket(), &mOutputFDs);
  }
}

/*!
  ProcessQueue attempts to process all the events contained in the eventqueue
  coming from our chat engine.

  \remarks
  The are only two kinds of messages.
    - @shutdown - requests the server to shutdown
    - everything else is interpreted as public conversation

  \todo
  Implement a disconnect request coming from driver.  Allow specialized
  Telnet commands to come from driver, and filter them through the
  connection depending on whether it is capable.
 */
void Server::ProcessQueue() {
  // manage event queue
  Event* e;
  while ((e = mrInQueue.Pop()) != NULL) {
    switch (e->mEventType) {
    case DISCONNECT_E:
      // We've been told by the chat driver to kill a connection.
      {
        // Not yet implemented.
      }
      break;
    case CONNECT_E:
      // Should never happen - included for completeness
      break;
    case MESSAGE_E:
      // A message from the chat driver to route to a connection here.
      {
        // Test for null message data and ignore it
        if (e->mpData && e->mDataLen) {
          string msg;
          msg.append(e->mpData, e->mDataLen);
          for(ConnList::iterator c = mConns.begin(); c != mConns.end(); c++) {
            if ((*c)->GetSocket() == e->mClientId) {
              (*c)->SendMsg(msg);
              // Register interest as we have data ready on the wire
              FD_SET ((*c)->GetSocket(), &mOutputFDs);
            }
          }
        }
      }
      break;
    case SHUTDOWN_E:
      // We've been requested to shutdown so let's comply.
      mShutdown = true;
      break;
    case NONE_E:
      break;
    } // switch
    delete e;
  } // while
}

/*!
  ShutdownServer requests each of the connections to disconnect in the
  server's user list, deletes the connections, and erases them from
  the user list.  It then closes its own listening port.
 */
void Server::ShutdownServer() {
  ServerLog().Write("INFO-Server(shutdown): Server shutting down");
  ConnList::iterator ci, curr;
  for(ci = mConns.begin(); ci != mConns.end();) {
    curr = ci++;// :WARN: The list containers iterator is invalidated on erase.
                // So we need to increment it first and delete the old.
    (*curr)->Disconnect();
    // Tell chat driver about each connection as it leaves
    mrOutQueue.Push(new Event(DISCONNECT_E, (*curr)->GetSocket(),
      0, NULL));
    delete (*curr);
    mConns.erase(curr);
  }
  closesocket(mAcceptor);
  mrOutQueue.Push(new Event(SHUTDOWN_E, 0, 0, 0));
}

/*!
  ServerLog returns a reference to this server's logging utility so that
  messages can be written to it by its associates and aggregates.

  \return a reference to this server's Log
 */
Log& Server::ServerLog() {
  return mrLog;
}

/*!
  ServerLog returns a reference to this server's logging utility so that
  messages can be written to it by its associates and aggregates.

  \param s The socket to be set non-blocking.
  \return true if successful, false if unsuccessful.
 */
bool Server::SetNonBlocking(SOCKET s) {
  unsigned long ioctl_cmd = 1; // Non-zero means turn on for ioctl commands.
  if (ioctlsocket(s, FIONBIO, &ioctl_cmd) == SOCKET_ERROR) {
    ServerLog().Write("ERROR-Server(SetNonBlocking): %d on socket %d",
      WSAGetLastError(), s);
    closesocket(s);
    return false;
  }
  return true;
}