|
| 1 | + |
| 2 | +//Jesus said to her, "Woman, what does that have to do with you and me? My hour has not yet come." (John 2:4) |
| 3 | + |
| 4 | +package com.javarush.task.task30.task3008.client; |
| 5 | + |
| 6 | +import java.io.IOException; |
| 7 | +import java.net.Socket; |
| 8 | +import com.javarush.task.task30.task3008.ConsoleHelper; |
| 9 | +import com.javarush.task.task30.task3008.Message; |
| 10 | +import com.javarush.task.task30.task3008.MessageType; |
| 11 | +import com.javarush.task.task30.task3008.Connection; |
| 12 | + |
| 13 | +public class Client extends Thread { |
| 14 | + protected Connection connection; |
| 15 | + private volatile boolean clientConnected; |
| 16 | + |
| 17 | + public static void main(String[] args) { |
| 18 | + Client client = new Client(); |
| 19 | + client.run(); |
| 20 | + } |
| 21 | + |
| 22 | + public void run() { |
| 23 | + { |
| 24 | + SocketThread socketThread = getSocketThread(); |
| 25 | + socketThread.setDaemon(true); |
| 26 | + socketThread.start(); |
| 27 | + try { |
| 28 | + synchronized (this) { |
| 29 | + wait(); |
| 30 | + } |
| 31 | + } catch (InterruptedException e) { |
| 32 | + ConsoleHelper.writeMessage("Thread error"); |
| 33 | + System.exit(1); |
| 34 | + } |
| 35 | + if (clientConnected) { |
| 36 | + ConsoleHelper.writeMessage("Connection established. To quit, type 'exit'"); |
| 37 | + while (clientConnected) { |
| 38 | + String message = ConsoleHelper.readString(); |
| 39 | + if (message.equalsIgnoreCase("exit")) { |
| 40 | + break; |
| 41 | + } else { |
| 42 | + if (shouldSendTextFromConsole()) { |
| 43 | + sendTextMessage(message); |
| 44 | + } |
| 45 | + } |
| 46 | + } |
| 47 | + } else { |
| 48 | + ConsoleHelper.writeMessage("Error at client side"); |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + protected String getServerAddress() { |
| 54 | + String serverAddress; |
| 55 | + ConsoleHelper.writeMessage("Enter server address: "); |
| 56 | + serverAddress = ConsoleHelper.readString(); |
| 57 | + return serverAddress; |
| 58 | + } |
| 59 | + |
| 60 | + protected int getServerPort() { |
| 61 | + ConsoleHelper.writeMessage("Enter server port: "); |
| 62 | + return ConsoleHelper.readInt(); |
| 63 | + } |
| 64 | + |
| 65 | + protected String getUserName() { |
| 66 | + ConsoleHelper.writeMessage("Enter User name: "); |
| 67 | + return ConsoleHelper.readString(); |
| 68 | + } |
| 69 | + |
| 70 | + protected boolean shouldSendTextFromConsole() { |
| 71 | + return true; |
| 72 | + } |
| 73 | + |
| 74 | + protected SocketThread getSocketThread() { |
| 75 | + return new SocketThread(); |
| 76 | + } |
| 77 | + |
| 78 | + protected void sendTextMessage(String text) { |
| 79 | + try { |
| 80 | + connection.send(new Message(MessageType.TEXT, text)); |
| 81 | + } catch (IOException e) { |
| 82 | + ConsoleHelper.writeMessage("Error, can't send your message..."); |
| 83 | + clientConnected = false; |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + public class SocketThread extends Thread { |
| 88 | + |
| 89 | + public void run() { |
| 90 | + String serverAddress = getServerAddress(); |
| 91 | + int serverPort = getServerPort(); |
| 92 | + try { |
| 93 | + Socket socket = new Socket(serverAddress,serverPort); |
| 94 | + connection = new Connection(socket); |
| 95 | + clientHandshake(); |
| 96 | + clientMainLoop(); |
| 97 | + |
| 98 | + } catch (IOException | ClassNotFoundException e) { |
| 99 | + notifyConnectionStatusChanged(false); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + protected void processIncomingMessage(String message) { |
| 104 | + ConsoleHelper.writeMessage(message); |
| 105 | + } |
| 106 | + |
| 107 | + protected void informAboutAddingNewUser(String userName) { |
| 108 | + ConsoleHelper.writeMessage("User " + userName + " joined to chat"); |
| 109 | + } |
| 110 | + |
| 111 | + protected void informAboutDeletingNewUser(String userName) { |
| 112 | + ConsoleHelper.writeMessage("User " + userName + " has left this chat"); |
| 113 | + } |
| 114 | + |
| 115 | + protected void notifyConnectionStatusChanged(boolean clientConnected) { |
| 116 | + synchronized (Client.this) { |
| 117 | + Client.this.clientConnected = clientConnected; |
| 118 | + Client.this.notify(); |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + protected void clientHandshake() throws IOException, ClassNotFoundException { |
| 123 | + Message message; |
| 124 | + |
| 125 | + while (!clientConnected) { |
| 126 | + try { |
| 127 | + message = connection.receive(); |
| 128 | + } catch (ClassNotFoundException e) { |
| 129 | + throw new IOException("Unexpected MessageType"); |
| 130 | + } |
| 131 | + if (message.getType() == MessageType.NAME_REQUEST) { |
| 132 | + connection.send(new Message(MessageType.USER_NAME, getUserName())); |
| 133 | + } else { |
| 134 | + if (message.getType() == MessageType.NAME_ACCEPTED) {notifyConnectionStatusChanged(true);} |
| 135 | + else throw new IOException("Unexpected MessageType");} |
| 136 | + |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + protected void clientMainLoop() throws IOException, ClassNotFoundException { |
| 141 | + Message message; |
| 142 | + |
| 143 | + while (true) { |
| 144 | + |
| 145 | + try { |
| 146 | + message = connection.receive(); |
| 147 | + } catch (Exception e) { |
| 148 | + break; |
| 149 | + } |
| 150 | + if (message.getType() == MessageType.TEXT) processIncomingMessage(message.getData()); |
| 151 | + else { |
| 152 | + if (message.getType() == MessageType.USER_ADDED) informAboutAddingNewUser(message.getData()); |
| 153 | + else { |
| 154 | + if (message.getType() == MessageType.USER_REMOVED) informAboutDeletingNewUser(message.getData()); |
| 155 | + else break; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + } |
| 160 | + throw new IOException("Unexpected MessageType"); |
| 161 | + |
| 162 | + } |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +/* |
| 167 | +Чат (22) |
| 168 | +Итак, подведем итог: |
| 169 | +• Ты написал сервер для обмена текстовыми сообщениями. |
| 170 | +• Ты написал консольный клиент, который умеет подключаться к серверу и |
| 171 | +обмениваться сообщениями с другими участниками. |
| 172 | +• Ты написал бот клиента, который может принимать запросы и отправлять данные о |
| 173 | +текущей дате и времени. |
| 174 | +• Ты написал клиента для чата с графическим интерфейсом. |
| 175 | +
|
| 176 | +Что можно добавить или улучшить: |
| 177 | +• Можно добавить поддержку приватных сообщений (когда сообщение отправляется не |
| 178 | +всем, а какому-то конкретному участнику). |
| 179 | +• Можно расширить возможности бота, попробовать научить его отвечать на |
| 180 | +простейшие вопросы или время от времени отправлять шутки. |
| 181 | +• Добавить возможность пересылки файлов между пользователями. |
| 182 | +• Добавить контекстное меню в графический клиент, например, для отправки |
| 183 | +приватного сообщения кому-то из списка участников. |
| 184 | +• Добавить раскраску сообщений в графическом клиенте в зависимости от отправителя. |
| 185 | +• Добавить блокировку сервером участников за что-либо, например, ненормативную |
| 186 | +лексику в сообщениях. |
| 187 | +• Добавить еще миллион фич и полезностей! |
| 188 | +
|
| 189 | +Ты научился: |
| 190 | +• Работать с сокетами. |
| 191 | +• Пользоваться сериализацией и десериализацией. |
| 192 | +• Создавать многопоточные приложения, синхронизировать их, применять модификатор |
| 193 | +volatile, пользоваться классами из библиотеки java.util.concurrent. |
| 194 | +• Применять паттерн MVC. |
| 195 | +• Использовать внутренние и вложенные классы. |
| 196 | +• Работать с библиотекой Swing. |
| 197 | +• Применять классы Calendar и SimpleDateFormat. |
| 198 | +
|
| 199 | +Так держать! |
| 200 | +
|
| 201 | +
|
| 202 | +Требования: |
| 203 | +1. Поздравляю, чат готов! |
| 204 | +Чат (21) |
| 205 | +У меня есть отличнейшая новость для тебя. Компонент представление (View) уже готов. Я добавил класс ClientGuiView. Он использует библиотеку javax.swing. Ты должен как следует разобраться в каждой строчке этого класса. Если тебе все понятно – это замечательно, если нет – обязательно найди ответы на свои вопросы с помощью дебага, документации или поиска в Интернет. |
| 206 | +
|
| 207 | +Осталось написать компонент контроллер (Controller): |
| 208 | +1. Создай класс ClientGuiController унаследованный от Client. |
| 209 | +2. Создай и инициализируй поле, отвечающее за модель ClientGuiModel model. |
| 210 | +3. Создай и инициализируй поле, отвечающее за представление ClientGuiView view. Подумай, что нужно передать в конструктор при инициализации объекта. |
| 211 | +4. Добавь внутренний класс GuiSocketThread унаследованный от SocketThread. Класс GuiSocketThread должен быть публичным. В нем переопредели следующие методы: |
| 212 | +а) void processIncomingMessage(String message) – должен устанавливать новое сообщение у модели и вызывать обновление вывода сообщений у представления. |
| 213 | +б) void informAboutAddingNewUser(String userName) – должен добавлять нового пользователя в модель и вызывать обновление вывода пользователей у отображения. |
| 214 | +в) void informAboutDeletingNewUser(String userName) – должен удалять пользователя из модели и вызывать обновление вывода пользователей у отображения. |
| 215 | +г) void notifyConnectionStatusChanged(boolean clientConnected) – должен вызывать аналогичный метод у представления. |
| 216 | +5. Переопредели методы в классе ClientGuiController: |
| 217 | +а) SocketThread getSocketThread() – должен создавать и возвращать объект типа GuiSocketThread. |
| 218 | +б) void run() – должен получать объект SocketThread через метод getSocketThread() и вызывать у него метод run(). Разберись, почему нет необходимости вызывать метод run в отдельном потоке, как мы это делали для консольного клиента. |
| 219 | +в) getServerAddress(), getServerPort(), getUserName(). Они должны вызывать одноименные методы из представления (view). |
| 220 | +6. Реализуй метод ClientGuiModel getModel(), который должен возвращать модель. |
| 221 | +7. Реализуй метод main(), который должен создавать новый объект ClientGuiController и вызывать у него метод run(). |
| 222 | +Запусти клиента с графическим окном, нескольких консольных клиентов и убедись, что |
| 223 | +все работает корректно. |
| 224 | +
|
| 225 | +Чат (20) |
| 226 | +Консольный клиент мы уже реализовали, чат бота тоже сделали, почему бы не сделать клиента с графическим интерфейсом? Он будет так же работать с нашим сервером, но иметь графическое окно, кнопки и т.д. |
| 227 | +Итак, приступим. При написании графического клиента будет очень уместно воспользоваться паттерном MVC (Model-View-Controller). Ты уже должен был с ним сталкиваться, если необходимо, освежи свои знания про MVC с помощью Интернет. В нашей задаче самая простая реализация будет у класса, отвечающего за модель (Model). |
| 228 | +
|
| 229 | +Давай напишем его: |
| 230 | +1) Создай класс ClientGuiModel в пакете client. Все классы клиента должны быть созданы в этом пакете. |
| 231 | +2) Добавь в него множество(set) строк в качестве final поля allUserNames. В нем будет храниться список всех участников чата. Проинициализируй его. |
| 232 | +3) Добавь поле String newMessage, в котором будет храниться новое сообщение, которое получил клиент. |
| 233 | +4) Добавь геттер для allUserNames, запретив модифицировать возвращенное множество. Разберись, как это можно сделать с помощью метода класса Collections. |
| 234 | +5) Добавь сеттер и геттер для поля newMessage. |
| 235 | +6) Добавь метод void addUser(String newUserName), который должен добавлять имя участника во множество, хранящее всех участников. |
| 236 | +7) Добавь метод void deleteUser(String userName), который будет удалять имя участника из множества. |
| 237 | +
|
| 238 | +Чат (19) |
| 239 | +Сейчас будем реализовывать класс BotSocketThread, вернее переопределять некоторые его методы, весь основной функционал он уже унаследовал от SocketThread. |
| 240 | +
|
| 241 | +1. Переопредели метод clientMainLoop(): |
| 242 | +а) С помощью метода sendTextMessage() отправь сообщение с текстом «Привет чатику. Я бот. Понимаю команды: дата, день, месяц, год, время, час, минуты, секунды.» |
| 243 | +б) Вызови реализацию clientMainLoop() родительского класса. |
| 244 | +2. Переопредели метод processIncomingMessage(String message). Он должен следующим образом обрабатывать входящие сообщения: |
| 245 | +а) Вывести в консоль текст полученного сообщения message. |
| 246 | +б) Получить из message имя отправителя и текст сообщения. Они разделены «: «. |
| 247 | +в) Отправить ответ в зависимости от текста принятого сообщения. |
| 248 | +Если текст сообщения: |
| 249 | +«дата» – отправить сообщение содержащее текущую дату в формате «d.MM.YYYY«; |
| 250 | +«день» – в формате «d«; |
| 251 | +«месяц» — «MMMM«; |
| 252 | +«год» — «YYYY«; |
| 253 | +«время» — «H:mm:ss«; |
| 254 | +«час» — «H«; |
| 255 | +«минуты» — «m«; |
| 256 | +«секунды» — «s«. |
| 257 | +Указанный выше формат используй для создания объекта SimpleDateFormat. Для получения текущей даты необходимо использовать класс Calendar и метод getTime(). |
| 258 | +Ответ должен содержать имя клиента, который прислал запрос и ожидает ответ, например, если Боб отправил запрос «время«, мы должны отправить ответ «Информация для Боб: 12:30:47«. |
| 259 | +Наш бот готов. Запусти сервер, запусти бота, обычного клиента и убедись, что все работает правильно. |
| 260 | +Помни, что message бывают разных типов и не всегда содержат «:« |
| 261 | +
|
| 262 | +Чат (18) |
| 263 | +Иногда бывают моменты, что не находится достойного собеседника. Не общаться же с самим собой :). Давай напишем бота, который будет представлять собой клиента, который автоматически будет отвечать на некоторые команды. Проще всего реализовать бота, который сможет отправлять текущее время или дату, когда его кто-то об этом попросит. |
| 264 | +
|
| 265 | +С него и начнем: |
| 266 | +1) Создай новый класс BotClient в пакете client. Он должен быть унаследован от Client. |
| 267 | +2) В классе BotClient создай внутренний класс BotSocketThread унаследованный от SocketThread. Класс BotSocketThread должен быть публичным. |
| 268 | +3) Переопредели методы: |
| 269 | +а) getSocketThread(). Он должен создавать и возвращать объект класса BotSocketThread. |
| 270 | +б) shouldSendTextFromConsole(). Он должен всегда возвращать false. Мы не хотим, чтобы бот отправлял текст введенный в консоль. |
| 271 | +в) getUserName(), метод должен генерировать новое имя бота, например: date_bot_X, где X – любое число от 0 до 99. Для генерации X используй метод Math.random(). |
| 272 | +4) Добавь метод main. Он должен создавать новый объект BotClient и вызывать у него метод run(). |
| 273 | +
|
| 274 | +Чат (17) |
| 275 | +Последний, но самый главный метод класса SocketThread – это метод void run(). Добавь его. Его реализация с учетом уже созданных методов выглядит очень просто. |
| 276 | +
|
| 277 | +Давай напишем ее: |
| 278 | +1) Запроси адрес и порт сервера с помощью методов getServerAddress() и getServerPort(). |
| 279 | +2) Создай новый объект класса java.net.Socket, используя данные, полученные в предыдущем пункте. |
| 280 | +3) Создай объект класса Connection, используя сокет из п.17.2. |
| 281 | +4) Вызови метод, реализующий «рукопожатие» клиента с сервером (clientHandshake()). |
| 282 | +5) Вызови метод, реализующий основной цикл обработки сообщений сервера. |
| 283 | +6) При возникновении исключений IOException или ClassNotFoundException сообщи главному потоку о проблеме, используя notifyConnectionStatusChanged и false в качестве параметра. |
| 284 | +
|
| 285 | +Клиент готов, можешь запустить сервер, несколько клиентов и проверить как все работает. |
| 286 | +*/ |
0 commit comments