Skip to content

Задание 2 Messenger

Dmitry Arkhangelskiy edited this page Apr 5, 2016 · 12 revisions

Проект Мессенджер

Описание

Разработка сервера для мессенджера - системы обмена сообщениями. Основной упор на сетевое взаимодействие, многопоточную обработку и работу с базой данных.

Название ветки для разработки [вашгитхабаккаунт]-messenger

Техническое задание

Сообщения

  • взаимодействие сервер-клиент происходит с помощью сообщений, с каждым действием связано соответствующее сообщение (логин, логаут, добавление в чат и т д). Сообщение инкапсулирует в себе все необходимы данные для обработки.
  • мессенджер поддерживает обработку сообщений от пользователя в специальном формате, начинающемся с /
  • в случае ошибки обработки сообщения, сервер возвращает сервисное сообщение со статусом ошибки (неправильные аргументы, не залогинен и т д)

Ниже описан консольный интерфейс команд. Для кодирования типа сообщения используем enum Type, общий предок всех сообщений в системе Message

public abstract class Message implements Serializable {

    private Long id;
    private Long senderId;
    private Type type;
}

// Список кодов сообщений
public enum Type {
    MSG_LOGIN,
    MSG_TEXT,
    MSG_PASS,
    MSG_HELP,
    MSG_INFO,
    MSG_CHAT_LIST,
    MSG_CHAT_CREATE,
    MSG_CHAT_HIST
}

У каждой команды со стороны клиента есть текстовое название (для консольного интерфейса)

MSG_HELP

/help

показать список команд и общий хэлп по месседжеру

MSG_LOGIN

/login <логин_пользователя> <пароль>

/login arhangeldim qwerty

залогиниться (если логин не указан, то авторизоваться). В случае успеха приходит вся инфа о пользователе

MSG_PASS

/pass <old_pass> <new_pass>

/pass qwerty 123qaz

сменить пароль (только для залогиненных пользователей)

MSG_INFO

/info [id]

/info инфа о себе

/info 3 - инфа о пользователе id=3

получить всю информацию о пользователе, без аргументов - о себе (только для залогиненных пользователей)

MSG_CHAT_LIST

/chat_list

получить список чатов пользователя(только для залогиненных пользователей). От сервера приходит список id чатов

MSG_CHAT_CREATE

/chat_create <user_id list>

/chat_create 1,2,3,4 - создать чат с пользователями id=1, id=2, id=3, id=4

/chat_create 3 - создать чат с пользователем id=3, если такой чат уже существует, вернуть существующий

создать новый чат, список пользователей приглашенных в чат (только для залогиненных пользователей).

MSG_CHAT_HIST

/chat_history <chat_id>

/chat_history 2 - сообщения из чата id=2

список сообщений из указанного чата (только для залогиненных пользователей)

MSG_TEXT

/text <id> <message>

/text 3 Hello, it's pizza time! - отправить указанное сообщение в чат id=3

отправить сообщение в заданный чат, чат должен быть в списке чатов пользователя (только для залогиненных пользователей)

Сетевое взаимодействие

В рамках проекта будем использовать сокеты (java.net.Socket & java.net.ServerSocket). Необходимо обеспечить асинхронное взаимодействие между клиентом и сервером. Сервер должен уметь обрабатывать несколько соединений одновременно. На каждое новое подключение создается объект сессии arhangel.dim.core.net.Session, который инкапсулирует в себе информацию о клиенте и in/out каналы сокета для чтения и записи данных. Сессия должна реализовывать интерфейс

public interface ConnectionHandler {

    /**
     * Отправить сообщение.
     * Требуется обработать 2 типа ошибок
     * @throws ProtocolException - ошибка протокола (не получилось кодировать/декодировать)
     * @throws IOException - ошибка чтения/записи данных в сеть
     */
    void send(Message msg) throws ProtocolException, IOException;

    /**
     * Реакция на сообщение, пришедшее из сети
     */
    void onMessage(Message msg);

    /**
     * Молча (без проброса ошибок) закрываем соединение и освобождаем ресурсы
     */
    void close();

}

Протокол

Чтобы передавать по сети сложные данные (объекты, коллекции) нужно разработать протокол общения. Протокол может быть текстовым или бинарным. Текстовый протокол проще для отладки, можно использовать формат json для представления объекта в виде строки. Итоговая строка конвертируется в byte[] - байтовый массив и отправляется в сокет. Бинарый протокол лучше, с точки зрения производительности и трафика, но сложнее в отладке. можно разработать свой протокол, можно воспользоваться встроенным механизмом сериализации.

JSON http://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/ (ссылка в репозиторий maven и примеры там есть)

Serializable http://skipy.ru/technics/serialization.html http://www.ccfit.nsu.ru/~deviv/courses/oop/java_ser_rus.html

Интерфейс протокол описан в arhangel.dim.core.net.Protocol (Для выполнения задания реализуйте BinaryProtocol в том же пакете)

public interface Protocol {

    Message decode(byte[] bytes) throws ProtocolException;

    byte[] encode(Message msg) throws ProtocolException;

}

Материалы по сериализации

http://habrahabr.ru/post/60317/ http://skipy.ru/technics/serialization.html http://www.codeproject.com/Tips/991180/Java-Sockets-and-Serialization

Сериализация через сокет выглядит очень просто, достаточно обернуть stream

socket = new Socket(InetAddress.getLocalHost(), portNumber);

// Обернем стримы и получим стримы для чтения и записи объектов
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket1.getOutputStream());

Message msg = new LoginMessage();

// На одной стороне пишем
oos.writeObject(msg);
oos.flush();


// С другой стороны читаем
// Класс Message должен implements Serializable
Message msg = (Message) ois.readObject();

Архитектура сервиса

На стороне сервера можно выделить следующие сущности -

  • User(пользователь)
  • Message(Сообщение)
  • Chat(Разговор, чат)

У каждой сущности есть уникальный идентификатор (Long id). Данные на сервере хранятся в хранилище (базе данных), за взаимодействие с бд отвечают интерфейсы UserStore (пользователи) и MessageStore (сообщения и чаты). С помощью этих интерфейсов можно получить объекты из БД.

Примерная диаграмма классов

alt tag

Примерная схема потока данных (данные идут как по стрелкам клиент-сервер, так и в обратную сторону)

alt tag

Описание

Общение пользователей происходит в чатах. Чат - это разговор 2х и более пользователей. Каждый залогиненый пользователь может создать чат командой /chat_create <ids>, где <ids> - это список участников чата.

При попытке создать диалог (2 участника), возвращается существующий чат (как личные сообщения в соц сетях). При создании мультичата (> 2 участников) - создается новый, даже если существует чат с таким же набором участников.

Клиент может получить список чатов, в которых он принимал участие и просмотреть историю переписки (все данные запрашиваются с сервера). Все текстовые сообщения сохраняются на сервере, служебные сообщения не нужно сохранять в истории.

На стороне сервера можно выделить следующие сущности

  • User(пользователь)
  • Message(Сообщение)
  • Chat(Разговор, чат)

У каждой сущности есть уникальный идентификатор (Long id).

Данные на сервере хранятся в хранилище (базе данных), за взаимодействие с бд отвечают интерфейсы UserStore (пользователи) и MessageStore (сообщения и чаты). С помощью этих интерфейсов можно получить сущности.

Оценка

Код должен состоять из независимых модулей, общающихся друг с другом через интерфейсы. Модуль должен быть вынесен в отдельный package. Не забывайте, что при хорошем ООП дизайне, каждый класс должен решать определенную задачу и, желательно, только её. Также большое внимание уделите оформлению кода и его чистоте: Code Style Guide Полный гайд от гугла на англ. Некоторые рекоммендации на рус.

В коде нужно правильно обрабатывать исключительные ситуации и уметь объяснить, почему обработка происходит таким образом. Чтобы определиться с исключениями, подумайте какую задачу вы решаете и как должна себя вести система в том или ином случае. Исключение - это обработка именно нештатных ситуаций, неожидаемое поведение.

Для обработки и хранения данных правильно используйте коллекции. Нужно уметь объяснить свой выбор той или иной структуры данных, знать основные свойства (контракт, скорость доступа, добавление, удаление элементов).

Для хорошего решения задачи нужно также продумать устройство базы данных, структуру таблиц и их связь.

Большое внимание уделите работе с потоками и корректной их остановке.

Итого, на оценку влияет

  • архитектура приложения (разделение на модули и классы, распределение ответсвенности между ними)
  • использование интерфейсов и наследования
  • ошибки разработчика (незакрытые ресурсы, необработанные исключения, неправильная проверка условий)
  • выбор способа хранения данных (правильно ли выбран коллекция для задачи, правильно ли выбран алгоритм, структура таблиц базы данных)
  • читаемость кода и его оформление

График сдачи

10 ноября

Оценка выставляется из 15 баллов

  • 15 баллов - до 18-00 10.11
  • 10 баллов - до 18-00 17.11
  • 0 баллов - позже

минимум

  • Клиент-серверное взаимодействие - реализован класс Server и Client, они взаимодействуют через сокеты
  • Многопоточный сервер - сервер умеет обрабатывать подключение от нескольких клиентов в разных потоках.
  • Обработка команд клиента - клиент отправляет сообщение на сервер, а сервер генерит какой-либо ответ.
  • Авторизация клиента - клиент при первом обращении к серверу проходит авторизацию. Логин и пароль запрашиваются по сокету.
  • Для хранилища юзеров и сообщений есть интерфейсы (логику хранилищ можно не реализовывать, а сделать заглушки).

средне

  • Клиент-серверное взаимодействие - асинхронное (на основе паттерна Observer)
  • Сервис поддерживает различные чаты (разговоры). На стороне клиента при отправке сообщения указываем, в какой чат отправить. Сообщения увидят
  • только участники указанного чата.

хорошо

  • Нужно поработать над протоколом - механизмом преобразования объектов в byte[] и обратно. Это может быть сделано с помощью сериализации.

http://habrahabr.ru/post/60317/ http://skipy.ru/technics/serialization.html http://www.codeproject.com/Tips/991180/Java-Sockets-and-Serialization

Сериализация через сокет выглядит очень просто, достаточно обернуть stream

socket = new Socket(InetAddress.getLocalHost(), portNumber);

// Обернем стримы и получим стримы для чтения и записи объектов
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket1.getOutputStream());

Message msg = new LoginMessage();

// На одной стороне пишем
oos.writeObject(msg);
oos.flush();


// С другой стороны читаем
// Класс Message должен implements Serializable
Message msg = (Message) ois.readObject();
  • По сети должны передаваться объекты-сообщения.
  • Поддерживается команда получения на клиенте полной истории разговора по chatId

24 ноября (РК1)

Практика оценивается из 20 баллов (сдается на занятии, 24го числа, если сдаете позже на неделю - из 10. Иначе - 0)

Основное ТЗ - 15 баллов. Еще 5 баллов (задачи помечены как (доп)) за использование шаблонных подходов - ConnectionPool, DAO/QueryExecutor, логирование. Также посмотрите технические критерии оценки в разделе выше. Пожалуйста, отформатируйте код и прочитайет его еще раз перед сдачей. Подумайте, будет ли он понятен другим людям, есть ли в нем вещи, которые вы не можете объяснить. Прочитайте, что такое рефакторинг.

  • Архитектура проекта примерно соответствует указанной выше

    • есть клиентская и серверная часть
    • клиент и сервер общаются через сообщения (некие java objects). Важно чтобы между клиентом и сервером не ходил plain text (типа ввод//вывод на консоль через сокет)
    • для преобразования Message Object -> byte[] и обратно используется сериализация (java serialization / json serialization)
  • Реализована работа с сетью

    • взаимодействие клиента/сервера через сокеты
    • асинхронное взаимодействие (на каждого клиента создается socket handler - поток обслуживающий клиентский сокет)
    • (доп) со стороны сервера для клиентских конектов используется ConnectionPool (ExecutorService)
  • Работают команды

    • авторизация/логин
    • создания чата
    • отправки сообщения в чат
    • получение истории чата
  • Реализована работа с базой:

    • Бизнес логика реализована в соответсвие с интерфейсами UserStore/MessageStore
    • (доп) Работа с jdbc вынесена в отдельный класс (На основе паттерна DAO/QueryExecutor). То есть работа с Connection/Statement/ResultSet отделена от бизнес-логики (запрос и обработка данных)
    • Используются PreparedStatement (где можно)
    • (доп) Используется ConnectionPool
  • Инфраструктурные задачи

    • Проект собирается с помощью maven скрипта (pom.xml)
    • (доп) Для отладочной информации используется логирование (библиотека log4j например)
    • Написаны тесты для протокола сериализации

15 декабря (РК2)

Оценка 30 баллов

ТЗ:

  • Поправить замечания к РК1

  • Выполнить все пункты РК1

  • Сервер должен поддерживать 2 режима работы (старый код не нужно удалять) - ServerSocket (io)/ ServerSocketChannel (nio). То есть нужно абстрагироваться от механизма чтения данных по сети.

Рекомендую сделать интерфейс Server

interface Server {
  void startServer();
  void destroyServer();
}
  
class SocketServer implements Server {}

class NioServer implements Server {}
  
  • Изучите примеры с занятия по nio. Напишите свой nio сервер, который слушает селектор в одном потоке, получает сигналы с него и раздает данные потокам обработчикам.

    • Для потоков-обработчиков должен использоваться пул потоков (ExecutorService)
    • Для передачи данных между потоками используйте BlockingQueue
    • Клиент также должен поддерживать nio (NioClient)
    • Нужно разобраться в технологии nio - это центральная тема РК2 и по ней будет больше всего вопросов на защите
  • Нужно написать метод остановки сервера (обычного и nio)

    • обработать закрытие всех клиентских конектов
    • закрыть ресурсы
    • остановить треды-обработчики и дождаться что все треды закончили исполнение
  • Не забывайте, что сериализация - обязательна. между клиентом и сервером не должны ходить plain text - только объекты

  • Приложение должно компилироваться, запускаться и работать без падений, обрабатывайте исключения так, чтобы можно было продолжить работу

  • Написать интеграционные тесты проверяющие

    • выдачу сообщения об ошибке при попытке написать сообщение не залогинившись
    • сообщение об ошибке при попытке войти с несуществующими креденшиалами
    • успешный вход после попытки войти с существующими креденшиалами
    • успешную отправку сообщения в "дефолтный" чат
    • успешное создание чата с другим пользователем
    • успешную отправку сообщения в созданный чат
    • успешное получение сообщения вторым клиентом
    • успешное получение истории сообщений