/// <summary> /// Finish establishing a connection to the server, invoke the callback in the preserved state object, and begin receiving data /// </summary> public static void Connected_to_Server(IAsyncResult state_in_an_ar_object) { // Get the state from the parameter Preserved_State_Object state = (Preserved_State_Object)state_in_an_ar_object.AsyncState; try { state.socket.EndConnect(state_in_an_ar_object); // Invoke the callback state.callback.DynamicInvoke(state); // Begin receiving data from the server state.socket.BeginReceive(state.buffer, 0, Preserved_State_Object.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } catch (SocketException) { // If there is a problem with the socket, gracefully close it down if (state.socket.Connected) { state.socket.Shutdown(SocketShutdown.Both); state.socket.Close(); } // Invoke the callback state.callback.DynamicInvoke(state); } }
/// <summary> /// Handles reception and storage of data /// </summary> public static void ReceiveCallback(IAsyncResult state_in_an_ar_object) { // Get the state from the parameter, declare a variable for holding count of received bytes Preserved_State_Object state = (Preserved_State_Object)state_in_an_ar_object.AsyncState; try { int bytesRead = state.socket.EndReceive(state_in_an_ar_object); // If bytes were read, save the decoded string and invoke the callback if (bytesRead > 0) { state.data.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead)); state.callback.DynamicInvoke(state); } // Otherwise we are disconnected - close the socket else { //TODO: do these have to stay commented out for it to work, or does it work now? //Needs to be tested again. state.socket.Shutdown(SocketShutdown.Both); state.socket.Close(); } } catch (Exception) { // If there is a problem with the socket, gracefully close it down if (state.socket.Connected) { state.socket.Shutdown(SocketShutdown.Both); state.socket.Close(); } } }
/// <summary> /// Method to send and receive data from client /// </summary> private void ManageData(Preserved_State_Object state) { // Try to perform the complete move or split actions string[] actions = Regex.Split(state.data.ToString(), @"\n"); for (int i = 0; i < actions.Length - 1; i++) { TryMoveOrSplit(actions[i], state); } // Try to perform the last move or split action if it is complete, otherwise append what is there for later string lastAction = actions.Last(); if (lastAction.Length > 1 && lastAction.Last() == ')') { TryMoveOrSplit(lastAction, state); } else { state.data = new StringBuilder(lastAction); } // Call for more client actions Network.I_Want_More_Data(state); }
/// <summary> /// Main callback method for setting up a client. /// </summary> private void SetUpClient(Preserved_State_Object state) { Console.WriteLine("User " + state.data + " has connected to the server."); // Generate 2 random starting coords within our world, check if other players are there, then send if player won't get eaten immediately (helper method) double x, y; Cube cube; string worldData; lock (World) { World.FindStartingCoords(out x, out y, false); cube = new Cube(x, y, World.GetUid(), false, state.data.ToString(), World.PLAYER_START_MASS, World.GetColor(), 0); World.Cubes[cube.uid] = cube; worldData = World.SerializeAllCubes(); World.DatabaseStats.Add(cube.uid, new World.StatTracker(state.data.ToString())); } state.CubeID = cube.uid; state.data.Clear(); state.callback = new Network.Callback(ManageData); // Send the client's cube and then all of the world data Network.Send(state.socket, JsonConvert.SerializeObject(cube) + "\n"); Network.Send(state.socket, worldData); lock (Sockets) { Sockets.Add(state.socket, new ScoreInformation(cube.uid)); } // Ask for more data from client Network.I_Want_More_Data(state); }
/// <summary> /// Callback method - gets the player cube from the server /// </summary> private void GetPlayerCube(Preserved_State_Object state) { // Get the player cube (and add its uid to the set of split player cubes) Cube c = JsonConvert.DeserializeObject <Cube>(state.data.ToString()); PlayerSplitID.Add(PlayerID = c.uid); // Set the max mass to the initial player mass MaxMass = c.Mass; // Add the player cube to the world lock (World) { World.Cubes.Add(c.uid, c); } // Begin painting the world this.Paint += new System.Windows.Forms.PaintEventHandler(this.Display_Paint); this.Invalidate(); // Set the default move coordinates to the player block's starting location PrevMouseLoc_x = (int)c.loc_x; PrevMouseLoc_y = (int)c.loc_y; // Provide the next callback and start getting game data from the server state.callback = new Network.Callback(SendReceiveData); Network.I_Want_More_Data(state); }
/// <summary> /// Accepts a new client and begins data transferring /// </summary> public static void Accept_a_New_Client(IAsyncResult ar) { Preserved_State_Object state = (Preserved_State_Object)ar.AsyncState; state.socket = state.server.EndAcceptSocket(ar); state.socket.BeginReceive(state.buffer, 0, Preserved_State_Object.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); //Get the name, then give them their cube. state.server.BeginAcceptSocket(new AsyncCallback(Accept_a_New_Client), new Preserved_State_Object(state.server, state.callback)); }
/// <summary> /// Heart of the server code. Creates an async loop for accepting new clients. /// </summary> public static void Server_Awaiting_Client_Loop(Delegate callback, int port) { TcpListener server = TcpListener.Create(port); server.Start(); Preserved_State_Object state = new Preserved_State_Object(server, callback); server.BeginAcceptSocket(new AsyncCallback(Accept_a_New_Client), state); }
/// <summary> /// Callback for web server - builds webpages based off of queries received from the browser /// </summary> private void CreateWebPage(Preserved_State_Object state) { string query = Regex.Split(state.data.ToString(), "\r\n")[0]; string score = @"GET /scores"; string games = @"GET /games\?player="; string eaten = @"GET /eaten\?id="; string ending = @" HTTP/1.1"; if (Regex.IsMatch(query, score)) { /* Upon receiving "GET /scores HTTP/1.1" the server will send back an HTML web page containing a table of information reporting all recorded scores. * This should include, the length of time alive, the maximum mass, the highest rank, and the number of cubes eaten. * The HTML table should have one row for each player/game in the database and one column for each of the above required pieces of information. * A superior solution would create links in this table to the information described below (e.g., clicking on a players name would invoke case 2 below).*/ string dbQuery = "Select * from Players"; Network.Send(state.socket, StatsHTML(new MySqlCommand(dbQuery), 1, "AgCubio Stats | High Scores"), true); } else if (Regex.IsMatch(query, games)) { /*Upon receiving "GET /games?player=Joe HTTP/1.1" the server will send back an HTML web page containing a table of information reporting all games by the player "Joe". * There should be one row for each game played by the player named in the line of text (e.g., "Joe" in the example above) and a column for each piece of information. * In addition to the above information, the time of death should be shown and the number of players eaten should be shown. * A superior solution would also have links to the main score table page and to the list of eaten players for a particular game.*/ query = Regex.Replace(query, "(" + games + ")|(" + ending + ")", ""); // Get the player name string dbQuery = "Select * from Players where name = '" + query + "'"; Network.Send(state.socket, StatsHTML(new MySqlCommand(dbQuery), 2, "AgCubio Stats | Player: " + query), true); } else if (Regex.IsMatch(query, eaten)) { /*Upon receiving "GET /eaten?id=35 HTTP/1.1" the server should send back an HTML page containing information about the specified game session (e.g., 35 in this example). * The page should contain all information about the players game, but most importantly highlight the names of players who were eaten. A superior solution would turn "eaten player" names into links to their high scores. * If there the specified game does not exist, treat this as an "anything else" case as discussed below. As always, a superior solution will have links from this page to other related pages.*/ query = Regex.Replace(query, "(" + eaten + ")|(" + ending + ")", ""); // Get the game id string dbQuery = "Select * from Eaten natural join Players where GameId = " + query; Network.Send(state.socket, StatsHTML(new MySqlCommand(dbQuery), 3, "AgCubio Stats | Game ID: " + query), true); } else { // Show an error page if the first line of text the browser sends the server is invalid Network.Send(state.socket, HTMLGenerator.GenerateError("Invalid web address"), true); } }
/// <summary> /// Tells server we are ready to receive more data /// </summary> public static void I_Want_More_Data(Preserved_State_Object state) { try { state.socket.BeginReceive(state.buffer, 0, Preserved_State_Object.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } catch (Exception) { // If there is a problem with the socket, gracefully close it down if (state.socket.Connected) { state.socket.Shutdown(SocketShutdown.Both); state.socket.Close(); } } }
/// <summary> /// Begins establishing a connection to the server /// </summary> public static Socket Connect_to_Server(Callback callback, string hostname) { // Store the server IP address and remote endpoint // MSDN: localhost can be found with the "" string. IPAddress ipAddress = (hostname.ToUpper() == "LOCALHOST") ? IPAddress.Parse("::1") : IPAddress.Parse(hostname); IPEndPoint remoteEP = new IPEndPoint(ipAddress, Port); // Make a new socket and preserved state object and begin connecting Socket socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); Preserved_State_Object state = new Preserved_State_Object(socket, callback); // Begin establishing a connection socket.BeginConnect(remoteEP, new AsyncCallback(Connected_to_Server), state); // Return the socket return(state.socket); }
/// <summary> /// Parses client requests into moves or splits /// </summary> private void TryMoveOrSplit(String str, Preserved_State_Object state) { // Get the coordinates for the move or split MatchCollection values = Regex.Matches(str, @"-*\d+"); double x = double.Parse(values[0].Value); double y = double.Parse(values[1].Value); // Handle moving or splitting // *NOTE: Cubes are not actually moved here, as that could lead to more or less movement per player in a given amount of time (based on connection speed) // - instead, movement direction is appended to a stringbuilder and dealt with all at the same time in the server's heartbeat tick if (str[1] == 'm') { lock (DataReceived) { DataReceived[state.CubeID] = new Tuple <double, double>(x, y); } } else if (str[1] == 's') { lock (World) { World.Split(state.CubeID, x, y); } } }
/// <summary> /// Callback method - sends the player name to the server /// </summary> private void SendName(Preserved_State_Object state) { // Pop a dialog box if the connection is not established if (!socket.Connected) { this.Invoke(new Action(UnableToConnect)); return; } // Save the network thread to a private member in the GUI (for deactivation later) NetworkThread = Thread.CurrentThread; // Prevent network from getting upset over empty string names string name = (textBoxName.Text == "") ? " " : textBoxName.Text; // Provide the next callback and send the player name to the server state.callback = new Network.Callback(GetPlayerCube); Network.Send(state.socket, name); }
/// <summary> /// Callback method - sends moves, receives data from the server /// </summary> private void SendReceiveData(Preserved_State_Object state) { if (socket.Connected) { lock (CubeData) { // Use the StringBuilder to append the string received from the server CubeData.Append(state.data); } state.data.Clear(); // Send a move request, following the convention: '(move, dest_x, dest_y)\n' string move = "(move, " + PrevMouseLoc_x + ", " + PrevMouseLoc_y + ")\n"; Network.Send(socket, move); // Ask for more data Network.I_Want_More_Data(state); } }