Pull to refresh

Создание простейшего сервера с помощью Apache MINA

Reading time4 min
Views10K
Apache MINAApache MINA — библиотека с открытым исходным кодом для написания клиент-серверных приложений на Java. Последняя на текущий момент версия — 2.0.4 — вышла 14 июня 2011 г.

В этой статье я опишу процесс создания простейшего сервера с использованием этой библиотеки.


Что пишем?


В этой статье мы напишем простейший сервер, к которому можно будет подключиться с помощью 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)
    — вызывается при принятии подключения;
  • void sessionOpened(IoSession session)
    — вызывается после sessionCreated;
  • 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)
    — отправляет данные;
  • CloseFuture close(boolean immediately)
    — закрывает подключение. Если immediately — false, то подключение будет закрыто лишь после того, как все ожидающие отправки данные будут отправлены.

Также 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.


Полный исходный код можно скачать здесь.
Спасибо за чтение!
Tags:
Hubs:
+35
Comments17

Articles