Home > Code Samples > Network Nodes

Network Nodes

This is a simple networking class I made to manage UDP ‘connections’ between peers and servers/clients. On part of this I was particularly happy with was our network input simulator. The reason stemmed from us having a heap corruption that wasn’t easily reproducible or deterministic. The network input simulator allowed us to replay back entire games until the point of the crash (and luckily for us, the simulated input caused the game to crash!). We were then able to hunt down the exact reason partially by examining the packets themselves and by debugging while running the simulator. For those curious, the problem came from joining a game late while another player had already attached a graphics object to themselves. When the player removed the object, it would doubly add a graphics component pointer into the component list (by accident) and would cause access of deleted memory (since we only removed the first pointer we found upon deletion of the component).

I did not modify this in any way before posting, except to remove commented out code blocks. Anything you see here directly corresponds to my own personal coding style and standards.

/******************************************************************************
  Author: Trevor Sundberg
    Date: 09/19/2009

 Purpose:
          The network node class implementation.
          All content © 2009 DigiPen (USA) Corporation, all rights reserved.
******************************************************************************/


// Includes
#include <stdio.h>
#include <stdlib.h>
#include "network_node.h"

// Using directives
using namespace Wallaby;

// Specifies how many instances we have (by default we start with zero!)
unsigned NetworkNode::mInstances = 0;

// Defines and macros
#define infinite_loop for(;;)

// Constants for file sections
#define FILE_END_UPDATE         'U'
#define FILE_NEW_PACKET         'P'
#define FILE_NEW_CONNECTION     'C'
#define FILE_NEW_DISCONNECTION  'D'

// Constructor
NetworkNode::NetworkNode(u32         program_id,
                         f32         connection_timeout,
                         const char* debug_name) :
// Initializer list
mDebugName(debug_name)
{
  // Increment the number of instances we have
  ++mInstances;

  // If this is the only instance running, instantiate winsock data
  if (mInstances == 1)
  {
    // Initialize winsock
    WSADATA winsock_data;
    WSAStartup(MAKEWORD(2,2), &winsock_data);
  }

  // Set the default program ID
  SetProgramID(program_id);

  // Makes the class unusable (if some really bad error occurs)
  mUnusable = false;

  // By default we're not bound
  mBound = false;

  // By default, there is a one to one ratio of updates to updates in simulation
  mFrameProcessedCount = 1;

  // No file to write to or read from by default
  mWriteToFile  = 0;
  mReadFromFile = 0;

  // By default, debug mode is off
  mDebugMode = DEBUG_MODE_OFF;

  // Set the default text-out callback
  mTextOutCallback = DefaultTextOut;

  // Set the timeout time
  mTimeoutSeconds = connection_timeout;

  // First create the socket that we'll be using over and over
  mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

  // If we got an invalid socket, get the error and set the socket to unusable
  if (mSocket == INVALID_SOCKET)
  {
    // Get the error associated
    mError = WSAGetLastError();

    // Show an error, then set the socket to be unusable
    Error("Networking: Unable to create socket (Error: %d)\n", mError);
    mUnusable = true;
    return;
  }

  // Used to set the socket to non blocking mode
  unsigned long non_blocking = 1;

  // Set the socket into non-blocking mode
  mError = ioctlsocket(mSocket, FIONBIO, &non_blocking);

  // If there was an error
  if (mError != 0)
  {
    // Show an error, then set the socket to be unusable
    Error("Networking: Unable to set socket to non-blocking\n");
    mUnusable = true;
    return;
  }
 
  // Used to set the socket to allow broadcasts
  unsigned long allow_broadcasts = 1;

  // Set the socket into broadcast mode
  mError = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, (const char*) &allow_broadcasts, sizeof(allow_broadcasts));

  // If there was an error
  if (mError != 0)
  {
    // Show an error, then set the socket to be unusable
    Error("Networking: Unable to set socket to accept broadcasts\n");
    mUnusable = true;
    return;
  }
 
  // Set the receive and send buffer sizes
  int buffer_size = 4194304;
  mError = setsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, (const char*) &buffer_size, sizeof(buffer_size));
  mError = setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, (const char*) &buffer_size, sizeof(buffer_size));
 

  // If there was an error
  if (mError != 0)
  {
    // Show an error, then set the socket to be unusable
    Error("Networking: Unable to set socket to accept broadcasts\n");
    mUnusable = true;
    return;
  }

}

// Destructor
NetworkNode::~NetworkNode()
{
  // Close the connection
  //Close();

  // If we have a file set to write to, close the handle now
  if (mWriteToFile != 0)
    fclose(mWriteToFile);

  // If we have a file set to read from, close the handle now
  if (mReadFromFile != 0)
    fclose(mReadFromFile);

  // Now actually free the resources
  closesocket(mSocket);

  // Decrement the number of instances we have
  --mInstances;

  // If no more instances are running, cleanup winsock data
  if (mInstances == 0)
    WSACleanup();
}


// Sets the program ID (for identifying connections)
void NetworkNode::SetProgramID(u32 id)
{
  mProgramID = htonl(id);
}

// Sets the timeout time for connections
void NetworkNode::SetTimeoutTime(f32 timeout_seconds)
{
  mTimeoutSeconds = timeout_seconds;
}

// Update the network node
void NetworkNode::Update()
{
  // If we have data to be sent, send it now
  SendQueuedData();

  // Receive data from the socket
  ReceiveData();

  // Update all the connections (drop connections, send heartbeats, etc)
  UpdateConnections();

  // Process any file input data
  ProcessFileData();
}


// Sets a flag to write incoming data to a file
void NetworkNode::WriteSimulationToFile(const char* file)
{
  // Attempt to open the file
  mWriteToFile = fopen(file, "wb");
}

// Simulates network input from a given file
void NetworkNode::SimulateFromFile(const char* file)
{
  // Read data from a file
  mReadFromFile = fopen(file, "rb");
}

// Sets the simulation count
void NetworkNode::SetSimulationsPerFrame(int count)
{
  // The number of updates processed for every frame
  mFrameProcessedCount = count;
}

// Tells the socket to connect to the outside
void NetworkNode::Connect(const char* host_or_ip, port_type port)
{
  // Get the ip address from a string
  ip_type ip = inet_addr(host_or_ip);

  // Check if the ip is valid or not
  if (ip == INADDR_NONE)
  {
    // Get the ip of the given host (if it is a host)
    hostent* host = gethostbyname(host_or_ip);

    // If the host was found
    if (host != 0)
      ip = *(ip_type*) host->h_addr_list[0];
    // Otherwise, exit early and give an error
    else
    {
      // Show an error, then exit out
      Error("Invalid host or IP given to Connect (%s)\n", host_or_ip);
      return;
    }
  }

  // Add a new connection to the list
  Connection* connection = CreateConnection(ip, port);

  // Check if that a connection didn't already exist
  if (connection != 0)
  {
    // Send a connection request
    SendConnectionSYN(ip, port, connection);

    // Set the connection state to be authenticated
    connection->SetState(Connection::STATE_SENT_SYN);
  }
  // Otherwise, a connection existed, show an error
  else
  {
    // Show an error
    Error("Attempt to connect to a host/port that was already connected (%s : %d)\n", host_or_ip, port);
  }
}

// Listen to a given port (returns false if that port is in use)
bool NetworkNode::Listen(port_type port)
{
  // Create the binder
  mBinderInfo.sin_family      = AF_INET;
  mBinderInfo.sin_port        = htons(port);
  mBinderInfo.sin_addr.s_addr = INADDR_ANY;

  // Attempt to bind the socket to the port
  int error = bind(mSocket, (const sockaddr*) &mBinderInfo, sizeof(mBinderInfo));

  // If we got an error, show an error
  if (error == SOCKET_ERROR)
    Error("Unable to bind on port %d\n", port);
  else
    mBound = true;

  // Return true if we had no error
  return (error == 0);
}

    // Closes all connections (safe to call at all times)
void NetworkNode::Disconnect()
{
  mBound = false;
}


// Sends data to a specific connection
void NetworkNode::SendTo(ip_type ip, port_type port, const char* data_in, size_t size, unsigned options)
{
  // Check if the data is null terminated
  if (size == DATA_IS_NULL_TERMINATED)
  {
    // Get the string length of the data
    size = strlen(data_in);
  }

  // Check if the data is below the max size
  if (size < MAXIMUM_PACKET_SIZE)
  {
    // Create a new data-gram without a header
    // The special IP and port will be checked when sending to all
    DataGram data = DataGram(data_in, size, ip, port, 0, options);

    // Add the data-gram to the "to be sent" list
    AddToBeSentDataGram(&data);
  }
  else
  {
    // Show an error that the packet size was too big
    Error("Networking: Packet send size too big "
          "(attempt to send packet of size %d, max is %d)\n",
          size, MAXIMUM_PACKET_SIZE);
  }
}

// Sends data to a specific connection
void NetworkNode::SendToAllExcept(ip_type ip, port_type port, const char* data_in, size_t size, unsigned options)
{
  // Use the auxiliary function to send data to everyone
  SendTo(ip, port, data_in, size, options | SEND_TO_ALL_EXCEPT);
}


// Sends data to all the connections
void NetworkNode::SendToAll(const char* data_in, size_t size, unsigned options)
{
  // Use the auxiliary function to send data to everyone
  SendTo(0, 0, data_in, size, options | SEND_TO_ALL);
}

// Sends a broadcast
void NetworkNode::SendBroadcast(port_type port, const char* data_in, size_t size)
{
  // Use the auxiliary function to send data to everyone
  SendTo(0xFFFFFFFF, port, data_in, size, SEND_TO_NON_CONNECTION);
}

// Sends a raw packet to a particular person that is not a connection
void NetworkNode::SendToRaw(ip_type ip, port_type port, const char* data_in, size_t size)
{
  // Use the auxiliary function to send data to everyone
  SendTo(ip, port, data_in, size, SEND_TO_NON_CONNECTION);
}


// Returns if we have data
bool NetworkNode::HasData()
{
  return mReceivedDataGrams.size() > 0;
}

// Returns the ip of the sender
ip_type NetworkNode::GetNextDataIP()
{
  // Check if we actually have data, and if so return the next size
  if (HasData())
    return mReceivedDataGrams.front().GetAddress();
  // Otherwise, return nothing
  else
    return 0;
}

// Returns the port of the sender
port_type NetworkNode::GetNextDataPort()
{
  // Check if we actually have data, and if so return the next size
  if (HasData())
    return mReceivedDataGrams.front().GetPortNum();
  // Otherwise, return nothing
  else
    return 0;
}

// Gets the size of the next message to receive
// Returns zero if no messages are available
size_t NetworkNode::GetNextDataSize()
{
  // Check if we actually have data, and if so return the next size
  if (HasData())
    return mReceivedDataGrams.front().GetContentsSize();
  // Otherwise, return zero
  else
    return 0;
}

// Receives a message and writes it out to a buffer
void NetworkNode::ReadData(char* out_data)
{
  // Check if we actually have data
  if (HasData())
  {
    // Copy all the data out
    mReceivedDataGrams.front().CopyContentsInto(out_data);

    // Pop the data-gram off the back  
    mReceivedDataGrams.pop_front();
  }
  // For some reason, the user is trying to read data without having it
  else
  {
    // Give an error
    Error("Attempt to read data from the network node when it has none\n");
  }
}

// Receives a message and writes it out to a buffer (only safe if data is guaranteed to be string data)
vector<char> NetworkNode::ReadData()
{
  // Check if we actually have data
  if (HasData())
  {
    // Get the contents and size
    const char* data_ptr  = mReceivedDataGrams.front().GetContents();
    unsigned    data_size = (unsigned) mReceivedDataGrams.front().GetContentsSize();

    // Create a buffer that holds all the data
    vector<char> data(data_ptr, data_ptr + data_size);

    // Pop the data-gram off the back  
    mReceivedDataGrams.pop_front();

    // Return the string data
    return data;
  }
  // For some reason, the user is trying to read data without having it
  else
  {
    // Give an error
    Error("Attempt to read data from the network node when it has none\n");

    // Return an empty buffer
    return vector<char>();
  }
}

// Writes the packet data to a file as a single packet simulation
void NetworkNode::WritePacketToFile(const char* file)
{
  // Attempt to open the file
  FILE* temp_file = fopen(file, "wb");

  // If the file opening was as success
  if (temp_file)
  {
    // Write that we got a packet
    fputc(FILE_NEW_PACKET, temp_file);

    // Get a reference to the current datagram
    DataGram& data = mReceivedDataGrams.front();

    // Get the length of the data
    size_t length = data.GetAllDataSize();

    // Get the ip and port
    ip_type ip      = data.GetAddress();
    port_type port  = data.GetPortNum();

    // Write the ip and port to a file
    fwrite(&ip,   sizeof(ip),   1, temp_file);
    fwrite(&port, sizeof(port), 1, temp_file);

    // Write the length of the data to a file
    fwrite(&length, sizeof(size_t), 1, temp_file);

    // Copy the contents into a buffer
    data.CopyAllDataInto(mBuffer);

    // Write the buffer to a file
    fwrite(mBuffer, length, 1, temp_file);

    // Close the file handle
    fclose(temp_file);
  }
}


// Checks if we have any new connections
bool NetworkNode::HasNewConnection()
{
  return mNewConnections.size() > 0;
}

// Gets the most recent connection ip
ip_type NetworkNode::GetNextConnectionIP()
{
  return mNewConnections.back().ip;
}

// Gets the most recent connection port (and pops the connection)
port_type NetworkNode::GetNextConnectionPort()
{
  return mNewConnections.back().port;
}

// Pops the new connection
void NetworkNode::PopNextConnection()
{
  mNewConnections.pop_back();
}

// Checks if we have any new disconnections
bool NetworkNode::HasNewDisconnection()
{
  return mNewDisconnections.size() > 0;
}

// Gets the most recent disconnection ip
ip_type NetworkNode::GetNextDisconnectionIP()
{
  return mNewDisconnections.back().ip;
}

// Gets the most recent disconnection port
port_type NetworkNode::GetNextDisconnectionPort()
{
  return mNewDisconnections.back().port;
}

// Pops the new disconnection
void NetworkNode::PopNextDisconnection()
{
  mNewDisconnections.pop_back();
}


// Checks if the client is connected
bool NetworkNode::IsConnected()
{
  // Loop through all the connections
  for (ConnectIter it = mConnections.begin(); it != mConnections.end(); ++it)
  {
    // If we found a single authenticated connection, return true
    if (it->second.GetState() == Connection::STATE_AUTHENTICATED)
      return true;
  }

  // Since we got here, we found no authenticated connections
  return false;
}

// Checks if the client is listening
bool NetworkNode::IsListening()
{
  return mBound;
}

// Checks if a client connection is closed
bool NetworkNode::IsClosed()
{
  // If we're not connected and not listening, consider it closed
  return IsConnected() == false && IsListening() == false;
}

// Sets the specific debug mode
void NetworkNode::SetDebugMode(DebugMode mode)
{
  mDebugMode = mode;
}

// Sets the specific debug mode
void NetworkNode::SetDebugMode(unsigned mode)
{
  mDebugMode = (DebugMode) mode;
}

// Raise an error
void NetworkNode::Error(const char* format, ...)
{
  // If debug mode is not set to show warnings, don't show them
  if (mDebugMode < DEBUG_MODE_ERRORS)
    return;

  // Create a character buffer
  char buffer[1024];

  // Print out the debug name
  sprintf(buffer, "%s: ", mDebugName.c_str());

  // Create a va list then print the data
  va_list  args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end  (args);

  // Send the buffer to the text out callback
  mTextOutCallback(buffer, NET_MESSAGE_ERROR);
}

// Show a warning
void NetworkNode::Warning(const char* format, ...)
{
  // If debug mode is not set to show warnings, don't show them
  if (mDebugMode < DEBUG_MODE_WARNINGS_ERRORS)
    return;

  // Create a character buffer
  char buffer[1024];

  // Print out the debug name
  sprintf(buffer, "%s: ", mDebugName.c_str());

  // Create a va list then print the data
  va_list  args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end  (args);

  // Send the buffer to the text out callback
  mTextOutCallback(buffer, NET_MESSAGE_WARNING);
}

// Show a message
void NetworkNode::Message(const char* format, ...)
{
  // If debug mode is not set to show messages, don't show them
  if (mDebugMode < DEBUG_MODE_ALL)
    return;

  // Create a character buffer
  char buffer[1024];

  // Print out the debug name
  sprintf(buffer, "%s: ", mDebugName.c_str());

  // Create a va list then print the data
  va_list  args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end  (args);

  // Send the buffer to the text out callback
  mTextOutCallback(buffer, NET_MESSAGE_GENERAL);
}

// Set an text callback function
void NetworkNode::SetTextOutCallback(TextOutCB callback)
{
  mTextOutCallback = callback;
}

// Receives data from the socket
void NetworkNode::ReceiveData()
{
  // Get the size of the receive socket info
  int size_of_sockaddr = sizeof(mReceiveInfo);

  // Receive the buffer
  size_t length = 0;

  // A counter to count how many packets we receive
  unsigned packet_counter = 0;

  // Loop until one of the quitting conditions happens below
  infinite_loop
  {
    // Receive the latest packet and get the length of the data
    length = recvfrom(mSocket, (char*) mBuffer, MAXIMUM_PACKET_SIZE, 0, (sockaddr*) &mReceiveInfo, &size_of_sockaddr);

    // Get the ip from the current packet
    ip_type ip = mReceiveInfo.sin_addr.s_addr;

    // Get the port from the current packet
    port_type port = ntohs(mReceiveInfo.sin_port);

    // Check if the socket had an error
    if (length == SOCKET_ERROR)
    {
      // Now grab the last error value
      mError = WSAGetLastError();

      // Based on the error, give meaningful error messages
      switch (mError)
      {
      // Do nothing if the connection wasn't bound or we got the would-block error
      case WSAEWOULDBLOCK:
      case WSAEINVAL:
        {
          // Break out (we're done!)
          return;
        }

      // In the case of this error, most likely the connection was forcibly closed
      case WSAECONNRESET:
        {
          // Show an error and the IP address
          Error("Connection to (%s) forcibly closed\n", IPToString(ip));

          // Remove the connection
          RemoveConnection(mConnections.find(IPPort(ip, port)));
          return;
        }

      // Some error case that we didn't know about
      default:
        {
          // Show an error then break out
          Error("Unknown error in ReceiveData (Error: %d)\n", mError);
          return;
        }

      } // End error switch
    }


    // Check if the length of the packet is less than the minimum size
    if (length < MINIMUM_PACKET_SIZE)
    {
      // Show an error then skip this data-gram
      Error("Received data-gram smaller than the minimum packet size\n");
      continue;
    }


    // Show a message that we received a packet
    Message("Received %d bytes of data from %s on port %d...\n",
      length, IPToString(ip), port);

    // Create a new data-gram
    DataGram data = DataGram(mBuffer, length, ip, port, sizeof(PacketHeader), 0);

    // Process the received data
    ProcessReceivedData(&data);


    // Count up the number of packets we've got
    ++packet_counter;

    // If the number of packets we've gotten is equal to the max...
    if (packet_counter == MAXIMUM_RECEIVED_PER_CYCLE)
    {
      // Show an error then return out
      Error("Received too many packets in a single cycle\n");
      break;
    }

  } // End loop
}

// Process all the queued received data
void NetworkNode::ProcessReceivedData(DataGram* data)
{
  // For convenience, extract the ip and port
  ip_type   ip    = data->GetAddress();
  port_type port  = data->GetPortNum();

  // Get the header from the data (this is safe because we should have checked size by here)
  PacketHeader* header = (PacketHeader*) data->GetHeader();

  // Immediately try and grab a connection with the given ip and port
  Connection* connection = GetConnection(ip, port);


  // If the connection is valid...
  if (connection != 0)
  {
    // Set the last received sequence number
    connection->AddRemoteSequenceNum(header->sequence_num);

    // Set the last received packet time (now basically)
    connection->SetLastPacketTime(clock() / (f32) CLOCKS_PER_SEC);
  }


  // Check if the program ID is valid
  if (header->program_id != mProgramID)
  {
    // Show an error then skip this data-gram
    Error("Received data-gram with invalid program ID\n");
    return;
  }


  // Handle acking and sequence number updating
  HandleMissedPackets(data, connection);


  // Check if the packet is of type SYN
  if (header->packet_type == PacketHeader::TYPE_CONNECT_SYN)
  {
    // Check if that there isn't already a connection
    if (connection == 0)
    {
      // Show a message that we received a connection request
      Message("Received connection SYN\n");

      // Add the connection
      Connection* connection = CreateConnection(ip, port);

      // Reply to the message
      SendConnectionSYNACK(ip, port, connection);

      // Set the connection state to be authenticated
      connection->SetState(Connection::STATE_RECEIVED_SYN_SENT_SYNACK);
    }
    else
    {
      // Show a message that we received a duplicate connection request
      Message("Received duplicate connection SYN\n");
    }

    // Return, since we've handled it
    return;
  }

  // Check if the packet is of type SYNACK
  if (header->packet_type == PacketHeader::TYPE_CONNECT_SYNACK)
  {
    // Check if the connection actually exists
    if (connection != 0)
    {
      // Check if the state of the connection is in the handshake sent
      // (Make sure that it should be receiving this packet)
      if (connection->GetState() == Connection::STATE_SENT_SYN)
      {
        // Show a message that we received a connection ack
        Message("Received connection SYNACK\n");

        // Reply to the message
        SendConnectionACK(ip, port, connection);

        // Set the connection state to be authenticated
        connection->SetState(Connection::STATE_AUTHENTICATED);

        // Add the new connection to the queue
        mNewConnections.push_back(IPPort(ip, port));

        // Write this current packet to a file if there is one open
        if (mWriteToFile)
        {
          // Write that we got a new connection
          fputc(FILE_NEW_CONNECTION, mWriteToFile);

          // Write the ip and port to a file
          fwrite(&ip,   sizeof(ip),   1, mWriteToFile);
          fwrite(&port, sizeof(port), 1, mWriteToFile);
        }
      }
      // Otherwise, the packet somehow got misordered...
      else
      {
        // Show a message that we received it out of order
        Message("Received a SYNACK out of order\n");
      }
    }
    // Otherwise, the connection was not found
    else
    {
      // Show a message that we received an unexpected connection ack
      Message("Received a connection SYNACK when no connection was initiated\n");
    }

    // Return, since we've handled it
    return;
  }

  // Check if the packet is of type ACK
  if (header->packet_type == PacketHeader::TYPE_CONNECT_ACK)
  {
    // Check if the connection actually exists
    if (connection != 0)
    {
      // Check if the state of the connection is in the handshake sent
      // (Make sure that it should be receiving this packet)
      if (connection->GetState() == Connection::STATE_RECEIVED_SYN_SENT_SYNACK)
      {
        // Show a message that we received a connection ack
        Message("Received connection ACK\n");

        // Set the connection state to be authenticated
        connection->SetState(Connection::STATE_AUTHENTICATED);

        // Add the new connection to the queue
        mNewConnections.push_back(IPPort(ip, port));

        // Write this current packet to a file if there is one open
        if (mWriteToFile)
        {
          // Write that we got a new connection
          fputc(FILE_NEW_CONNECTION, mWriteToFile);

          // Write the ip and port to a file
          fwrite(&ip,   sizeof(ip),   1, mWriteToFile);
          fwrite(&port, sizeof(port), 1, mWriteToFile);
        }
      }
      // Otherwise, the packet somehow got misordered...
      else
      {
        // Show a message that we received it out of order
        Message("Received a ACK out of order\n");
      }
    }
    // Otherwise, the connection was not found
    else
    {
      // Show a message that we received an unexpected connection ack
      Message("Received a connection ACK when no connection was initiated\n");
    }

    // Return, since we've handled it
    return;
  }

  // Check if the packet is of type HEARTBEAT
  if (header->packet_type == PacketHeader::TYPE_HEARTBEAT)
  {
    // Check if the connection actually exists
    if (connection != 0)
    {
      // Check if the state of the connection is in the handshake sent
      // (Make sure that it should be receiving this packet)
      if (connection->GetState() == Connection::STATE_AUTHENTICATED)
      {
        // Show a message that we received a connection ack
        Message("Received connection HEARTBEAT\n");

        // Set the connection state to be authenticated
        connection->SetState(Connection::STATE_AUTHENTICATED);
      }
      // Otherwise, the packet somehow got misordered...
      else
      {
        // Show a message that we received it out of order
        Message("Received a HEARTBEAT out of order\n");
      }
    }
    // Otherwise, the connection was not found
    else
    {
      // Show a message that we received an unexpected HEARTBEAT
      Message("Received a HEARTBEAT when no connection was initiated\n");
    }

    // Return, since we've handled it
    return;
  }


  // Check if the amount of data we've got is more than the max bufferable packets
  if (mReceivedDataGrams.size() < MAXIMUM_BUFFERED_RECEIVED)
  {
    // Write this current packet to a file if there is one open
    if (mWriteToFile)
    {
      // Write that we got a packet
      fputc(FILE_NEW_PACKET, mWriteToFile);

      // Get the length of the data
      size_t length = data->GetAllDataSize();

      // Write the ip and port to a file
      fwrite(&ip,   sizeof(ip),   1, mWriteToFile);
      fwrite(&port, sizeof(port), 1, mWriteToFile);

      // Write the length of the data to a file
      fwrite(&length, sizeof(size_t), 1, mWriteToFile);

      // Write the buffer to a file
      fwrite(mBuffer, length, 1, mWriteToFile);
    }

    // Create a new data-gram and push it on (this one is for the user!)
    mReceivedDataGrams.push_back(*data);
  }
  // Show an error since the user is not checking the packets
  else
  {
    // Show an error that we've buffered too many received packets
    Error("Maximum buffered receive-packets reached (please read the data out!)\n");
  }
}

// Process any file input data
void NetworkNode::ProcessFileData()
{
  // Check if we should be receiving packets from a file
  if (mReadFromFile != 0 && mFrameProcessedCount != 0)
  {
    // The update counter (how many updates chunks per actual update)
    int update_counter = 0;

    // The file header of each chunk
    int file_header;

    // Loop until we get a frame update
    infinite_loop
    {
      // Read the next file chunk header
      file_header = fgetc(mReadFromFile);

      switch (file_header)
      {
      // We got someone new connecting, tell everyone about it
      case FILE_NEW_CONNECTION:
        {
          // The ip and port of the connection
          ip_type   ip;
          port_type port;

          // Read the ip and port from the file
          fread(&ip,   sizeof(ip),   1, mReadFromFile);
          fread(&port, sizeof(port), 1, mReadFromFile);

          // Inform everone of a new connection
          mNewConnections.push_back(IPPort(ip, port));
        }
        break;

      // We got a new packet, give it to the receive function
      case FILE_NEW_DISCONNECTION:
        {
          // The ip and port of the connection
          ip_type   ip;
          port_type port;

          // Read the ip and port from the file
          fread(&ip,   sizeof(ip),   1, mReadFromFile);
          fread(&port, sizeof(port), 1, mReadFromFile);

          // Inform everone of a new disconnection
          mNewDisconnections.push_back(IPPort(ip, port));
        }
        break;

      // We got a new packet, give it to the receive function
      case FILE_NEW_PACKET:
        {
          // The ip and port of the connection
          ip_type   ip;
          port_type port;

          // Read the ip and port from the file
          fread(&ip,   sizeof(ip),   1, mReadFromFile);
          fread(&port, sizeof(port), 1, mReadFromFile);

          // Holds the length of the data
          size_t length;

          // Read the length of the data from the file
          fread(&length, sizeof(size_t), 1, mReadFromFile);

          // Read the pack/buffer from the file
          fread(mBuffer, length, 1, mReadFromFile);

          // Create a new data-gram
          DataGram data = DataGram(mBuffer, length, ip, port, sizeof(PacketHeader), 0);

          // Process the received data
          ProcessReceivedData(&data);
        }
        break;

      // We reached the end of the file
      case EOF:
        {
          // Close the file and set the handle to null
          fclose(mReadFromFile);
          mReadFromFile = 0;
        }
        break;

      } // End file header switch


      // If we reached a file end update, we need to break out
      if (file_header == FILE_END_UPDATE)
      {
        // Increment the update counter
        ++update_counter;

        // All of the frames updated per process
        if (update_counter == mFrameProcessedCount)
          break;
      }

      // If we reach the end of the file, stop...
      if (file_header == EOF)
        break;

    } // End looping through data
  }

  // Write a new frame to the file if there is a file open
  if (mWriteToFile)
  {
    // Write an end update
    fputc(FILE_END_UPDATE, mWriteToFile);

    // Flush the stream
    fflush(mWriteToFile);
  }
}

// If we have data to be sent, send it now (but respect flow control)
void NetworkNode::SendQueuedData()
{
  // If we have any data-grams to be sent out
  while (mToSendDataGrams.size() > 0)
  {
    // Flow control here

    // Get a pointer to the data gram we're using
    DataGram* data = &mToSendDataGrams.front();

    // Get the options for this data-gram
    unsigned options = data->GetOptions();

    // Get the ip address
    ip_type ip = data->GetAddress();

    // Get the port
    port_type port = data->GetPortNum();


    // Check if the options is set to send to all connections (not broadcast!)
    if (options & SEND_TO_ALL)
    {
      // Loop through all the existing connections
      for (ConnectIter it = mConnections.begin(); it != mConnections.end(); ++it)
      {
        // Create a pointer to the current connection for ease of use
        Connection* connection = &it->second;

        // Create a pointer to the IP/Port interface (for convenience)
        const IPPort* ip_port = &it->first;

        // Modify the data-gram to send to the current connection (ip and port)
        data->SetAddress(ip_port->ip);
        data->SetPortNum(ip_port->port);

        // Print out the data as readable text (for debugging)
        //string readable = ToReadableText(data->GetContents(), data->GetContentsSize());
        //ShowMessage(Engine::MESSAGE_GENERAL, "%s SENT DATA (%d, %d): [%s]",
        //  mDebugName.c_str(), ip_port->ip, ip_port->port, readable.c_str());

        // Perform a raw send to the current connection
        RawSendTo(data, connection);
       
      } // End connection loop
    }
    // Otherwise, look for the send to all except option
    else if (options & SEND_TO_ALL_EXCEPT)
    {
      // Loop through all the existing connections
      for (ConnectIter it = mConnections.begin(); it != mConnections.end(); ++it)
      {
        // Create a pointer to the current connection for ease of use
        Connection* connection = &it->second;

        // Create a pointer to the IP/Port interface (for convenience)
        const IPPort* ip_port = &it->first;

        // If the connection has the same ip and port as specified, skip it
        if (ip_port->ip == ip && ip_port->port == port)
          continue;

        // Modify the data-gram to send to the current connection (ip and port)
        data->SetAddress(ip_port->ip);
        data->SetPortNum(ip_port->port);

        // Perform a raw send to the current connection
        RawSendTo(data, connection);
       
      } // End connection loop
    }
    // Check if it's supposed to be sent to a client that isn't connected
    else if (options & SEND_TO_NON_CONNECTION)
    {
      // Perform a raw send to the connection
      RawSendTo(data, 0);
    }
    // Otherwise, just send it normally if an actual connection exists
    else if (Connection* connection = GetConnection(ip, port))
    {
      // Perform a raw send to the connection
      RawSendTo(data, connection);
    }
    // The connection was somehow dropped or never existed, only show this if we're not simulating
    else if (mReadFromFile == 0)
    {
      // Show an error and the IP address
      Error("Attempt to send to an unknown or dropped connection (%s) on port %d\n",
            IPToString(ip), port);
    }

    // Pop the data-gram off since we sent it
    mToSendDataGrams.pop_front();

  } // End data-gram availability check
}

// Send a raw packet
void NetworkNode::RawSendTo(DataGram* data, Connection* connection)
{
  // Check if the state of the connection is valid
  if (connection != 0 && connection->GetState() != Connection::STATE_AUTHENTICATED && (data->GetOptions() & SEND_AS_CONNECTION_PACKET) == 0)
    return;

  // Check if the data-gram has a header
  if (data->HasHeader() == false)
  {
    // If not, append a generic header to the data
    AppendGenericHeader(data, connection);
  }

  // Create a socket address (used for sending)
  sockaddr_in sending_addr;

  // Setup the socket address (with ip and port)
  sending_addr.sin_family       = AF_INET;
  sending_addr.sin_port         = htons(data->GetPortNum());
  sending_addr.sin_addr.s_addr  = data->GetAddress();

  // Read the data from the current data-gram (the size should be checked already)
  data->CopyAllDataInto(mBuffer);

  // Show a message that we received a connection
  Message("Sending %d bytes of data to %s on port %d...\n",
    data->GetAllDataSize(), IPToString(data->GetAddress()), data->GetPortNum());

  // Send the data gram to the given IP
  mError = sendto(mSocket,
                  mBuffer,
                  (int) data->GetAllDataSize(),
                  NULL,
                  (const sockaddr*) &sending_addr,
                  sizeof(sending_addr));

  // If the connection is valid, we should do somethign with the sequence number
  if (connection != 0)
  {
    // Since we're sending a packet, increment the sequence number
    connection->IncrementSequenceNum();
  }

  // Check if the socket had an error
  if (mError == SOCKET_ERROR)
  {
    // Now grab the last error value
    mError = WSAGetLastError();

    // Based on the error, give meaningful error messages
    switch (mError)
    {
    // Do nothing if we got the would-block error
    case WSAEWOULDBLOCK:
      {
        // Break out (we're done!)
        break;
      }

    // Some error case that we didn't know about
    default:
      {
        // Show an error then break out
        Error("Unknown error in RawSendTo (Error: %d)\n", mError);
        break;
      }

    } // End error switch
  } // End error check
}

// Add a data-gram to be sent
void NetworkNode::AddToBeSentDataGram(DataGram* data)
{
  // Check if we have less than the maximum buffered sent data-grams
  if (mToSendDataGrams.size() < MAXIMUM_BUFFERED_SENT)
  {
    // Add the data-gram to the "to be sent" list
    mToSendDataGrams.push_back(*data);
  }
  // Show an error since the user is sending packets too fast
  else
  {
    // Show an error that we've buffered too many sent packets
    Error("Maximum buffered sent-packets reached "
          "(please reduce the rate at which you are sending packets!)\n");
  }
}


// Handle acking and sequence number updating
void NetworkNode::HandleMissedPackets(DataGram* data, Connection* connection)
{
  // Grab the packet header from the data
  PacketHeader* header = (PacketHeader*) data->GetHeader();

  // Check that the header and connection both exist
  if (header != 0 && connection != 0)
  {
    // Check if the current ack we're on is greater
    // than the (ack+1) gotten from the packet
    if (header->ack + 1 < connection->GetCurrentSequenceNum())
    {
      // Resend the packets that weren't acknowledged
     
    }

  } // End checking the header and connection
}


// Send a connection request
void NetworkNode::SendConnectionSYN(ip_type ip, port_type port, Connection* connection)
{
  // Create the datagram that we'll send
  DataGram data(ip, port, SEND_AS_CONNECTION_PACKET);

  // The packet will mainly consist of the packet header
  PacketHeader header;

  // Initialize the packet header
  InitPacketHeader(&header, connection);

  // Set the packet type
  header.packet_type = PacketHeader::TYPE_CONNECT_SYN;

  // Set the header for the data-gram
  data.SetHeader(&header, sizeof(PacketHeader));

  // Now add it to the "to be sent" list
  AddToBeSentDataGram(&data);

  // Show a message about the sent packet
  Message("Sent connection SYN\n");
}

// Send a reply to a connection request
void NetworkNode::SendConnectionSYNACK(ip_type ip, port_type port, Connection* connection)
{
  // Create the datagram that we'll send
  DataGram data(ip, port, SEND_AS_CONNECTION_PACKET);

  // The packet will mainly consist of the packet header
  PacketHeader header;

  // Initialize the packet header
  InitPacketHeader(&header, connection);

  // Set the packet type
  header.packet_type = PacketHeader::TYPE_CONNECT_SYNACK;

  // Set the header for the data-gram
  data.SetHeader(&header, sizeof(PacketHeader));

  // Now add it to the "to be sent" list
  AddToBeSentDataGram(&data);

  // Show a message about the sent packet
  Message("Sent connection SYNACK\n");
}

// Send an acknowledgment to a connection request
void NetworkNode::SendConnectionACK(ip_type ip, port_type port, Connection* connection)
{
  // Create the datagram that we'll send
  DataGram data(ip, port, SEND_AS_CONNECTION_PACKET);

  // The packet will mainly consist of the packet header
  PacketHeader header;

  // Initialize the packet header
  InitPacketHeader(&header, connection);

  // Set the packet type
  header.packet_type = PacketHeader::TYPE_CONNECT_ACK;

  // Set the header for the data-gram
  data.SetHeader(&header, sizeof(PacketHeader));

  // Now add it to the "to be sent" list
  AddToBeSentDataGram(&data);

  // Show a message about the sent packet
  Message("Sent connection ACK\n");
}

// Send a non-acknowledgment over the connection
void NetworkNode::SendNAK(ip_type ip, port_type port, Connection* connection)
{
  // Create the datagram that we'll send
  DataGram data(ip, port, 0);

  // The packet will mainly consist of the packet header
  PacketHeader header;

  // Initialize the packet header
  InitPacketHeader(&header, connection);

  // Set the packet type
  header.packet_type = PacketHeader::TYPE_NAK;

  // Set the header for the data-gram
  data.SetHeader(&header, sizeof(PacketHeader));

  // Now add it to the "to be sent" list
  AddToBeSentDataGram(&data);

  // Show a message about the sent packet
  Message("Sent connection NAK\n");
}

// Send an heartbeat over the connection
void NetworkNode::SendHEARTBEAT(ip_type ip, port_type port, Connection* connection)
{
  // Create the datagram that we'll send
  DataGram data(ip, port, 0);

  // The packet will mainly consist of the packet header
  PacketHeader header;

  // Initialize the packet header
  InitPacketHeader(&header, connection);

  // Set the packet type
  header.packet_type = PacketHeader::TYPE_HEARTBEAT;

  // Set the header for the data-gram
  data.SetHeader(&header, sizeof(PacketHeader));

  // Now add it to the "to be sent" list
  AddToBeSentDataGram(&data);

  // Show a message about the sent packet
  Message("Sent HEARTBEAT\n");
}


// Create a connection and return a pointer to it
// Returns 0 if a connection with that ip and port already exists
Connection* NetworkNode::CreateConnection(ip_type ip, port_type port)
{
  // Add a new connection to the list
  pair<ConnectIter, bool> it = mConnections.insert(ConnectPair(IPPort(ip, port), Connection()));

  // Check if the connection already existed
  if (it.second == false)
    return 0;

  // Otherwise, return the newly created connection!
  return &(it.first->second);
}

// Get a connection by ip and port
Connection* NetworkNode::GetConnection(ip_type ip, port_type port)
{
  // Attempt to find the connection by the ip and port
  ConnectIter it = mConnections.find(IPPort(ip, port));

  // Check if we found it
  if (it != mConnections.end())
    return &it->second;

  // Otherwise, we didn't find it, return zero
  else
    return 0;
}

// Update all the connections
void NetworkNode::UpdateConnections()
{
  // Loop through all the connections
  for (ConnectIter it = mConnections.begin(); it != mConnections.end();)
  {
    // Create a pointer to the current connection for ease of use
    Connection* connection = &it->second;

    // Create a pointer to the IP/Port interface (for convenience)
    const IPPort* ip_port = &it->first;

    // Check if we should send a heartbeat
    if (connection->GetState() == Connection::STATE_AUTHENTICATED &&
        connection->ShouldSendHeartbeat(mTimeoutSeconds, HEARTBEAT_TIMEOUT_RATIO))
    {
      // Send a heartbeat packet
      SendHEARTBEAT(ip_port->ip, ip_port->port, connection);

      // Tell the connection that we sent a heartbeat
      connection->SentHeartbeat();
    }

    // Check if the connection is dead with the current threshold
    if (connection->IsDead(mTimeoutSeconds))
    {
      // Show a message that a particular connection was dropped
      Message("Connection with IP (%s) and port %d dropped due to timeout\n",
        IPToString(ip_port->ip), ip_port->port);

      // Remove the connection from the list
      it = RemoveConnection(it);
    }
    // The connection wasn't dead, iterate to the next connection
    else
    {
      // Increment the iterator
      ++it;
    }

  } // End connection loop
}

// Remove a connection (count it as a disconnect also)
NetworkNode::ConnectIter NetworkNode::RemoveConnection(ConnectIter iter)
{
  // If we're not using an invalid iterator...
  if (iter != mConnections.end())
  {
    // Mark this as a disconect
    mNewDisconnections.push_back(iter->first);

    // Write this current disconnection to a file if there is one open
    if (mWriteToFile)
    {
      // Write that we got a new disconnection
      fputc(FILE_NEW_DISCONNECTION, mWriteToFile);

      // Write the ip and port to a file
      fwrite(&iter->first.ip,   sizeof(iter->first.ip),   1, mWriteToFile);
      fwrite(&iter->first.port, sizeof(iter->first.port), 1, mWriteToFile);
    }

    // Erase the iterator and return the next one
    return mConnections.erase(iter);
  }

  // Otherwise, return an invalid iterator
  return mConnections.end();
}


// Initializes a packet header
void NetworkNode::InitPacketHeader(PacketHeader* header_to_initialize, Connection* connection)
{
  // Check if we got nothing for the connection
  if (connection != 0)
  {
    // Set the program ID and sequence number
    header_to_initialize->program_id    = mProgramID;
    header_to_initialize->sequence_num  = connection->GetCurrentSequenceNum();
    header_to_initialize->ack           = connection->GetLatestRemoteSequenceNum();
    header_to_initialize->packet_type   = PacketHeader::TYPE_NONE;
  }
  else
  {
    // Set the program ID and sequence number
    header_to_initialize->program_id    = mProgramID;
    header_to_initialize->sequence_num  = 0;
    header_to_initialize->ack           = 0;
    header_to_initialize->packet_type   = PacketHeader::TYPE_NONE;
  }
}

// Appends a generic header to a data-gram
void NetworkNode::AppendGenericHeader(DataGram* append_to, Connection* connection)
{
  // Create a packet header
  PacketHeader header;

  // Initialize the packet header with defaults
  InitPacketHeader(&header, connection);

  // Append the header to the data-gram
  append_to->SetHeader(&header, sizeof(PacketHeader));
}

// Turns packet data into readable text
string NetworkNode::ToReadableText(const char* data, size_t size)
{
  // The output string we'll be returning
  string output;

  // Loop through all the data bytes
  for (unsigned i = 0; i < size; ++i)
  {
    // Check if the character is readable
    if (isprint(data[i]))
    {
      // Append the readable character to the output
      output += data[i];
    }
    else
    {
      // Append a different readable character to the output
      output += '.';
    }
  }
 
  // Return the output string
  return output;
}


// Converts an IP into a string
const char* NetworkNode::IPToString(ip_type ip)
{
  // Fill in the in_addr struct with the given ip
  in_addr addr;
  addr.S_un.S_addr = ip;

  // Use NTOA to convert the ip into a string
  return inet_ntoa(addr);
}

// Default text out callback
void NetworkNode::DefaultTextOut(const char* text, NetMessageType type)
{
  // Based off the type, do something...
  switch (type)
  {
  // If it's a normal message, print it normally
  case NET_MESSAGE_GENERAL:
    {
      // Set the color of the console text
      SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15 /*WHITE*/);

      // Output the text to stdout
      fprintf(stdout, text);
    }
    break;

  // If it's a warning message, print it with a special color
  case NET_MESSAGE_WARNING:
    {
      // Set the color of the console text
      SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14 /*YELLOW*/);

      // Output the text to stdout
      fprintf(stdout, text);
    }
    break;

  // If it's a error message, print it with a special color
  case NET_MESSAGE_ERROR:
    {
      // Set the color of the console text
      SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12 /*RED*/);

      // Output the text to stderr
      fprintf(stderr, text);
    }
    break;
  }
}
Categories: Code Samples Tags:
  1. No comments yet.
  1. No trackbacks yet.