/// <summary>
        /// Attempt to connect to the server via the provided hostname and port.
        /// </summary>
        /// <param name="callbackFunction"> a function to be called when a connection is made </param>
        /// <param name="hostname"> name of the server to connect to </param>
        /// <returns></returns>
        public static Socket ConnectToServer(NetworkAction callbackFunction, string hostname, int port)
        {
            // Create a TCP/IP socket.
            MakeSocket(hostname, out Socket socket, out IPAddress ipAddress);

            // make a new state of the socket we just made
            SocketState state = new SocketState(socket, -1);

            // call the correct function needed for the client
            state.SetNetworkAction(callbackFunction);

            // make a new connection to the server
            state.GetSocket().BeginConnect(ipAddress, port, ConnectedCallback, state);

            // return the current socket with respect to the state
            return(state.GetSocket());
        }
 /// <summary>
 /// Takes in a SocketState object, state, and loads the buffer with data coming from the socket.
 /// </summary>
 public static void GetData(SocketState state)
 {
     try
     {
         state.GetSocket().BeginReceive(state.GetMessageBuffer(), 0, state.GetMessageBuffer().Length, SocketFlags.None, ReceiveCallback, state);
     }
     catch (SocketException e)
     {
         // put the state object in a state of error with the given message
         state.HasError     = true;
         state.ErrorMessage = e.Message;
         // invoke client delegate so it can take whatever action it needs with an error
         state.InvokeNetworkAction(state);
     }
 }
        /// <summary>
        /// This function is "called" by the operating system when the remote site acknowledges connect request
        /// </summary>
        /// <param name="ar"></param>
        private static void ConnectedCallback(IAsyncResult ar)
        {
            //pull SocketState object from the IAsyncResult object, ar
            SocketState state = (SocketState)ar.AsyncState;

            try
            {
                // Complete the connection.
                state.GetSocket().EndConnect(ar);
            }
            catch (Exception e)
            {
                state.HasError     = true;
                state.ErrorMessage = e.Message;
            }

            state.InvokeNetworkAction(state);
        }
        /// <summary>
        /// Called by the OS when new data arrives. If the connection is closed does nothing, else
        /// gets the SocketState and calls the callback function provided by the SocketState.
        /// </summary>
        public static void ReceiveCallback(IAsyncResult stateAsArObject)
        {
            SocketState state = (SocketState)stateAsArObject.AsyncState;

            int bytesRead = 0;

            try
            {
                bytesRead = state.GetSocket().EndReceive(stateAsArObject);
            }
            catch (SocketException e)
            {
                state.HasError     = true;
                state.ErrorMessage = e.Message;
                // invoke client delegate so it can take whatever action it needs with an error
                state.InvokeNetworkAction(state);
            }
            catch (ObjectDisposedException e)
            {
                state.HasError     = true;
                state.ErrorMessage = e.Message;
                // invoke client delegate so it can take whatever action it needs with an error
                state.InvokeNetworkAction(state);
            }

            // If the socket is still open
            if (bytesRead > 0)
            {
                string theMessage = Encoding.UTF8.GetString(state.GetMessageBuffer(), 0, bytesRead);
                // Append the received data to the growable buffer.
                // It may be an incomplete message, so we need to start building it up piece by piece
                state.GetStringBuilder().Append(theMessage);

                // Instead, just invoke the client's delegate, so it can take whatever action it desires.
                state.InvokeNetworkAction(state);
            }
        }