WindowMud

Communication.cpp Source Code

Download this source code in text format (Communication.cpp)

/***********************************************************
* WindowMud - Window Mud Server                            *
* File:   Communication.cpp                                *
* Usage:  Winsock tcp/ip telnet player communications      *
*                                                          *
* No warranty is given or implied.                         *
* There is no license associated with this code.           *
* There are no restrictions on the use of this code.       *
* This code may be used in any way the reader wishes.      *
* No credit or credits are given and none are required.    *
* The reader may take this code and claim they wrote it.   *
************************************************************/

/***********************************************************
* Includes                                                 *
************************************************************/

#include "stdafx.h"         // precompiled headers
#include "Communication.h"
// Server
#include "Dnode.h"
#include "Descriptor.h"
#include "Log.h"
#include "Utility.h"

/***********************************************************
* Globals                                                  *
************************************************************/

extern CString   ErrorMsg;
extern bool      StateConnections;
extern bool      StateRunning;
extern bool      StateStopping;
extern bool      WinSockCleanUp;

Dnode           *pDnodeActor;
fd_set           ExcSet;
fd_set           InpSet;
int              ListenSocket;
fd_set           OutSet;

/***********************************************************
 * Communication class constructor                         *
 ***********************************************************/

Communication::Communication()
{
}

/***********************************************************
 * Communication class destructor                          *
 ***********************************************************/

Communication::~Communication()
{
}

////////////////////////////////////////////////////////////
// Public functions static                                //
////////////////////////////////////////////////////////////

/***********************************************************
 * Check for new connections                               *
 ***********************************************************/

void Communication::SockCheckForNewConnections()
{
  Dnode                *pDnodeActor;
  int                   SocketCount;
  static struct timeval TimeOut;

  TimeOut.tv_sec = 0;
  TimeOut.tv_usec = 1;
  FD_ZERO(&InpSet);
  FD_ZERO(&OutSet);
  FD_ZERO(&ExcSet);
  FD_SET(ListenSocket, &InpSet);
  // Check status of connections
  Descriptor::SetpDnodeCursorFirst();
  while (!Descriptor::EndOfDnodeList())
  { // Loop thru all connections
    pDnodeActor = Descriptor::GetDnode();
    FD_SET(pDnodeActor->DnodeFd, &InpSet);
    FD_SET(pDnodeActor->DnodeFd, &OutSet);
    FD_SET(pDnodeActor->DnodeFd, &ExcSet);
    Descriptor::SetpDnodeCursorNext();
  }
  // Detect socket state (pg 159-161)
  SocketCount = select(-1, &InpSet, &OutSet, &ExcSet, &TimeOut);
  if (SocketCount == -1)
  { // Select failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication::SockCheckForNewConnections: select: " + ErrorMsg;
    Utility::BlowUp();
  }
  if (FD_ISSET(ListenSocket, &InpSet))
  { // Process new connection
    SockNewConnection();
  }
}

/***********************************************************
 * Close port                                              *
 ***********************************************************/

void Communication::SockClosePort(int Port)
{ 
  CString LogBuf;
  int     Result;

  // Close the socket (pg 70)
  Result = ::closesocket(ListenSocket);
  if (Result!= 0)
  { // Sock error on close
    ErrorMsg = "Communication::SockClosePort - Error: closesocket";
    Utility::BlowUp();
  }
  LogBuf.Format("%d", Port);
  LogBuf = "Closed port " + LogBuf;
  Log::LogIt(LogBuf);
  // Free WinSock resources
  WinSockCleanUp = false;
  Result = WSACleanup();
  if (Result!= 0)
  { // WinSock cleanup failed
    ErrorMsg = "Communication::SockClosePort - Error: WSACleanup";
    Utility::BlowUp();
  }
}

/***********************************************************
 * Open port                                               *
 ***********************************************************/

void Communication::SockOpenPort(int Port)
{
  unsigned long FionbioParm;
  struct        linger      ld;
  CString       LogBuf;
  int           OptionValue;
  int           Result;
  struct        sockaddr_in sa;
  WORD          VersionRequested;
  WSADATA       WsaData;

  FionbioParm       = 1;
  ld.l_onoff        = 0;
  ld.l_linger       = 0;
  OptionValue       = 1;
  VersionRequested  = MAKEWORD(1, 1);
  // Initialize WinSock API (pg 320)
  Result = WSAStartup(VersionRequested, &WsaData);
  if (Result != 0)
  { // WinSock is not available
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication::SockOpenPort - WinSock not available: " + ErrorMsg;
    Utility::BlowUp();
  }
  WinSockCleanUp = true;
  // Establish a streaming socket (pg 51)
  ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
  if (ListenSocket == SOCKET_ERROR)
  { // No socket, time to die
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: initializing socket: " + ErrorMsg;
    Utility::BlowUp();
  }
  // Enable reuse of local socket name (pg 305)
  Result = setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&OptionValue, sizeof(OptionValue));
  if (Result != 0)
  { // Set socket option failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: setsockopt: SOL_SOCKET SO_REUSEADDR" + ErrorMsg;
    Utility::BlowUp();
  }
  // Establish underlying TCP/IP buffer size (pg 305)
  Result = setsockopt(ListenSocket, SOL_SOCKET, SO_SNDBUF, (char *) &OptionValue, sizeof(OptionValue));
  if (Result != 0)
  { // Set socket option failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: setsockopt SNDBUF: " + ErrorMsg;
    Utility::BlowUp();
  }
  // Disable linger and set timeout to zero (pg 301)
  Result = setsockopt(ListenSocket, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld));
  if (Result != 0)
  { // Set socket option failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: setsockopt SO_LINGER: " + ErrorMsg;
    Utility::BlowUp();
  }
  // Initialize sockaddr structure (pg 53)
  memset((char *)&sa, 0, sizeof(sa));
  sa.sin_family       = AF_INET;
  sa.sin_addr.s_addr  = INADDR_ANY;
  sa.sin_port         = htons(Port);
  // Associate a local address with the socket (pg 56)
  Result = bind(ListenSocket, (struct sockaddr *)&sa, sizeof(sa));
  if (Result != 0)
  { // Bind socket failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: bind " + ErrorMsg;
    Utility::BlowUp();
  }
  // Make socket nonblocking (pg 286)
  Result = ioctlsocket(ListenSocket, FIONBIO, &FionbioParm);
  if (Result != 0)
  { // Failed to make socket nonblocking
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort ioctlsocket: " + ErrorMsg;
    Utility::BlowUp();
  }
  // Listen on port and limit pending connections (pg 60)
  Result = listen(ListenSocket, 20);
  if (Result != 0)
  { // Listen failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication:SockOpenPort - Error: listen " + ErrorMsg;
    Utility::BlowUp();
  }
  LogBuf.Format("%d", Port);
  LogBuf = "Listening on port " + LogBuf;
  Log::LogIt(LogBuf);
}

/***********************************************************
 * Receive player input, check player status, send output  *
 ***********************************************************/

void Communication::SockRecv()
{
  int      ConnectionCount;
  int      DnodeFdSave;
  char     InpStr[MAX_INPUT_LENGTH];
  CString  LogBuf;
  int      RecvByteCount;

  //************************
  //* Is Game is stopping? *
  //************************
  if (StateStopping)
  { // Game is stopping
    Descriptor::SetpDnodeCursorFirst();
    while (!Descriptor::EndOfDnodeList())
    { // Loop thru all connections
      pDnodeActor = Descriptor::GetDnode();
      if (!pDnodeActor->PlayerStateBye)
      { // If player is not already going Bye Bye
        pDnodeActor->PlayerStateBye = true;
        pDnodeActor->PlayerStateSendBanner = false;
        pDnodeActor->PlayerOut += "\r\n";
        pDnodeActor->PlayerOut += "Game is stopping ... Bye Bye!";
        pDnodeActor->PlayerOut += "\r\n";
        LogBuf  = "Unknown";
        LogBuf += " will be force disconnected";
        Log::LogIt(LogBuf);
      }
      Descriptor::SetpDnodeCursorNext();
    }
  }
  //***********************
  //* Service connections *
  //***********************
  Descriptor::SetpDnodeCursorFirst();
  while (!Descriptor::EndOfDnodeList())
  { // Loop thru all connections
    pDnodeActor = Descriptor::GetDnode();
    //***************************
    //* Check connection status *
    //***************************
    if (FD_ISSET(pDnodeActor->DnodeFd, &OutSet))
    { // This code don't do nothin', sample OutSet check
      int dummy = 0;
    }
    if (FD_ISSET(pDnodeActor->DnodeFd, &ExcSet))
    { // Kick out connections with exceptions
      pDnodeActor->PlayerStateBye = true;
    }
    else
    { // Good connection
      if (FD_ISSET(pDnodeActor->DnodeFd, &InpSet))
      { // Receive
        memset(InpStr, '\0', sizeof(InpStr));
        RecvByteCount = ::recv(pDnodeActor->DnodeFd, InpStr, MAX_INPUT_LENGTH-1, 0);
        if (RecvByteCount == 0)
        { // Should be input but there is none -- disconnected ??
          pDnodeActor->PlayerStateBye = true;
        }
        if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR)
        { // Nothing worth processing
         RecvByteCount = 0;
        }
        if (RecvByteCount > 0)
        { // Got something ... append it to player input
          pDnodeActor->PlayerInp += InpStr;
        }
      }
    }
    //*****************************
    //* Banner for new connection * 
    //*****************************
    if (pDnodeActor->PlayerStateSendBanner)
    { // New connection
      pDnodeActor->PlayerStateSendBanner = false;
      // Send greeting  
      pDnodeActor->PlayerOut += "\r\n";
      pDnodeActor->PlayerOut += "Create a new character Y-N?";
      pDnodeActor->PlayerOut += "\r\n";
    }
    //**********************
    //* Send player output *
    //**********************
    if (pDnodeActor->PlayerOut.GetLength() > 0)
    { // 'send' resquires a standard c string
      SockSend((LPCTSTR) pDnodeActor->PlayerOut);
    }
    //**********************
    //* Is player quiting? *
    //**********************
    if (pDnodeActor->PlayerStateBye)
    { // Player leaving the game ... disconnect them
      DnodeFdSave = pDnodeActor->DnodeFd;
      if (Descriptor::DeleteNode())
      { // When connection is deleted from list, log it
        LogBuf.Format("%d", DnodeFdSave);
        LogBuf = "Closed connection on descriptor " + LogBuf;
        Log::LogIt(LogBuf);
        ConnectionCount = Dnode::GetCount();
        if (ConnectionCount == 1)
        { // Connection count is one means no players are connected
          if (StateStopping)
          { // OMugs is stopping
            StateRunning = false;
          }
        }
      }
      // Skip to next dnode, this player's dnode has been deleted
      Descriptor::SetpDnodeCursorNext();
      continue;
    }
    // Get the next Dnode to process
    Descriptor::SetpDnodeCursorNext();
  }
}

////////////////////////////////////////////////////////////
// Private functions static                               //
////////////////////////////////////////////////////////////

/***********************************************************
 * New connection                                          *
 ***********************************************************/

void Communication::SockNewConnection()
{
  unsigned long       FionbioParm;
  CString             LogBuf;
  int                 Result;
  struct sockaddr_in  Sock;
  int                 SocketHandle;
  int                 SocketSize;
  CString             IpAddress;
  CString             TmpStr;
    
  FionbioParm = 1;
  SocketSize  = sizeof(Sock);
  // Return a new socket for a newly created connection (pg 63)
  SocketHandle = accept(ListenSocket, (struct sockaddr *)&Sock, &SocketSize);
  if (!SocketHandle)
  { // Accept failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication::SockNewConnection - Error: accept: " + ErrorMsg;
    Utility::BlowUp();
  }
  IpAddress = inet_ntoa(Sock.sin_addr);
  // Make socket nonblocking (pg 286)
  Result = ioctlsocket(SocketHandle, FIONBIO, &FionbioParm);
  if (Result != 0)
  { // Failed to make socket nonblocking
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication::SockNewConnection - Error: ioctlsocket " + ErrorMsg;
    Utility::BlowUp();
  }
  TmpStr.Format("%d", SocketHandle);
  LogBuf  = "New connection with socket handle ";
  LogBuf += TmpStr;
  LogBuf += " and address ";
  LogBuf += IpAddress;
  Log::LogIt(LogBuf);
  pDnodeActor = new Dnode(SocketHandle, IpAddress);
  Descriptor::AppendIt();
  StateConnections = true;
}

/***********************************************************
 * Send message                                            *
 ***********************************************************/

void Communication::SockSend(const char *SendBuffer)
{
  int Length;
  int Written;

  if (!SendBuffer || SendBuffer[0] == '\0')
  { // Nothing to send
    return;
  }
  Length = strlen(SendBuffer);
  Written = ::send(pDnodeActor->DnodeFd, SendBuffer, Length, 0);
  if (Written == SOCKET_ERROR)
  { // Send failed
    ErrorMsg.Format("%s", strerror(errno));
    ErrorMsg = "Communication::SockSend - Error: send: " + ErrorMsg;
    Utility::BlowUp();
  } 
  if (Written == Length)
  { // Everything was sent
    pDnodeActor->PlayerOut = "";
  }
  else
  { // Some was not sent
    pDnodeActor->PlayerOut = pDnodeActor->PlayerOut.Right(Length-Written);
  }
}