Разработка клиент-серверного чата на Java. Часть 1. Немного теории и сервер
Краткое описание
На данный момент заканчиваю 2-й курс универститета, одной из лабораторных работ по курсу Java было написание чата. После того, как разобрался в теме сокетов, сериализации объектов и MVC, хотелось бы поделиться с читателим, тем более, что оно мне несказанно помогло при написании проекта.
Ну и, разумеется, учту все ошибки и недочеты, которые будут озвучены.
Немного теории
Итак, для начала. Проект будет состоять из двух частей: клиента и сервера. Клиент будет иметь GUI, написанный с помощью библиотеки Swing. Сервер GUI иметь не будет, только log-файл и небольшой вывод в консоль.
Для написания чата, нам понадобятся некоторые знания.
Сокеты
Поскольку взаимосвязь клиента и сервера у нас будет реализована с помощью сокетов, познакомимся с ними поближе.
Со́кеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Сокет — абстрактный объект, представляющий конечную точку соединения.
Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы. Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения.
Для нас это означает, что сервер будет слушать какой-либо порт (для большей универсальности, мы будем задавать порт в конфигурационном файле) и после подключения клиента, будем производить с ним какие-то действия.
Передача объектов по сети
Но просто получать сообщение от клиента нам мало. Нам хочется знать его имя, IP-адрес, а также передавать ему в ответ список подключенных пользователей. Таким образом, просто передача текстовых данных нас не устроит. У нас есть 2 выхода:
1. Передавать xml-строку с описанием всей информации
2. Передавать сериализованный объект, в котором будут храниться все необходимые нам данные в виде полей.
Не будем говорить о плюсах и минусах каждого подхода. Воспользуемся вторым вариантом. (А если понадобится, когда-нибудь потом, я напишу второй)
Итак, что такое сериализация объектов.
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.
Сериализация используется для передачи объектов по сети и для сохранения их в файлы.
В Java единственное, что нужно для сериализации объекта — имплементировать интерфейс Serializable, который является интерфейсом-маркером, т.е не содержит методов.
Пишем Сервер
Итак, краткое описание работы сервера.
Сервер работает в вечном цикле. Как только подключается новый клиент, он создает для работы с ним новый поток, оповещает уже подключенных клиентов о новом пользователей, а новичку отсылает какое-то количество последних сообщений в чате. Клиент же, при подключении сообщает о себе некоторую информациию, а также какое-то сообщение, идентифицирующее то, что он только что подключился.
Но помимо этого, надо не забыть о том, что клиенты могут и отключаться. То есть мы периодически должны обмениваться с клиентами сигналами (ping’овать друг друга), чтобы в случае отключения клиента (или сервера) все об этом узнали.
Итак, приступим. Создадим наш первый класс
Думаю, данный код, пока что, не нуждается в комментариях. Я не стал особо заморачиваться на обработке исключений, но нам ведь не это важно, верно?
Ах да, мы же хотели получать номер порта из конфигурационного файла. Создадим для конфига отдельный класс, в котором будем хранить статические поля — параметры нашего чата.
Удобнее всего хранить параметры в properties-файле, благо Java предоставляет удобный интерфейс для работы с ними.
Итак, наш properties-файл будет состоять всего из одной строки (пока что)
PORT=1234
Вся загрузка параметров происходит в блоке статической инициализации, поскольку полноценный конструктор в нашем случае — непозволительная роскошь.
Осталось только заменить в Server.java
ServerSocket socketListener = new ServerSocket(«1234»); на ServerSocket socketListener = new ServerSocket(Config.PORT); и добавить нужные import’ы
В дальнейшем, import’ы в коде буду упускать, поскольку любая IDE их сама подставит.
Итак, мы написали new ClientThread(); . Но что это такое, пока не решили. Пора исправить это.
Этот класс у нас будет отвечать за прием и передачу сообщений между клиентом и сервером, а значит, самый главный класс в чате — именно ClientThread.
Поскольку он работает в отдельном потоке, первое, что мы должны сделать — написать
А теперь подумаем, что же написать в методе run().
Итак, по порядку. Для начала, мы должны получить от клиента информацию «Ты кто такой?» в противном случае — «Давай, до свидания!». Как только мы узнали кто он такой, мы должны отправить ему последние сообщения в нашем чате.
Далее, периодически, мы должны его ping’овать его каким-нибудь запросом, чтобы убедиться, что не ведем общение с трупом.
Затем, мы должны получать от него сообщения, о которых должны уведомлять всех подключенных клиентов.
Ну и, как только мы перестали получать от него запросы — клиента следует удалить из списка доступнх пользователей.
На время забудем о ClientThread, а задумаемся «Каким образом будет происходить общение?»
Мы уже решили, что будем передавать сериализованный объект. Итак, чтоже должно быть в этом объекте?
Я остановился на следующем варианте:
1. Логин пользователя, отправившего это сообщение (или Server-Bot)
2. Собственно, само сообщение
3. Время отправки
4. Список доступных серверу клиентов (для отображения у пользователя)
Для успешной сериализации/десериализации, класс в клиенте и сервере должен быть одинаковым. Поэтому позаботимся сразу и о том, и о другом.
Думаю, всё очевидно. Также, помимо сообщений мы хотим передавать нечто вроде ping’ов.
По сути, этот класс нам не сильно-то нужен, просто потом код будет удобнее читать
Итак, приступим к написанию ClientThread
Код немного комментирован, поэтому все должны разобраться. Осталось дописать только несколько функций.
Итак, начнем по порядку.
Данная функция рассылает какое-то сообщение всем клиентам
Ах да, мы еще забыли вписать некоторые поля класса ClientThread, которые активно использовали. Итак, класс ClientThread,java целиком
Осталось разобраться с функциями getUserList() и getChatHistory
Для начала, определим еще 3 класса
По сути, сервер готов к работе, осталось только немного модифицировать 2 класса.
Методы getChatHistory() и getUserList() сделаны синхронизированными, потому что с ними могут работать несколько потоков
И, доделаем наш конфиг, так как у нас добавились некоторые параметры
Заключение
Теперь наш сервер готов к использованию. Мы познакомились с сериализацией объектов и работой с сокетами в Java.
В следующей статье (завтра-послезавтра) мы напишем клиент для нашего чата, а пока жду комментариев, особенно относительно устройства чата, в частности — «Здесь абсолютно неправильно реаизовано XXX» (разумеется, жду только аргументированных оценок)
В любом случае, данная статья не призвана стать эталоном написания чата на Java, это лишь инструмент для понимания как вообще создавать такие приложения на хорошем примере, а не на эхо-чате в 10 строк.
Рекомендованный контент
Все ясно, но проблема NetBeans ругается 🙁 Можно мне архив с сорсами?)
Ты нашел решение проблемы?
Поддержу вышеотписавшегося, можете ли дать ссылку на проет?
у меня осталась одна ошибка вот в этом месте this.timer = new Timer(DELAY, new ActionListener() < , как её исправить?
то Игорь: Подключайте таймер из библиотеки awt.
Вопрос к автору: где клиент.
Ребят, автор выложил клиент или нет еще?
У socketListener в конструкторе указан порт в виде String.Надо бы исправить.
ServerSocket socketListener = new ServerSocket(“1234”);
у кого-нидь была проблема с timer.stop()?
как ее решить?
getUserList() не видит этот метод, предлагает его объявить.Что делать?
Где продолжение? Ссылку, пожалуйста.
Перелыл весь сайт так и не нашел продолжения этой хорошей статьи! Может кто нибудь подскажет. дайте ссылку плизззз…..
Не знаю кому там всё ясно, но код вообще трудно читабельный. Пояснений во второй части вообще нету. Некоторые методы вообще вызываются не пойми как , так как находятся вне зоны видимости.
Хотя если не пытаться вдаваться в код, а просто понять общий принцип работы, то более менее понятно
хорошая статья, а вот продолжение где?
В пакете java.awt я не нашел ни одного таймера…
Алекс надо смотреть тут import java.awt.event.*;
К слову я что-то не догоняю о почему не видно вызовы getUserList и getChatHistory ? в какой класс зырить ?
В Server, там они определены. Просто из ClientThread они не видны…
Ребята кто-нибудь написал клиент к этому серверу? Поделитесь, у меня постоянно ошибки вылетают.
Ребят, вот я честно дуб-дубом в этом всем….. но мне нужна помощ как раз тех кто шарит. Хотим с кентами просто сделать свой чатик что бы там трещать на досуге, но вот проблема никто в этом не шарит, денег платить как то не охота,
кто может помочь, киньте инструкцию полную на почту tarasovdaniil22@gmail.com
Вопрос автору: а как поведёт себя сервер, если будет 10 000 000+ клиентов онлайн? сервак от количества потоков не упадёт? и сколько надо оперативной памяти под это? и ещё один вопрос не лучше ли сообщения передавать в хотя бы xml формате, на серваке брать парсером его статус(ping | text | login |…) и дальше зависимости от статуса его обрабатывать или всё таки лучше сериализованный объект передавать и на сервере не парсить а десериализовывать?
ThreadClient не очень написан. Запускать поток в конструкторе как-то не очень.
Что бы не было ошибки связанной с с timer.stop() надо подключить import javax.swing.Timer;
Проблема с классом СlientTread.
Искал ошибки, но не нашел
ServerSocket socketListener = new ServerSocket(Config.PORT);
Не пойму почему ругается, в классе Server проблема, почему то не видит в классе config, не могли бы вы помочь разобраться?
В классе ClientThread не видит методы getChatHistory и getUserList. Как решить проблему?
Эти методы находятся в классе Server, и являются статическими. Вызываются так: Server.getChatHistory(), Server.getUserList().
порт в кавычках. мдаааааа….бб ламер
в общем иди нахер…твой код безнадежно устарел…в топку его и тебя
Может кому пригодится клиент 🙂
Написал его за пару часов, и опыта в работе с потоками нет, поэтому не придирайтесь.
public class SocketClient <
private final static String address = “127.0.0.1”; // это IP-адрес компьютера, где исполняется наша серверная программа
private final static int serverPort = 6666; // здесь обязательно нужно указать порт к которому привязывается сервер
private static String userName = “”;
static Socket socket = null;
public static void main( String[] args ) <
System.out.println(“Вас приветствует клиент чата!\n”);
System.out.println(“Введите свой ник и нажмите \”Enter\””);
// Создаем поток для чтения с клавиатуры
BufferedReader keyboard = new BufferedReader( new InputStreamReader( System.in ) );
try <
// Ждем пока пользователь введет свой ник и нажмет кнопку Enter
userName = keyboard.readLine();
System.out.println();
> catch ( IOException e )
try <
try <
InetAddress ipAddress = InetAddress.getByName( address ); // создаем объект который отображает вышеописанный IP-адрес
socket = new Socket( ipAddress, serverPort ); // создаем сокет используя IP-адрес и порт сервера
// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// Конвертируем потоки в другой тип, чтоб легче обрабатывать текстовые сообщения
ObjectOutputStream objectOutputStream = new ObjectOutputStream( outputStream );
ObjectInputStream objectInputStream = new ObjectInputStream( inputStream );
new PingThread( objectOutputStream, objectInputStream );
// Создаем поток для чтения с клавиатуры
String message = null;
System.out.println(“Наберите сообщение и нажмите \”Enter\”\n”);
while (true) < // Бесконечный цикл
message = keyboard.readLine(); // ждем пока пользователь введет что-то и нажмет кнопку Enter.
objectOutputStream.writeObject( new Message( userName, message ) ); // отсылаем введенную строку текста серверу.
>
> catch ( Exception e ) < e.printStackTrace(); >
>
finally <
try <
if ( socket != null ) < socket.close(); >
> catch ( IOException e ) < e.printStackTrace(); >
>
>
>
public class ServerListenerThread implements Runnable <
private Thread thread = null;
private ObjectOutputStream objectOutputStream = null;
private ObjectInputStream objectInputStream = null;
public ServerListenerThread( ObjectOutputStream objectOutputStream, ObjectInputStream objectInputStream ) <
this.objectOutputStream = objectOutputStream;
this.objectInputStream = objectInputStream;
thread = new Thread( this );
thread.start();
>
@Override
public void run() <
try <
while (true) <
Message messageIn = (Message) objectInputStream.readObject();
if ( messageIn instanceof Ping ) <
Ping ping = (Ping) messageIn;
objectOutputStream.writeObject( new Ping() );
> else <
System.out.println(“[ ” + messageIn.getDate().toString() + ” ] ” + messageIn.getLogin() + ” : ” + messageIn.getMessage() );
>
>
>
catch ( SocketException e ) < e.getMessage(); >
catch ( ClassNotFoundException e ) < e.getMessage(); >
catch ( IOException e ) < e.getMessage(); >
>
>
3, 4) Это файла протокола обмена с сервером: Message.java и Ping.java
Собственно вот и весь клиент. А дальше уже сами наращивайте функционал и придумывайте GUIню какую хотите)
Источник статьи: http://www.pvsm.ru/java/10098
Как написать чат на java
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Это демонстрационная проект, который ставит своей целью закрепление знаний Java по созданию клиент-серверного приложения с GUI.
В проекте присутсвуют обе части чата:
Серверная часть. Поднимается только на localhost на порту 8189. Не имеет никакого GUI. Её просто нужно запустить первой (перед подключением клиентов).
Внутри есть класс для проверки авторизации BaseAuthService, в который захардкожены несколько логинов для подключения клиентов. В рамках данного проекта работа с базой данных не предусматривалось, а потом было решено сделать так. Пры логин-пароль для авторизации: login1 и pass1, login2 и pass2, login3 и pass3.
Клиентская часть. Используется для подключения к серверу на localhost на порту 8189. GUI написан на JavaFX.
Первое, что отображается при запуске клиента – это окно авторизации (первая вьюшка), где нужно вбить одну из пар логин-пароль упомянутую выше. После этого становится доступным основной интерфейс чата (вторая вьюшка) со следующими фичами:
- Можно авторизоваться под существующим пользователем или зарегестрироваться с нуля, при этом автоматически произойдёт и авторизация.
- Можно отправлять сообщение одновременно всем.
- Можно отправлять личный сообщения вида /w , например, /w nick2 Привет!.
- Можно выйти из чата написав /end или нажав кнопку Выйти. При этом попадаешь обратно в окно авторизации.
- Можно удалить свой аккаунт, если нажать кнопку Удалиться или вписать команду /delete. При этом попадаешь обратно в окно авторизации.
- Есть поле со списком участников чата находящихся online.
Чат далеко не идиален и его можно долго ещё допиливать:
- Отрефакторить код улучшив его структуру и организацию файлов.
- Добавить специальный класс для работы с сообщениями, чтобы не кидать в сокет просто строки, парсинг которые происходит по факту разбора.
- Улучшить внешний вид, сделав все иконки и закосив под нормальный клиент, чтобы хоть немного избавиться от jav’овости приложения.
- И т.д.
Но так или иначе, как бы мне не было стыдно за этот «проект», свою задачу приложение выполнело: с JavaFX работать научился и простое клиент-серверное приложение писать научился.
Источник статьи: http://github.com/slowprog/java_socket_chat