#include <stdio.h>
#include <winsock2.h>
 
#include "ServerWS.hxx"
#include "Chat.hxx"
 
 
 
// Public Functions //////////////////////////////////////////////////////
 
 
// Constructor ///////////////////////////////////////////////////////////
//
// Description:  Sets up the class for usage for either connecting to
//               or hosting a game.  Defaults are filled in.
//////////////////////////////////////////////////////////////////////////
 
ServerWS::ServerWS()
{
  // Must be done at the beginning of every WinSock program
 
  // Used to store information about WinSock version
  WSADATA w;
  
  int error = WSAStartup(0x0202, &w);   // Fill in w
 
  if (error)
    {
     // There was an error; don't have to call WSACleanup because Winsock
     // wasn't initialized.  Since isConnected = false, we prevent problems.
     return;
    }
  else if (w.wVersion != 0x0202)
    { 
     // Wrong WinSock version!
     WSACleanup();
     return;
    }
 
  players = 1;   // Ourselves
  isHost = false;
  isConnected = false;
  myIndex = 0;
 
  // Allocate message buf
  theMesg = (WS_STRING_MSG_PTR)GlobalAllocPtr(GHND, MAX_MESGSIZE);
 
  // Set all players disconnected, all names NULL
  ZeroMemory(player_list, sizeof(player_list));
  
 
}
 
// Destructor ////////////////////////////////////////////////////////////
//
// Description:  Calls Disconnect() and quits.
//////////////////////////////////////////////////////////////////////////
 
ServerWS::~ServerWS()
{
  Disconnect();
 
  // Free our global message buffer
  GlobalFreePtr(theMesg);
}
 
 
// CheckIP() /////////////////////////////////////////////////////////////
//
// Description:  Checks to make sure the given IP string is valid.  
//               Returns false on error, and copies the error string
//               into reason.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::CheckIP(char *ip, char *reason) const
{
  int ret, one, two, three, four;
 
  // Check for NULL
  if (ip == NULL)
     return false;
 
  // Check for numbers and .'s only
  for (unsigned int i = 0; i < strlen(ip); i++)
    {
     if (!isdigit(ip[i]) && ip[i] != '.')
       {
        strcpy(reason, "Error: Bad char.");
        return false;
       }
    }
 
  ret = sscanf(ip, "%d.%d.%d.%d", &one, &two, &three, &four);
 
  // Check that four numbers were scanned
  if (ret != 4)
    {
     strcpy(reason, "Error: Bad format.");
     return false;
    }
 
  // Check numeric bounds
  if (one < 0 || two < 0 || three < 0 || four < 0)
    {
     strcpy(reason, "Error: Negative number.");
     return false;
    }
 
  if (one > 255 || two > 255 || three > 255 || four > 255)
    {
     strcpy(reason, "Error: Number too big.");
     return false;
    }
  
  // Passes all checks
  return true;
}
 
 
// Connect() /////////////////////////////////////////////////////////////
//
// Description:  Given an IP address and a port, tries to connect to the
//               game server and sets up asynchronous communication on
//               the new socket.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::Connect(HWND hwnd, UINT msg, char *ip, int port, char *play_name)
{
  int ret;
 
  // Create the socket
  sock = socket(AF_INET, SOCK_STREAM, 0);
 
  if (sock == INVALID_SOCKET)
    {
     strcpy(lasterr, "Could not create socket.");
     return false;
    }
 
  // Set up asynchronous recepit of read/close messages at this socket...
  if (WSAAsyncSelect(sock, hwnd, msg, (FD_READ | FD_CLOSE | FD_CONNECT)))
    {
     // Error occurred
     sprintf(lasterr, "WSAAsyncSelect() failed; error code %d", WSAGetLastError());
     return false;
    }
 
  // Connect to the host
  sockaddr_in target;
 
  target.sin_family = AF_INET;      
  target.sin_port = htons(port);
  target.sin_addr.s_addr = inet_addr(ip);
 
  while (true)
    { 
     if (connect(sock, (sockaddr*)&target, sizeof(target)) == SOCKET_ERROR)
       {
        ret = WSAGetLastError();
 
        if (ret == WSAEWOULDBLOCK)
           continue;
        else if (ret == WSAEISCONN)
           break;
 
        // Actual error
        sprintf(lasterr, "Connect() failed; Error code (%d)", GetLastError());
        return false;
       }
     else
        break;
    }
 
  isConnected = true;
  isHost = false;
 
  // Set our name
  strcpy(player_name, play_name);
 
  Sleep(20);
  SendNameMesg(0, 1, player_name);
 
  return true;
}
 
 
// Disconnect() //////////////////////////////////////////////////////////
//
// Description:  Simply shuts down our socket, closes it, and sets
//               the "connected" attribute to false.
//////////////////////////////////////////////////////////////////////////
 
void ServerWS::Disconnect()
{
  // Shutdown socket, close socket, disconnect
  shutdown(sock, SD_BOTH);
  closesocket(sock);   
 
  isConnected = false;
}
 
 
// GetIndex() ////////////////////////////////////////////////////////////
//
// Description:  Looks through the player list and returns the index of
//               your own computer.
//////////////////////////////////////////////////////////////////////////
 
int ServerWS::GetIndex() const
{
  return myIndex;
}
 
// GetPlayerName() ///////////////////////////////////////////////////////
//
// Description:  Copies the correct player's name into the given string.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::GetPlayerName(char *name, int index)
{
   if (index >= 0 && index < players)
     {
      strcpy(name, player_list[index].name);
      return true;
     }
   else
     {
      strcpy(lasterr, "GetPlayerName(): Index Invalid");
      return false;
     }
}
 
 
// HandleEvent() /////////////////////////////////////////////////////////
//
// Description:  The application must call this when it receives a 
//               socket message with the appropriate hwnd, msg, wparam,
//               and lparam.  Also, mesg & type is used when a FD_READ
//               event has occurred--otherwise they are not changed.
//////////////////////////////////////////////////////////////////////////
 
void ServerWS::HandleEvent(HWND hwnd, UINT msg, WPARAM socket, LPARAM event, 
                           char *mesg, int& type, int& from)
{
 
  if (isHost)
    {
     for (int i = 0; i < players; i++)
        if (player_list[i].sock == socket)
           from = i;
    }
  else
    from = 0;
 
 
  if (WSAGETSELECTERROR(event))
    {
     // Ignore the error
     return;
    }
 
  switch (WSAGETSELECTEVENT(event))
    {
     case FD_CONNECT:
       {
        if (myIndex == 0)
          {
           SendNameMesg(0, 1, player_name);
           Sleep(200);
           PostMessage(hwnd, msg, socket, event);
          }
        else
          {
           for (int i = 0; i < myIndex; i++)
             {
              if (player_list[i].connected == false)
                {
                 //SendNameMesg(0, 1, player_name);
                 break;
                }
             }
          }
        break;
       }
     case FD_ACCEPT:
       {
        // Only let maxplayers connect.
        if (players >= max_players)
           return;
 
        // Size of sockaddr structure
        int addr_size = sizeof(sockaddr_in);
 
         // Get the next client
        player_list[players].sock = accept(socket, (sockaddr*)&player_list[players].info, &addr_size); 
 
        // Error handling
        if (player_list[players].sock != INVALID_SOCKET)
          { 
           // Client connected successfully
           player_list[players].connected = true;
           player_list[players].name[0] = 0;
 
           // Increase player count
           players++;
 
           // Send this client WS_MSG_NAME messages for players they missed
           // BEFORE they send one to us.
           for (int i = 0; i < players-1; i++)
             {
              SendNameMesg(players-1, i, player_list[i].name);
              Sleep(300);
             }
 
           // Set up Asynchronous receipt of messages on new socket
           WSAAsyncSelect(player_list[players-1].sock, hwnd, msg, (FD_READ | FD_CLOSE));
          }
 
        break;
       }
     case FD_READ:
       {
        ReceiveMesg(socket, mesg, type, from);
        break;
       }       
     case FD_CLOSE:
       {
        if (isHost)
          {
           // Set the appropriate player's socket to INVALID_SOCKET
 
           for (int i = 0; i < players; i++)
             {
              if (player_list[i].sock == socket)
                {
                 player_list[i].sock = INVALID_SOCKET;
                 player_list[i].connected = false;
                 from = i;
                }
             }
 
           // Loop again and send a DROP message to other clients
           for (i = 0; i < players; i++)
             {
              if (i != from)
                 SendDropMesg(from, i);
             }
 
           // Any other handling must be done in main application code
          }      
        else
           isConnected = false;
        break;
       }
     default: break;
    }
 
}
 
 
// HostGame() ////////////////////////////////////////////////////////////
//
// Description:  Called by the main window handle to start hosting a
//               networked game.  msg is used to specify the windows
//               message to send to hwnd when an asynchronous event occurs
//               on a socket.  Also sets up a max_players limit and the
//               player's name.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::HostGame(HWND hwnd, UINT msg, int port, char *play_name, int maxplayers)
{
  max_players = maxplayers;
  myIndex = 0;
 
  // Create socket
  sock = socket(AF_INET, SOCK_STREAM, 0);
 
  if (sock == INVALID_SOCKET)
    {
     strcpy(lasterr, "Could not create socket.");
     return false;
    }
 
  if (WSAAsyncSelect(sock, hwnd, msg, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
    {
     sprintf(lasterr, "Async error (%d)", WSAGetLastError());
     return false;
    }
 
 
  // The address structure for a TCP socket
  sockaddr_in addr; 
 
  addr.sin_family = AF_INET;     
  addr.sin_port = htons(port);   
  addr.sin_addr.s_addr = htonl(INADDR_ANY);   // No destination
 
 
  // Now bind the socket
  if (bind(sock, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
    { 
     // Error in bind
     strcpy(lasterr, "Error binding port.");
     return false;
    }
 
  isConnected = true;
  isHost = true;
 
 
  // Set up player_list[0] to be ourselves
 
  strncpy(player_list[0].name, play_name, NAMELEN);
  player_list[0].name[NAMELEN-1] = 0;
  player_list[0].sock = sock;
  player_list[0].info = addr;
  player_list[0].connected = true;
 
  strncpy(player_name, play_name, NAMELEN);
  player_name[NAMELEN-1] = 0;
 
  // Listen to port!
 
  if (listen(sock, max_players))
    {
     // An error occurred; set lasterr
     sprintf(lasterr, "listen() failed; Winsock Error %d", WSAGetLastError());
     isConnected = false;
     return false;
    }
 
  return true;
}
 
 
 
// IsConnected() /////////////////////////////////////////////////////////
//
// Description:  Returns the current value of isConnected.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::IsConnected() const
{
  return isConnected;
}
 
// IsConnected() /////////////////////////////////////////////////////////
//
// Description:  Checks the player list to see if a certain client is
//               connected or not.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::IsConnected(int index) const
{
  if (index >= 0 && index < players)
     return player_list[index].connected;
  else
     return false;
}
 
 
// IsHost() //////////////////////////////////////////////////////////////
//
// Description:  Returns the current value of isHost.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::IsHost() const
{
  return isHost;
}
 
 
// LastError() ///////////////////////////////////////////////////////////
//
// Description:  Copies the last error message set to the string specified.
//////////////////////////////////////////////////////////////////////////
 
void ServerWS::LastError(char *err) const
{
  strcpy(err, lasterr);
}
 
 
// NumPlayers() //////////////////////////////////////////////////////////
//
// Description:  Returns the number of players currently in the server
//////////////////////////////////////////////////////////////////////////
 
int ServerWS::NumPlayers() const
{ 
  return players;
}
 
 
// SendChatMesg() ////////////////////////////////////////////////////////
//
// Description:  Given a destination socket (on the host), a string, and
//               a everyone flag, will send a chat message to the server.
//               If everyone flag is set, then the server will relay it
//               to all.  Otherwise the server must relay to the right 
//               party in ReceiveMesg.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::SendChatMesg(int indexTo, LPSTR lpstr, bool everyone)
{
  // Return code
  bool ret = true;
 
  // Send a pointer to a WS_STRING_MSG
  WS_STRING_MSG_PTR mesg;
  DWORD dwMsgSize;
 
  // Get message size
  dwMsgSize = sizeof(WS_STRING_MSG)+lstrlen(lpstr);
 
  // Allocate space
  mesg = (WS_STRING_MSG_PTR)GlobalAllocPtr(GHND, dwMsgSize);
 
  // Copy the actual message
  lstrcpy(mesg->szMsg, lpstr);
 
  // Set the source location & type
  mesg->dwType = WS_MSG_CHATSTRING;
  mesg->dwSize = dwMsgSize;
  mesg->bcast = everyone;
  mesg->src = GetIndex();
  mesg->dest = indexTo;
 
  // Send the message
  if (isHost)
    {
     if (everyone)
       {
        // Send to everyone
        for (int i = 1; i < players; i++)
          {
           if (player_list[i].sock != INVALID_SOCKET)
              if (send(player_list[i].sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
                {
                 sprintf(lasterr, "send() failed in SendChatMesg(); Error %d", WSAGetLastError());
                 ret = false;
                }
          }
       }
     else
       {
        // Make sure socket is valid
        if (indexTo >= 0 && indexTo < players)
           if (send(player_list[indexTo].sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
             {
              sprintf(lasterr, "send() failed in SendChatMesg(), Error %d", WSAGetLastError());
              ret = false;
             }
       }
    }
  else // Not Host
     if (send(sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
       {
        sprintf(lasterr, "send() failed in SendChatMesg(), Error %d", WSAGetLastError());
        ret = false;
       }
 
  // Free memory
  GlobalFreePtr(mesg);
 
  return ret;
}
 
 
 
// SendGameMesg() ////////////////////////////////////////////////////////
//
// Description:  Given a destination socket (on the host), a string, and
//               a everyone flag, will send a chat message to the server.
//               If everyone flag is set, then the server will relay it
//               to all.  Otherwise the server must relay to the right 
//               party in ReceiveMesg.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::SendGameMesg(int indexTo, LPSTR lpstr)
{
  // Return code
  bool ret = true;
 
  // Send a pointer to a WS_STRING_MSG
  WS_STRING_MSG_PTR mesg;
  DWORD dwMsgSize;
 
  // Get message size
  dwMsgSize = sizeof(WS_STRING_MSG)+lstrlen(lpstr);
 
  // Allocate space
  mesg = (WS_STRING_MSG_PTR)GlobalAllocPtr(GHND, dwMsgSize);
 
  // Copy the actual message
  lstrcpy(mesg->szMsg, lpstr);
 
  // Set the source location & type
  mesg->dwType = WS_MSG_GAMEPLAY;
  mesg->dwSize = dwMsgSize;
  mesg->bcast = false;
  mesg->src = GetIndex();
  mesg->dest = indexTo;
 
  // Send the message
  if (isHost)
    {
     // Make sure socket is valid
     if (indexTo >= 0 && indexTo < players)
        if (send(player_list[indexTo].sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
          {
           sprintf(lasterr, "send() failed in SendGameMesg(); Error %d", WSAGetLastError());
           ret = false;
          }
 
    }
  else // Just send it to server
     if (send(sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
       {
        sprintf(lasterr, "send() failed in SendGameMesg(); Error %d", WSAGetLastError());
        ret = false;
       }
 
  // Free memory
  GlobalFreePtr(mesg);
 
  return ret;
}
 
 
 
// Private Functions //////////////////////////////////////////////////////////
 
 
 
 
 
// ReceiveMesg() /////////////////////////////////////////////////////////
//
// Description:  If hosting, will relay message to the appropriate party.
//               Otherwise just returns a message back to the app.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::ReceiveMesg(SOCKET s, char *mesg, int& type, int& from)
{
  bool ret = true;
 
 
  // Get a copy of the message
  recv(s, (char*)theMesg, MAX_MESGSIZE, 0);
 
 
  // Set valuable fields for main window
  type = theMesg->dwType;
  strcpy(mesg, theMesg->szMsg);
 
 
  // Do something with the message...
  if (isHost)
    {
     // Don't trust sender
     for (int i = 0; i < players; i++)
        if (player_list[i].sock == s)
           from = i;
 
     if (theMesg->dwType == WS_MSG_NAME)
       {
 
        if (player_list[from].name[0] == 0) // Only process first message
          {
           // Copy player's name into right spot
           strncpy(player_list[from].name, theMesg->szMsg, NAMELEN);
           player_list[from].name[NAMELEN-1] = 0; 
 
 
           // Relay to other players
           theMesg->src = from;
 
           for (int i = 1; i < players; i++)
             {
              theMesg->dest = i;
              if (send(player_list[i].sock, (char*)theMesg, theMesg->dwSize, 0) == SOCKET_ERROR)
                {
                 sprintf(lasterr, "send() failed in ReceiveMesg(); Error %d", WSAGetLastError());
                 ret = false;
                }
              else
                 ;
             }
          }
        else
          {
           // This client lost his own WS_MSG_NAME message; resend them all players
           theMesg->dest = from;
           theMesg->src = from;
 
           if (send(player_list[from].sock, (char*)theMesg, theMesg->dwSize, 0) == SOCKET_ERROR)
             {
              sprintf(lasterr, "send() failed in ReceiveMesg(); Error %d", WSAGetLastError());
              ret = false;
             }
           type = -1;
          }
       }
     else if (theMesg->bcast && theMesg->dwType == WS_MSG_CHATSTRING)
       {
        // Broadcast chat message; relay & process
 
        for (int i = 1; i < players; i++)
           if (player_list[i].sock != s)
              if (send(player_list[i].sock, (char*)theMesg, theMesg->dwSize, 0) == SOCKET_ERROR)
                {
                 sprintf(lasterr, "send() failed in ReceiveMesg(); Error %d", WSAGetLastError());
                 ret = false;
                }
 
       }
     else if (theMesg->dest == 0)
       {
        // It's for us
        strcpy(mesg, theMesg->szMsg);
        type = theMesg->dwType;
       }
     else if (theMesg->dwType == WS_MSG_CHATSTRING)
       {
        // Relay to someone else
        if (theMesg->dest > 0 && theMesg->dest < players)
           if (send(player_list[theMesg->dest].sock, (char*)theMesg, theMesg->dwSize, 0) == SOCKET_ERROR)
             {
              sprintf(lasterr, "send() failed in ReceiveMesg(); Error %d", WSAGetLastError());
              ret = false;
             }
 
        // Don't process in main window
        type = -1; 
       }
    }
  else // Client
    {
     from = theMesg->src;
 
     if (theMesg->dwType == WS_MSG_NAME)
       {
        if (player_list[from].connected == false/* || player_list[from].name[0] == 0*/)
          {
           // Copy name, set player connected, increment player count
           strncpy(player_list[from].name, theMesg->szMsg, NAMELEN);
           player_list[from].name[NAMELEN-1] = 0;
   
           if (player_list[from].connected == false)
              players++;
 
           player_list[from].connected = true;
          }
 
        // The NAME message is from ourselves; set our index
        if (theMesg->dest == from && !myIndex)
           myIndex = from;
        else
           type = -1;
       }
     else if (theMesg->dwType == WS_MSG_DROP)
       {
        // Make sure we know this client has dropped
        player_list[from].connected = false;
       }
     else
       {
        // We are the client; let main window process mesg.     
        strcpy(mesg, theMesg->szMsg);
        type = theMesg->dwType;
       }
    }
 
  return ret;
}
 
 
// SendDropMesg() /////////////////////////////////////////////////////////////
//
// Description:  Only the host should call this, and it calls it when it gets
//               an FD_CLOSE message.  A drop message is then sent to all other
//               clients to notify them of this.
///////////////////////////////////////////////////////////////////////////////
 
bool ServerWS::SendDropMesg(int who, int indexTo)
{
  bool ret = true;
 
  // Send a pointer to a WS_STRING_MSG
  WS_STRING_MSG_PTR mesg;
  DWORD dwMsgSize;
 
  // Get message size
  dwMsgSize = sizeof(WS_STRING_MSG)+1;
 
  // Allocate space
  mesg = (WS_STRING_MSG_PTR)GlobalAllocPtr(GHND, dwMsgSize);
 
  // Copy the actual message
  lstrcpy(mesg->szMsg, "");
 
  // Set the source location & type
  mesg->dwType = WS_MSG_DROP;
  mesg->dwSize = dwMsgSize;
  mesg->bcast = false;
  mesg->src = who;
  mesg->dest = indexTo;
 
  // Send the message
  if (isHost)
    {
     // Make sure socket is valid
     if (indexTo >= 0 && indexTo < players)
        if (send(player_list[indexTo].sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
          {
           sprintf(lasterr, "send() failed in SendNameMesg(); Error %d", WSAGetLastError());
           ret = false;
          }
    }
 
 
  // Free memory
  GlobalFreePtr(mesg);
 
  return ret;
}
 
 
 
// SendNameMesg() ////////////////////////////////////////////////////////
//
// Description:  Given a destination index (on the host), and a string,
//               will send a NAME message to the specified index.
//////////////////////////////////////////////////////////////////////////
 
bool ServerWS::SendNameMesg(int indexTo, int indexFrom, LPSTR lpstr)
{
  bool ret = true;
 
  // Send a pointer to a WS_STRING_MSG
  WS_STRING_MSG_PTR mesg;
  DWORD dwMsgSize;
 
  // Get message size
  dwMsgSize = sizeof(WS_STRING_MSG)+lstrlen(lpstr);
 
  // Allocate space
  mesg = (WS_STRING_MSG_PTR)GlobalAllocPtr(GHND, dwMsgSize);
 
  // Copy the actual message
  lstrcpy(mesg->szMsg, lpstr);
 
  // Set the source location & type
  mesg->dwType = WS_MSG_NAME;
  mesg->dwSize = dwMsgSize;
  mesg->bcast = false;
  mesg->src = indexFrom;
  mesg->dest = indexTo;
 
  // Send the message
  if (isHost)
    {
     // Make sure socket is valid
     if (indexTo >= 0 && indexTo < players)
       {
        if (send(player_list[indexTo].sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
          {
           sprintf(lasterr, "send() failed in SendNameMesg(); Error %d", WSAGetLastError());
           ret = false;
          }
       }
    }
  else
    {
     // Send to host
     if (send(sock, (char*)mesg, dwMsgSize, 0) == SOCKET_ERROR)
       {
        sprintf(lasterr, "send() failed in SendNameMesg(); Error %d", WSAGetLastError());
        ret = false;
       }
    }
 
 
  // Free memory
  GlobalFreePtr(mesg);
 
  return ret;
 
}

All material Copyright (c) 2001-2010 Hexar
Please direct all inquiries, love letters, and hate mail to .

Home •  Blog •  Photos •  Code •  Features •  Resume •  WinTetris