/// <summary> /// Performs a shallow copy of the specified TFMS_Data object /// </summary> /// <param name="data">the TFMS_Data object that you want to duplicate</param> private void clone(TFMS_Data data) { if (data == null) return; this.acknowledged = data.acknowledged; this.cmdCommand = data.cmdCommand; this.strMessage = data.strMessage; this.strName = data.strName; this.timeStamp = data.timeStamp; }
/// <summary> /// Create a TFMS_Data object containing the name of all clients currently connected to the server /// </summary> /// <returns>TFMS_Data object with message field filled</returns> private TFMS_Data GetClientListMessage() { TFMS_Data msgToSend = new TFMS_Data(TFMS_Command.List, null, null); foreach (TFMS_ClientInfo c in clientList) { // use a * as delimiter msgToSend.strMessage += c.strName + "*"; } return msgToSend; }
/// <summary> /// When the server receives a message from a client, it rebroadcasts the message to all other clients /// This method sends the message to a single client /// </summary> /// <param name="data">TFMS_Data object to be sent</param> /// <param name="socket">Socket belonging to the target client</param> /// <param name="send">function to be called when the message is done sending (should be "onSend")</param> /// <returns>the result of sending the message to the target client</returns> private IAsyncResult sendTFMSmsg(TFMS_Data data, Socket socket, AsyncCallback send) { // get the byte array of the TFMS_Data object byte[] message = data.ToByte(); Console.WriteLine("Sending length: {0}", message.Length); // syncronously send the length of the actual message and block until the message has been sent. socket.Send(new TFMS_Data(TFMS_Command.MsgLen, string.Format("{0}", message.Length), data.strName).ToByte()); Console.WriteLine("BeginSend: {0} msg", data.cmdCommand); // the async call to send the message // returns an IAsyncResult object return socket.BeginSend(message, 0, message.Length, SocketFlags.None, send, socket); }
/// <summary> /// this hadles the actual message forwarding ect. /// you should never call this function directly /// </summary> /// <param name="result">this encapsulates the Socket of the client</param> public void OnReceive(IAsyncResult result) { // get the socket that received data Socket clientSocket = (Socket)result.AsyncState; // get the ClientInfo from the connected client TFMS_ClientInfo CI = null; if (findIndexFromClient(clientList, clientSocket) >= 0) CI = clientList[findIndexFromClient(clientList, clientSocket)]; Console.WriteLine("\n****************************************"); Console.WriteLine("Data Received from {0}", getNamefromSocket(clientSocket)); // attempt to get the data from the client and convert it into TFMS_Data object TFMS_Data msgReceived = extractData(clientSocket, CI, result); TFMS_Data msgToSend = new TFMS_Data(); msgToSend.cmdCommand = msgReceived.cmdCommand; msgToSend.strName = msgReceived.strName; #region Interpret incoming message command // set the message to send, client info buffer size, and client info based on the incoming command switch (msgReceived.cmdCommand) { case TFMS_Command.Login: // new client logging in #region Login command Console.WriteLine("Received login from {0}", getNamefromSocket(clientSocket)); // tell the server a logon was requested logonRequested(msgReceived); // add the new client to the current list of clients TFMS_ClientInfo clientInfo = new TFMS_ClientInfo(clientSocket, msgReceived.strName); clientList.Add(clientInfo); // pass the incoming client's name to other clients msgToSend.strMessage = msgReceived.strName; clientInfo.buffer = new byte[TFMS_Constants.BUFFER_SIZE]; // dimension the clients buffer. CI = clientInfo; // CI would not have been found before since its not in the clientList so just add it now #endregion break; case TFMS_Command.Logout: // client logging out #region Logout command Console.WriteLine("Received Logout from {0}", getNamefromSocket(clientSocket)); // tell the server a logout was requested logoffRequested(msgReceived); // remove the client from the list of connected clients. clientList.RemoveAt(findIndexFromClient(clientList, clientSocket)); clientSocket.Close(); // pass the client's name to other clients msgToSend.strMessage = msgReceived.strName; #endregion break; case TFMS_Command.List: // client requesting a list of all clients #region List command Console.WriteLine("Received List request from {0}", getNamefromSocket(clientSocket)); // tell the server a list of clients was requested listRequested(msgReceived); // get the list of all currently connected clients msgToSend = GetClientListMessage(); // send the message to a single client sendTFMSmsg(msgToSend, clientSocket, new AsyncCallback(OnSend)); #endregion break; case TFMS_Command.Message: // incoming message to be broadcast #region Message command Console.WriteLine("Received TFM from {0} (size={1})", getNamefromSocket(clientSocket), CI.buffer.Length); // tell the server a message broadcast was requested relayRequested(msgReceived); // copy the message to the server's outgoing message field msgToSend.strMessage = msgReceived.strMessage; #endregion break; case TFMS_Command.MsgLen: // incoming message is greater than the current buffer size #region Long Message command Console.WriteLine("Received long TFM from {0} (size={1})", getNamefromSocket(clientSocket), CI.buffer.Length); // resize the buffer for the incoming large message CI.buffer = new byte[int.Parse(msgReceived.strMessage) + TFMS_Constants.BUFFER_SIZE]; #endregion break; default: // unrecognizable command #region Unknown command // we gracefully fail and gtfoh Console.WriteLine("Cant interpret command! Aborting this relay."); Console.WriteLine("BeginReceive from {0}", getNamefromSocket(clientSocket)); // restart the receive command clientSocket.BeginReceive(CI.buffer, 0, CI.buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), clientSocket); return; #endregion } #endregion // send the message to every client logged in to the server broadcastToClients(msgToSend, clientList); // after a message is broadcast, begin listening for more incoming messages // NOTE: if many users are logged in at once, the broadcast method could block other incoming messages // for future iterations of TFMS, we want the receiving command to run on a separate thread from the sending command if (msgReceived.cmdCommand != TFMS_Command.Logout) { Console.WriteLine("Waiting for data from {0}", getNamefromSocket(clientSocket)); clientSocket.BeginReceive(CI.buffer, 0, CI.buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), clientSocket); } }
/// <summary> /// Step through the list of clients, sending the message to everybody currently logged in to the server /// This is the "meat and potatoes" of TFMS /// </summary> /// <param name="data">message to be broadcast</param> /// <param name="list">list of clients logged in to the server</param> private void broadcastToClients(TFMS_Data data, List<TFMS_ClientInfo> list) { // List commands are only sent to one recipient // MsgLen commands are also not resent to anyone if (data.cmdCommand != TFMS_Command.List && data.cmdCommand != TFMS_Command.MsgLen) { // loop through the client list, sending the message to every client currently logged in foreach (TFMS_ClientInfo c in list) { // send a message to a single client Console.WriteLine("Relaying {0} from {1} to {2}", data.cmdCommand, data.strName, c.strName); sendTFMSmsg(data, c.socket, new AsyncCallback(OnSend)); } } }
/// <summary> /// OnReceive will interpret messages received from the TFMS_Server object and handle the possible messages /// </summary> /// <param name="result">the result of the response from the server</param> public void OnReceive(IAsyncResult result) { // assume default buffer size int buffSize = TFMS_Constants.BUFFER_SIZE; try { int numBytesReceived = 0; TFMS_Data msgReceived; try { // attempt to get the number of bytes received from the server numBytesReceived = clientSocket.EndReceive(result); } catch (Exception) { disconnectDetected(null); return; } // using the received number of bytes, create a new TFMS_Data object to handle incoming data if (numBytesReceived == 0) { msgReceived = new TFMS_Data(TFMS_Command.Null, "", ""); } else { byte[] temp = new byte[numBytesReceived]; Array.Copy(byteData, temp, numBytesReceived); msgReceived = new TFMS_Data(temp); } #region Interpret incoming message command // handlccordingly process the message received switch (msgReceived.cmdCommand) { case TFMS_Command.Login: loginReceived(msgReceived); break; case TFMS_Command.Logout: logoffReceived(msgReceived); break; case TFMS_Command.Message: dataReceived(msgReceived); break; case TFMS_Command.MsgLen: // this is the only place that a long message has to do work in the client class // everything else is passed on to the client buffSize = int.Parse(msgReceived.strMessage); break; case TFMS_Command.List: listReceived(msgReceived); break; case TFMS_Command.Null: break; } #endregion byteData = new byte[buffSize]; // wait for next message from server. clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnReceive), null); } catch (ObjectDisposedException) { Console.WriteLine("This object has been disposed"); } catch (SocketException ex) { // if there is a Socket exception, clear out this client from the socket and disconnect Console.WriteLine(ex.Message); if (ex.SocketErrorCode == SocketError.ConnectionReset) this.clientSocket = null; } }
/// <summary> /// Send a message to the server that needs to be broadcast to all other clients /// </summary> /// <param name="path">the path drawn on the client's DrawingBox that will be relayed to other clients</param> public void sendMessage(string path) { // prepare the data by converting the message informatino into a byte array byte[] data = (new TFMS_Data(TFMS_Command.Message, path, strName)).ToByte(); TFMS_Data lenToSend = new TFMS_Data(TFMS_Command.MsgLen, string.Format("{0}", data.Length), strName); // send the message to the socket clientSocket.Send(lenToSend.ToByte()); Thread.Sleep(TFMS_Constants.DELAY_TIME); // begin sending the message to the server clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(OnSend), null); }
/// <summary> /// If the current TFMS_Data objects contains the Relay command, echo the status (and perform the command elsewhere) /// When a message is sent to be broadcast to other uses, attempt to log it in the SQL database /// </summary> /// <param name="data">TFMS_Data object containing the Relay command</param> private static void handleRelayRequest(TFMS_Data data) { Console.WriteLine("{0} has sent a message", data.strName); // log the message in the SQL database //logMessage(data); }
/// <summary> /// Attempt to connect to the server at the specified IP address /// </summary> /// <param name="IP_address">the IP address of the target server ("a.b.c.d" format)</param> /// <returns>true if the connection was succesful, otherwise false</returns> public bool connect(string IP_address) { try { // create the socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // get the IP addresses, then connect them with the socket IPAddress serverip = IPAddress.Parse(IP_address); IPEndPoint ipEnd = new IPEndPoint(serverip, serverPort); clientSocket.Connect(ipEnd); // try to login after connecting to the server TFMS_Data msgToSend = new TFMS_Data(TFMS_Command.Login, null, strName); clientSocket.Send(msgToSend.ToByte()); // setup buffer to receive data from the server byteData = new byte[TFMS_Constants.BUFFER_SIZE]; // start listening to the data asynchronously clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnReceive), null); // the client successfully connected Console.WriteLine("{0} successfully connected to the server!", msgToSend.strName); return true; } catch (Exception) { // the client failed to connect Console.WriteLine("Client at '{0}' failed to connect to the server!", IP_address); return false; } }
/// <summary> /// If the current TFMS_Data objects contains the List command, echo the status (and perform the command elsewhere) /// </summary> /// <param name="data">TFMS_Data object containing the List command</param> private static void handleListRequest(TFMS_Data data) { Console.WriteLine("{0} has requested a list of clients", data.strName); }
/// <summary> /// If the current TFMS_Data objects contains the Logoff command, echo the status (and perform the command elsewhere) /// </summary> /// <param name="data">TFMS_Data object containing the Logoff command</param> private static void handleLogoffRequest(TFMS_Data data) { Console.WriteLine("{0} is logging off", data.strName); }
/// <summary> /// Attempt to log the current message in the SQL database /// </summary> /// <param name="data">TFMS_Data object containing the current message</param> private static void logMessage(TFMS_Data data) { Console.WriteLine("Logging {0}'s message to database", data.strName); // Attmpt to log the message to the SQL database try { // open a new SQL connection SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnect"].ToString()); conn.Open(); // add the message to the database SqlCommand cmd = new SqlCommand("INSERT INTO TFMessage (sender, date, message) VALUES (@sent_by, @date, @msg)", conn); cmd.Parameters.AddWithValue("@sent_by", data.strName); cmd.Parameters.AddWithValue("@date", data.timeStamp); cmd.Parameters.AddWithValue("@msg", data.strMessage); cmd.ExecuteNonQuery(); Console.WriteLine("Logged {0}'s message.", data.strName); // close the connection conn.Close(); } catch (Exception e) { // print to the console if the logging failed Console.WriteLine("Logging of {0}'s message failed. {1}", data.strName, e.Message); } }
/// <summary> /// If a client is running and the server has disappeared, alert the user /// </summary> /// <param name="dataReceived">the TFMS_Data object indicating the server was lost</param> private void myClient_disconnectDetected(TFMS_Data msg) { myClient = null; MessageBox.Show("The server has been closed or has crashed. Please restart the server.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Exit(); }
/// <summary> /// If the server indicates that a message was received, alert the current user /// </summary> /// <param name="msg">the TFMS_Data object indicating a message was received</param> private void handleMessage(TFMS_Data msg) { notifyIcon1.Visible = true; notifyIcon1.BalloonTipText = string.Format("You have a TFM"); notifyIcon1.ShowBalloonTip(10 * POPUP_TIME); // make the call to add an item thread safe because chances are that this will be called from another thread if (lstMessages.InvokeRequired) lstMessages.Invoke(new Action<TFMS_Data>(delegate(TFMS_Data a) { lstMessages.Items.Add(a); }), msg); else lstMessages.Items.Add(msg); }
/// <summary> /// If the server indicates that another client has logged in, alert the current user /// </summary> /// <param name="msg">the TFMS_Data object indicating another user logged in</param> private void handleLogon(TFMS_Data msg) { notifyIcon1.BalloonTipText = string.Format("{0} has joined", msg.strName); notifyIcon1.ShowBalloonTip(POPUP_TIME); }
/// <summary> /// When the server indicates that a client has logged in, send the updated list to the current user /// </summary> /// <param name="msg">the TFMS_Data object indicating the peer list was updated</param> private void handleList(TFMS_Data msg) { notifyIcon1.ShowBalloonTip(POPUP_TIME, "List", "You got the list of peers", ToolTipIcon.Info); }