Доброго времени суток всем, кто то сталкивался с такой проблемой:
клиент ГУИ пишет exit, а его не удаляет из чата?
для обычного клиента я сделал вот что:
отдельный тип сообщений MessageType.USER_LEFT
в классе Client доп. метод sendLeftMessage() - посылается вместо sendTextMessage на сервер если тип сообщения MessageType.USER_LEFT
в методе run() класса Client если сообщение равно exit, то вызвать sendLeftMessage() и выйти из цикла
в классе Server в методе serverMainLoop() в беск. цикле проверку если тип сообщения равен MessageType.USER_LEFT - выйти из бесконечного цикла
теперь вопрос, а как быть с графическим клиентом?
package com.javarush.task.task30.task3008;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Server {
private static Map<String, Connection> connectionMap = new ConcurrentHashMap<>();
public static void sendBroadcastMessage(Message message) {
for (Map.Entry<String, Connection> pair :
connectionMap.entrySet()) {
try{
pair.getValue().send(message);
} catch (IOException ioException) {
System.out.println("Не удалось отправить сообщение");
ioException.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
//считали порт
int port = ConsoleHelper.readInt();
//Создавать серверный сокет java.net.ServerSocket, используя порт из предыдущего пункта.
try(ServerSocket serverSocket = new ServerSocket(port)) {
// Выводить сообщение, что сервер запущен. - ???
ConsoleHelper.writeMessage(String.format("Сервер запущен.\nIP: %s\nport: %d", serverSocket.getInetAddress().toString(), port));
//г) В бесконечном цикле слушать и принимать входящие сокетные соединения только что созданного серверного сокета.
while(true) {
Socket socket = serverSocket.accept();
// д) Создавать и запускать новый поток Handler, передавая в конструктор сокет из предыдущего пункта.
Handler handler = new Handler(socket);
handler.start();
// е) После создания потока обработчика Handler переходить на новый шаг цикла.
continue;
}
}catch (Exception e){
// ж) Предусмотреть закрытие серверного сокета в случае возникновения исключения.
// з) Если исключение Exception все же произошло, поймать его и вывести сообщение об ошибке.
System.out.println("серверный сокет закрыт из за воникновения ошибки");
System.out.println(e);
}
}
private static class Handler extends Thread{
private Socket socket;
private String serverHandshake(Connection connection) throws IOException, ClassNotFoundException {
while(true){
// 1) Сформировать и отправить команду запроса имени пользователя
connection.send(new Message(MessageType.NAME_REQUEST));
// 2) Получить ответ клиента
Message answer = connection.receive();
//// а что если answer == null
// if (answer == null)
// continue;
// 3) Проверить, что получена команда с именем пользователя
MessageType type = answer.getType();
if (type != MessageType.USER_NAME)
continue;
// 4) Достать из ответа имя, проверить, что оно не пустое
// и пользователь с таким именем еще не подключен (используй connectionMap)
String name = answer.getData();
if (name == null)
continue;
if (name.equals(""))
continue;
if (connectionMap.containsKey(name))
continue;
// 5) Добавить нового пользователя и соединение с ним в connectionMap
connectionMap.put(name, connection);
// 6) Отправить клиенту команду информирующую, что его имя принято
connection.send(new Message(MessageType.NAME_ACCEPTED));
// 8) Вернуть принятое имя в качестве возвращаемого значения
return name;
// 7) Если какая-то проверка не прошла, заново запросить имя клиента
}
}
private void notifyUsers(Connection connection, String userName) throws IOException {
//1) Пройтись по connectionMap.
for (Map.Entry<String, Connection> pair :
connectionMap.entrySet()) {
//2) У каждого элемента из п.1 получить имя клиента, сформировать команду с типом USER_ADDED и полученным именем.
String name = pair.getKey();
//4) Команду с типом USER_ADDED и именем равным userName отправлять не нужно, пользователь и так имеет информацию о себе.
if (!name.equals(userName)){
Message message = new Message(MessageType.USER_ADDED, name);
//3) Отправить сформированную команду через connection.
connection.send(message);
}
}
}
private void serverMainLoop(Connection connection, String userName) throws IOException, ClassNotFoundException {
while (true) {
//1. Принимать сообщение клиента
Message message = connection.receive();
MessageType type = message.getType();
//4. Если принятое сообщение не является текстом, вывести сообщение об ошибке
if (type == MessageType.USER_LEFT)
break;
if (type != MessageType.TEXT) {
ConsoleHelper.writeMessage(String.format("Тип сообщения %s не совпадает с %s", type, MessageType.TEXT));
continue;
}
//2. Если принятое сообщение - это текст (тип TEXT), то формировать новое текстовое сообщение путем конкатенации:
// имени клиента, двоеточия, пробела и текста сообщения.
// Например, если мы получили сообщение с текстом "привет чат" от пользователя "Боб",
// то нужно сформировать сообщение "Боб: привет чат".
String messageToAllUsersData = userName + ": " + message.getData();
Message messageToAllUsers = new Message(MessageType.TEXT, messageToAllUsersData);
//3. Отправлять сформированное сообщение всем клиентам с помощью метода sendBroadcastMessage().
sendBroadcastMessage(messageToAllUsers);
}
//5. Организовать бесконечный цикл, внутрь которого перенести функционал пунктов 10.1-10.4.
}
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//1. Выводить сообщение, что установлено новое соединение с удаленным адресом,
// который можно получить с помощью метода getRemoteSocketAddress().
ConsoleHelper.writeMessage("установлено новое соединение с удаленным адресом: " + socket.getRemoteSocketAddress());
//2. Создавать Connection, используя поле socket.
//7. Обеспечить закрытие соединения при возникновении исключения.
try(Connection connection = new Connection(socket)) {
//3. Вызывать метод, реализующий рукопожатие с клиентом, сохраняя имя нового клиента.
String newUserName = serverHandshake(connection);
//4. Рассылать всем участникам чата информацию об имени присоединившегося участника (сообщение с типом USER_ADDED).
Message newUserAddedMessage = new Message(MessageType.USER_ADDED, newUserName);
sendBroadcastMessage(newUserAddedMessage);
//5. Сообщать новому участнику о существующих участниках.
notifyUsers(connection, newUserName);
//6. Запускать главный цикл обработки сообщений сервером.
serverMainLoop(connection, newUserName);
//9. После того как все исключения обработаны, если п.11.3 отработал и возвратил нам имя,
// мы должны удалить запись для этого имени из connectionMap
// и разослать всем остальным участникам сообщение с типом USER_REMOVED и сохраненным именем.
if (newUserName != null) {
connectionMap.remove(newUserName);
Message newUserRemovedMessage = new Message(MessageType.USER_REMOVED, newUserName);
sendBroadcastMessage(newUserRemovedMessage);
}
//10. Последнее, что нужно сделать в методе run() -
// вывести сообщение, информирующее что соединение с удаленным адресом закрыто.
ConsoleHelper.writeMessage("соединение с удаленным адресом " + socket.getRemoteSocketAddress() + " закрыто");
//8. Отловить все исключения типа IOException и ClassNotFoundException,
// вывести в консоль информацию, что произошла ошибка при обмене данными с удаленным адресом.
} catch (IOException | ClassNotFoundException ioException) {
ConsoleHelper.writeMessage("Произошла ошибка при обмене данными с удаленным адресом");
}
}
}
}