I've implemented the asked functionality using a switch-case statement which is obvious. After all, we even use an enum. But it does not want to validate. Does somebody see a problem in my code? Otherwise I rewrite to use if - if else... but that really would be a pity if that's the problem... crazy, crazy coding. This task is challenging, don't confuse me with trivia, CodeGym 😜🤪
package com.codegym.task.task30.task3008;
// The server's main class
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Let's move on to the most important part: writing the Server class.
* The server must support multiple simultaneous connections with different clients.
* This can be done using the following algorithm:
* - The server creates a server socket connection.
* - In a loop, the server waits for some client to connect to the socket.
* - It creates a new Handler thread on which messages will be exchanged with the client.
* - It waits for another connection.
*/
public class Server {
/**
* 1) A static Map<String, Connection> connectionMap field, where the key is the client name,
* and the value is the connection with the client.
* 2) Initialization of the field from step 7.1 using the appropriate Map from the java.util.concurrent library,
* since this field will be accessed from different threads and we need to ensure thread safety.
*/
private static Map<String, Connection> connectionMap = new ConcurrentHashMap<>();
/**
* 1) To the Server class, a nested private static Handler class that inherits Thread.
*/
private static class Handler extends Thread {
/**
* 2) To the Handler class, a Socket socket field.
* 3) To the Handler class, a constructor that has a Socket parameter and uses it to initialize the appropriate field.
*/
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
/**
* Chat (part 8)
* The Handler class must implement the client communication protocol.
* Let's identify independent stages of the protocol and implement them in separate methods:
* The first stage is the handshake (in which the server meets the client).
* We'll implement it in the private String method serverHandshake(Connection connection) throws IOException,
* ClassNotFoundException method. The method takes a connection as an argument, and returns the name of the new client.
*/
private String serverHandshake(Connection connection) throws IOException, ClassNotFoundException {
// we have a Connection and a Socket... let's start
// 8.7) If any verification step fails, request the client's name again
while (true) {
// 8.1) Generate and send a username request
connection.send(new Message(MessageType.NAME_REQUEST));
// 8.2) Get the client's response
Message m = connection.receive();
// 8.3) Verify that the response has a username
if (m.getType() != MessageType.USER_NAME) {
ConsoleHelper.writeMessage("Not a user name");
continue;
}
// 8.4) Extract the name from the response, and verify that it is not empty and is not already being used (use connectionMap)
String name = m.getData();
if (name.isEmpty()) {
ConsoleHelper.writeMessage("Enter a valied user name");
continue;
}
if (connectionMap.containsKey(name)) {
ConsoleHelper.writeMessage("User name already exists");
continue;
}
connectionMap.put(name, connection);
// adding the connection to the connection map succeeded
// 8.6) Send an informational message to the client, informing it that the name was accepted
connection.send(new Message(MessageType.NAME_ACCEPTED, "Name has been accepted"));
// 8.8) Return the accepted name as the return value
return name;
} // end while
} // end serverHandshake
/**
* Chat (part 9)
* The second stage, which is no less important, is sending information to the client (new participant) about
* the other clients (chat participants).
*
* To do this, add a private void notifyUsers(Connection connection, String userName) throws IOException method,
* where connection is the connection with the participant we are sending information to, and userName is that participant's name.
* @param connection
* @param userName
*/
private void notifyUsers(Connection connection, String userName) throws IOException {
/**
* 9.1) Iterate over connectionMap.
* 9.2) For each element in step 1, get the client name, generate a USER_ADDED message that includes the passed name.
* 9.3) Use the connection field to send the generated message.
* 9.4) Don't send a USER_ADDED message to the user whose name is equal to userName.
*/
for (String existingUser : connectionMap.keySet()) {
if (!existingUser.equals(userName))
connection.send(new Message(MessageType.USER_ADDED, existingUser));
}
}
/**
* 10
* The third stage is the server's main message-processing loop.
* Add a private void serverMainLoop(Connection connection, String userName) throws IOException, ClassNotFoundException
* method, where the meaning of the parameters is the same as in the notifyUsers() method.
*/
private void serverMainLoop(Connection connection, String userName) throws IOException, ClassNotFoundException {
/**
* 10.1) Receive client messages
* 10.2) If a received message is text (TEXT), then generate a new text message by concatenating the client name,
* a colon, a space, and the text message. For example, if we received a message with the text
* "Hi, everyone" from the user "Bob", then you would need to generate this message: "Bob: Hi, everyone".
* 10.3) Send the generated message to all clients using the sendBroadcastMessage() method.
* 10.4) If the received message is not text, display an error message
* 10.5) Create an infinite loop and move the functionality of steps 10.1-10.4 into the loop.
*/
while (true) {
Message message = connection.receive();
if (message.getType() == MessageType.TEXT) {
sendBroadcastMessage(new Message(MessageType.TEXT, String.format("%s: %s", userName, message.getData())));
} else {
ConsoleHelper.writeMessage("Message is not of type TEXT");
}
}
}
/**
* Chat (part 11)
* It's time to write the main method of the Handler class, which will call all
* the helper methods we wrote earlier. Implement the void run() method in the Handler class.
*/
@Override
public void run() {
/**
* 1) Display a message indicating that a new connection was established with a remote
* address that can be obtained using the getRemoteSocketAddress() method.
* 2) Create a Connection object using the socket field.
* 3) Call the method that implements the handshake with the client, and store the name of the new client.
* 4) Send the name of the new participant (USER_ADDED message) to all chat participants.
* Think about which method is most suitable for this.
* 5) Inform the new participant about the existing participants.
* 6) Start the server's main message-processing loop.
* 7) Be sure that the connection is closed if an exception occurs.
* 8) If any IOExceptions or ClassNotFoundExceptions occur, catch them and display a message indicating
* that an error occurred while communicating with the remote address.
* 9) After handling any exceptions, if step 11.3 has finished and returned a name, we need to remove
* from connectionMap the entry for that name and send a USER_REMOVED message containing the name to all other participants.
* 10) The last thing we need to do in the run() method is display a message
* indicating that the connection with the remote address is closed.
*/
String clientName = "";
ConsoleHelper.writeMessage("A new connection was established with a remote address " + socket.getRemoteSocketAddress()); // 1
try (Connection connection = new Connection(socket)) { // 2, 7
clientName = serverHandshake(connection); // 3
sendBroadcastMessage(new Message(MessageType.USER_ADDED, clientName)); // 4
notifyUsers(connection, clientName); // 5
serverMainLoop(connection, clientName); // 6
} catch (IOException | ClassNotFoundException e) {
ConsoleHelper.writeMessage("An error occurred while communicating with the remote address");
} finally {
connectionMap.remove(clientName);
sendBroadcastMessage(new Message(MessageType.USER_REMOVED, clientName)); // 4
ConsoleHelper.writeMessage("Connection with the remote address has been closed");
}
}
} // end Handler class
/**
* 7.3) A static void sendBroadcastMessage(Message message) method, which should send the message argument
* to all connections in connectionMap. If an IOException occurs while the message is being sent,
* you need to catch it and inform the user that the message couldn't be sent.
*/
public static void sendBroadcastMessage(Message message) {
for (Map.Entry<String, Connection> connection : connectionMap.entrySet()) {
try {
connection.getValue().send(message);
} catch (IOException ioe) {
ConsoleHelper.writeMessage("Message to " + connection.getKey() + " could not be sent!");
}
}
}
public static void main(String[] args) throws IOException {
/**
* 4) The Server's main method should:
* a) Request a server port using ConsoleHelper.
* b) Create a java.net.ServerSocket object using the port from the previous step.
* c) Display a message indicating that the server is running.
*/
// Start the server
ConsoleHelper.writeMessage("Enter server port: ");
int serverPort = ConsoleHelper.readInt();
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(serverPort);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
ConsoleHelper.writeMessage("Server is up and running...");
// listen and start new handler (handler is for managing each single connection)
/**
* d) In an infinite loop, listen for and accept incoming socket connections with the newly created server socket.
* e) Create and start a new Handler thread, passing the socket from the previous step to the constructor.
* e) Once the Handler thread is created, proceed to the next iteration of the loop.
* g) Be sure that the server socket is closed if there is an exception.
* h) If an Exception occurs, catch it and display an error message.
*/
try {
while (true) {
// Socket socket = serverSocket.accept();
new Handler(serverSocket.accept()).start();
}
} catch (IOException ioe) {
ConsoleHelper.writeMessage("An error occured establishing a connection");
} finally {
if (serverSocket != null)
serverSocket.close();
}
}
}