
В этой статье я опишу процесс создания простейшего сервера с использованием этой библиотеки.
Что пишем?
В этой статье мы напишем простейший сервер, к которому можно будет подключиться с помощью telnet. При подключении сервер поприветствует клиента, по команде time скажет, сколько секунд прошло с момента подключения, а по команде q попрощается и закроет подключение.
Итак, начнём.
Подключение к проекту
Помимо основной библиотеки mina-core, требуется также подключить библиотеку slf4j (Simple Logging Facade for Java), которая используется MINA в качестве интерфейса к различным системам логирования, например, Apache log4j. Подключить требуется slf4j-api и одну из реализаций логирования. В нашем примере логирование использоваться не будет, а потому можно использовать slf4j-nop.
Точка входа и IoAcceptor
В главном методе приложения нам понадобится создать и настроить IoAcceptor — объект, который будет принимать входящие подключения. Нам понадобятся следующие его методы (IoAcceptor расширяет интерфейс IoService и некоторые методы объявлены в нём):
— возвращает так называемую «цепочку фильтров»;DefaultIoFilterChainBuilder getFilterChain()
— устанавливает обработчик — объект, в котором находится основная логика сервера;void setHandler(IoHandler handler)
— начинает прослушивать указанный адрес.void bind(SocketAddress localAddress)
Для нашего примера подойдёт NioSocketAcceptor, который является реализацией IoAcceptor для TCP/IP.
«Цепочка фильтров» и протокол
Прежде чем попасть в обработчик, все события (принятие подключения, получение данных и т.д.) проходят так называемую «цепочку фильтров». Фильтры могут выполнять любые действия, которые могли бы быть в обработчике, но не очень там уместны — логирование и т.п.
Один из фильтров стоит рассмотреть отдельно — это ProtocolCodecFilter. Задача этого фильтра — преобразовывать данные из некоторого объекта в последовательность байт при передаче и в оформленный объект из последовательности байт при приёме. Один из конструкторов фильтра выглядит так:
ProtocolCodecFilter(ProtocolCodecFactory factory)
В этой статье я не буду касаться создания своей «фабрики», своего протокола, для нашего примера подойдёт один из простейших и уже реализованных в MINA протоколов — TextLine («фабрика» — TextLineCodecFactory), который преобразует последовательность байт в строку (String) и наоборот. Помимо этого, при отправке к строке будет дописан символ конца строки, а при получении строка будет передана дальше по «цепочке фильтров» или в обработчик лишь тогда, когда будет получен тот же символ конца строки. Кодировку и символ конца строки можно указать в конструкторе:
TextLineCodecFactory(Charset charset, String encodingDelimiter, String decodingDelimiter)
Таким образом, в обработчике придётся иметь дело уже с оформленными объектами, в нашем случае — строками (String).
У объекта DefaultIoFilterChainBuilder существует несколько методов для добавления фильтров, но нам достаточно лишь одного:
void addLast(String name, IoFilter filter)
Этот метод добавляет фильтр в конец цепочки.
Итак, теперь мы можем написать метод main:
public static void main(String[] args) throws IOException
{
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.defaultCharset(), System.lineSeparator(), System.lineSeparator())));
acceptor.setHandler(new MyMinaServerHandler());
acceptor.bind(new InetSocketAddress(12345));
}
Обработчик, IoHandler и IoSession
Обработчик — это реализация интерфейса IoHandler, который содержит следующие методы:
— вызывается при принятии подключения;void sessionCreated(IoSession session)
— вызывается после sessionCreated;void sessionOpened(IoSession session)
— вызывается при закрытии подключения;void sessionClosed(IoSession session)
— вызывается при простое (по таймауту);void sessionIdle(IoSession session, IdleStatus status)
— вызывается при получении данных;void messageReceived(IoSession session, Object message)
— вызывается при отправке данных;void messageSent(IoSession session, Object message)
— вызывается при возникновении исключения.void exceptionCaught(IoSession session, Throwable cause)
В нашем примере достаточно реализовать лишь пару из них — sessionOpened и messageReceived, поэтому вместо реализации интерфейса напрямую мы можем расширять класс IoHandlerAdapter, который является пустой реализацией интерфейса, и переопределять нужные нам методы.
Объект IoSession является представлением сессии (она же подключение). Нам понадобятся некоторые из его методов:
— отправляет данные;WriteFuture write(Object message)
— закрывает подключение. Если immediately — false, то подключение будет закрыто лишь после того, как все ожидающие отправки данные будут отправлены.CloseFuture close(boolean immediately)
Также IoSession позволяет нам хранить связанные с подключением данные, и мы воспользуемся этой возможностью, чтобы сохранить время подключения. Для этого мы воспользуемся двумя методами:
Object setAttribute(Object key, Object value)
Object getAttribute(Object key)
Итак, теперь мы можем написать и обработчик:
public class MyMinaServerHandler extends IoHandlerAdapter
{
public void sessionOpened(IoSession session)
{
session.setAttribute("time", System.currentTimeMillis());
session.write("Hello!");
}
public void messageReceived(IoSession session, Object message)
{
switch (((String) message).trim())
{
case "time":
session.write(String.format("You connected %d seconds ago", (System.currentTimeMillis() - (Long) session.getAttribute("time")) / 1000));
break;
case "q":
session.write("Bye!");
session.close(false);
break;
}
}
}
Вот и всё
Запустим свежесобранный сервер и напишем в командной строке или терминале:
telnet localhost 12345
Сервер отвечает нам на команды:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
time
You connected 11 seconds ago
q
Bye!
Connection closed by foreign host.
Полный исходный код можно скачать здесь.
Спасибо за чтение!