/// <summary> /// Removes the client from the client list and all of the server rooms /// </summary> /// <param name="client">Our client</param> public static void RemoveClientFromAll(ClientInfo client) { Console.WriteLine($"Client {client.GetUsername()} disconnected"); // Remove client from global list Console.WriteLine(Globals.ClientList.Remove(client) ? $"Removed client {client.GetUsername()} from client list" : $"Error removing client {client.GetUsername()} from client list"); // remove from rooms too int removedFrom = 0; foreach (var room in Globals.ServerRooms) { if (room.RemoveClient(client)) { removedFrom++; // Notify all clients of new member list List <string> toSendList = new List <string> { room.RoomName() }; toSendList.AddRange(room.GetAllClientsNames()); MessageAllClients(FormatMessage(Headers.RoomMembersResult, toSendList.ToArray())); } } MessageAllClients(FormatMessage(Headers.ClientDisconnected, client.GetUsername()));// Notify all clients that this client left Console.WriteLine($"Removed client {client.GetUsername()} from {removedFrom} rooms"); client.StopClient(); }
/// <summary> /// Sends a private message to another member /// </summary> /// <param name="memberName">Member to message</param> /// <param name="message">Unformatted message to send</param> /// <param name="client">Client sending the message</param> /// <returns>True if member was found and false otherwise</returns> public static bool MessageMember(string memberName, string message, ClientInfo client) { foreach (var curClient in Globals.ClientList) { if (curClient.GetUsername() == memberName) { curClient.MessageClient(FormatMessage(Headers.PrivMsgData, client.GetUsername(), curClient.GetUsername(), message)); client.MessageClient(FormatMessage(Headers.PrivMsgData, client.GetUsername(), curClient.GetUsername(), message)); return(true); } } return(false); }
/// <summary> /// Changes the clients username /// </summary> /// <param name="wantedUsername">The new username</param> /// <param name="client">Our client</param> /// <returns>True if username was changed and false otherwise</returns> public static bool ChangeClientUsername(string wantedUsername, ClientInfo client) { if (wantedUsername == "") { return(false); } if (!UsernameTaken(wantedUsername) && client != null) { Console.Write($"Client changed usernames. Previous username: {client.GetUsername()}\tNew username: "******"ERROR: Client wasn't in client list"); return(false); }
/// <summary> /// Sends a message to all members in a room /// </summary> /// <param name="roomName">Name of room to message</param> /// <param name="message">Unformatted message to send</param> /// <param name="client">Client sending the message</param> /// <returns>True if room exists and false if it doesn't</returns> public static bool MessageRoomMembers(string roomName, string message, ClientInfo client) { foreach (var room in Globals.ServerRooms) { // if room name is invalid or client isn't a member we continue looping if (room.RoomName() != roomName || !room.Contains(client)) { continue; } room.MessageClients(FormatMessage(Headers.RoomMsgData, roomName, client.GetUsername(), message)); return(true); } return(false); }
/// <summary> /// Runs when data is sent to the client /// </summary> /// <param name="asyncResult">Result containing the client info</param> public void DataSentCallback(IAsyncResult asyncResult) { ClientInfo client = (ClientInfo)asyncResult.AsyncState; try { if (_usingSsl) { client._clientSslStream.EndWrite(asyncResult); Console.WriteLine($"DataSentCallbackSsl(): Successfully sent message to {client.GetUsername()}"); _semaphorePool.Release(); } else { int result = client.ClientSocket().EndSend(asyncResult, out var errorCode); Console.WriteLine(errorCode == SocketError.Success ? $"Successfully sent message with size of {result} bytes to {client.GetUsername()}" : $"Error sending. code: {errorCode}"); } } catch (Exception e) { Console.WriteLine("Unhandled DataSentCallback() Exception! " + e); } }
/// <summary> /// Handles the commands sent from the client to the server /// </summary> /// <param name="command">The command received from the client</param> /// <param name="client">Client that sent the command</param> public static void CommandHandler(string command, ClientInfo client) { // Our format is // COMMAND<EOL>COMMAND DATA<EOF> string[] commandStrings = command.Split(new[] { "<EOL>" }, StringSplitOptions.None); switch (commandStrings[0]) { // Set username // USERNAME<EOL>username<EOF> // Will return // USERNAMERESULT<EOL>TRUE?FALSE?USERNAME<EOL>NOTE<EOF> // USERNAMECHANGE<EOL>OLDUSERNAME<EOL>NEWUSERNAME<EOF> case Headers.Username: string oldUsername = client.GetUsername(); // trim to remove white space from begging and end if (ChangeClientUsername(commandStrings[1].Trim(), client)) { client.MessageClient(FormatMessage(Headers.UsernameResult, "TRUE", client.GetUsername(), "Change successful")); MessageAllClients(FormatMessage(Headers.UsernameChange, oldUsername, client.GetUsername())); // Message all clients of username change } else { client.MessageClient(FormatMessage(Headers.UsernameResult, "FALSE", client.GetUsername(), "Change unsuccessful; username taken")); } break; // Make room // MAKEROOM<EOL>ROOMNANE<EOF> // Will return // MAKEROOMRESULT<EOL>TRUE?FALSE<EOL>NOTE<EOL>ROOMCREATEDNAME<EOF> case Headers.MakeRoom: if (CreateRoom(commandStrings[1].Trim(), client)) { client.MessageClient(FormatMessage(Headers.MakeRoomResult, "TRUE", "Created room successfully", commandStrings[1].Trim())); MessageAllClients(FormatMessage(Headers.GetRoomsResult, GetRoomNameList().ToArray())); // Send all clients all of the rooms } else { client.MessageClient(FormatMessage(Headers.MakeRoomResult, "FALSE", "Error creating room. Room name could be taken")); } break; // Delete room(can only be done by server or client who made it) // REMOVEROOM<EOL>ROOMNAME<EOF> // Will return // REMOVEROOMRESULT<EOL>TRUE?FALSE<EOL>NOTE<EOF> // ROOMREMOVED<EOL>ROOMNAME<EOF> case Headers.RemoveRoom: if (RemoveRoom(commandStrings[1].Trim(), client)) { client.MessageClient(FormatMessage(Headers.RemoveRoomResult, "TRUE", "Removed room successfully")); MessageAllClients(FormatMessage(Headers.RoomRemoved, commandStrings[1].Trim())); // Notify clients that the room was removed } else { client.MessageClient(FormatMessage(Headers.RemoveRoomResult, "FALSE", "Error removing room. Not owner or invalid room name")); } break; // Get all rooms // GETROOMS<EOF> // Will return rooms in format // GETROOMSRESULT<EOL>RM1<EOL>RM2<EOF> case Headers.GetRooms: client.MessageClient(FormatMessage(Headers.GetRoomsResult, GetRoomNameList().ToArray())); break; // Join room // JOINROOM<EOL>ROOMNAME<EOF> // Will return // JOINROOMRESULT<EOL>TRUE?FALSE<EOF> case Headers.JoinRoom: client.MessageClient(AddClientToRoom(commandStrings[1].Trim(), client) ? FormatMessage(Headers.JoinRoomResult, "TRUE") : FormatMessage(Headers.JoinRoomResult, "FALSE")); break; // Leave room // LEAVEROOM<EOL>ROOMNAME<EOF> // Will return // LEAVEROOMRESULT<EOL>TRUE?FALSE<EOF> case Headers.LeaveRoom: client.MessageClient(RemoveClientFromRoom(commandStrings[1].Trim(), client) ? FormatMessage(Headers.LeaveRoomResult, "TRUE") : FormatMessage(Headers.LeaveRoomResult, "FALSE")); break; // Get room members // ROOMMEMBERS<EOL>ROOMNAME<EOF> // Will return // ROOMMEMBERSRESULT<EOL>ROOMNAME<EOL>MEM1<EOL>MEM2<EOF> case Headers.RoomMembers: List <string> roomMembers = new List <string> { commandStrings[1].Trim() }; if (GetRoomMembers(commandStrings[1].Trim(), ref roomMembers)) { client.MessageClient(FormatMessage(Headers.RoomMembersResult, roomMembers.ToArray())); } break; // Message to room // ROOMMSG<EOL>ROOMNAME<EOL>MSG<EOF> // Will return to client // ROOMMSGRETURN<EOL>TRUE?FALSE<EOF> // Will send to all room members // ROOMMSGDATA<EOL>ROOMNAME<EOL>SENDERNAME<EOL>MSG<EOF> // msg will be formatted in <username> CLIENTMSG case Headers.RoomMsg: client.MessageClient(MessageRoomMembers(commandStrings[1].Trim(), commandStrings[2].Trim(), client) ? FormatMessage(Headers.RoomMsgReturn, "TRUE") : FormatMessage(Headers.RoomMsgReturn, "FALSE")); break; // Message from client to client // PRIVMSG<EOL>DESTUSERNAME<EOL>MSG<EOF> // Will return to client // PRIVMSGRETURN<EOL>TRUE?FALSE<EOF> // Will send to dest client // PRIVMSGDATA<EOL>FROMUSERNAME<EOL>TOUSERNAME<EOL>MSG<EOF> case Headers.PrivMsg: client.MessageClient(MessageMember(commandStrings[1].Trim(), commandStrings[2].Trim(), client) ? FormatMessage(Headers.PrivMsgReturn, "TRUE") : FormatMessage(Headers.PrivMsgReturn, "FALSE")); break; // CLIENTDISCONNECTING<EOF> // Will return to all // CLIENTDISCONNECTED<EOL>USERNAME<EOF> case Headers.ClientDisconnecting: RemoveClientFromAll(client); break; // Ping pong // PING<EOF> // Will return // PONG<EOF> case Headers.Ping: client.MessageClient(FormatMessage(Headers.Pong)); break; default: // We don't know the command break; } }
/// <summary> /// Removes an existing room /// </summary> /// <param name="roomName">Room name to remove</param> /// <param name="client">Client trying to remove the room</param> /// <returns>True if room was removed and false otherwise</returns> public static bool RemoveRoom(string roomName, ClientInfo client) { foreach (var room in Globals.ServerRooms) { if (room.RoomName() == roomName && room.IsOwner(client)) { // Send a message to the room that it's getting removed room.MessageClients(FormatMessage(Headers.RoomMsgData, roomName, "SERVER", $"Room owner {client.GetUsername()} has deleted the room.")); room.RemoveAllClients(); // Remove the room from the room list Console.WriteLine(Globals.ServerRooms.Remove(room) ? $"Successfully deleted room {roomName}" : $"Error removing room {roomName} from room list"); return(true); } } return(false); }
/// <summary> /// Runs on a new client connection, and sets a client id and adds to list of clients. /// </summary> /// <param name="asyncResult">Result from connection containing socket to client</param> public static void ClientConnectCallback(IAsyncResult asyncResult) { try { Socket clientSocket = Globals.Listener.EndAccept(asyncResult); NetworkStream networkStream = new NetworkStream(clientSocket); SslStream sslStream = new SslStream(networkStream, false); // Authenticate the server but don't require the client to authenticate. try { sslStream.AuthenticateAsServer(Globals.ServerCertificate, false, true); // Display the properties and settings for the authenticated stream. HelperMethods.DisplaySecurityLevel(sslStream); HelperMethods.DisplaySecurityServices(sslStream); HelperMethods.DisplayCertificateInformation(sslStream); HelperMethods.DisplayStreamProperties(sslStream); // Set timeouts to 5 seconds. sslStream.ReadTimeout = 5000; sslStream.WriteTimeout = 5000; } catch (AuthenticationException e) { Console.WriteLine($"Exception: {e.Message}"); if (e.InnerException != null) { Console.WriteLine($"Inner exception: {e.InnerException.Message}"); } Console.WriteLine("Authentication failed - closing the connection."); sslStream.Close(); clientSocket.Close(); Globals.Listener.BeginAccept(ClientConnectCallback, null);// keep listening return; } catch (Exception e) { Console.WriteLine($"Exception: {e.Message}"); sslStream.Close(); clientSocket.Close(); Globals.Listener.BeginAccept(ClientConnectCallback, null);// keep listening return; } byte[] clientId = new byte[10]; Globals.Random.NextBytes(clientId); string idString = clientId.Aggregate("", (current, idByte) => current + idByte); // Keep trying until we get a username that isn't taken while (HelperMethods.UsernameTaken(idString)) { Globals.Random.NextBytes(clientId); idString = clientId.Aggregate("", (current, idByte) => current + idByte); } ClientInfo newClient = new ClientInfo(clientSocket, sslStream, networkStream); newClient.SetUsername(idString);// Set the default username to the randomly generated byte id Globals.ClientList.Add(newClient); Console.WriteLine($"Client {newClient.GetUsername()} connected"); newClient.BeginReceiveData(); Globals.Listener.BeginAccept(ClientConnectCallback, null); } catch (ObjectDisposedException) { Console.WriteLine("ClientConnectCallback(): Object already disposed"); } catch (SocketException se) { Console.WriteLine(se.Message); } }