Java: встраиваем сервер Telnet::Журнал СА 5.2004
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Архив номеров
Контакты
   

  Опросы
  Статьи

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

Принципы проектирования  

Dependency Inversion Principle. Принцип инверсии зависимостей в разработке

Мы подошли к последнему принципу проектирования приложений из серии SOLID – Dependency

 Читать далее...

Рынок труда  

Вакансия: Администратор 1С

Администратор 1С – это специалист, который необходим любой организации, где установлены программы

 Читать далее...

Книжная полка  

Книги для профессионалов, студентов и пользователей

Книги издательства «БХВ» вышли книги для тех, кто хочет овладеть самыми востребованными

 Читать далее...

Принципы проектирования  

Interface Segregation Principle. Принцип разделения интерфейсов в проектировании приложений

Эта статья из серии «SOLID» посвящена четвертому принципу проектирования приложений – Interface

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 10282
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 8475
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 8570
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 5462
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6147
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Друзья сайта  

 Java: встраиваем сервер Telnet

Архив номеров / 2004 / Выпуск №5 (18) / Java: встраиваем сервер Telnet

Рубрика: Программирование /  Анализ данных   | Дополнительные материалы

АЛЕКСАНДР ФЕФЕЛОВ

Java: встраиваем сервер Telnet

В процессе работы над одним Java-проектом я столкнулся с необходимостью удаленного управления серверным приложением. Разрабатывать полновесный графический интерфейс пользователя (GUI) мне показалось нерациональным. Ведь если GUI создать на базе Swing или SWT, то придется устанавливать дополнительные программы (JRE и собственно GUI) на клиентских рабочих местах. А если GUI построить на основе JSP, то дополнительные программы (JSP-контейнер) придется устанавливать на сервере. В то же время проект был рассчитан на эксплуатацию квалифицированным персоналом, которому нет нужды до свистулек и погремушек графического интерфейса. Значит, вполне подходящим выбором могли быть доступные удаленно интерфейс командной строки (CLI) или текстовый интерфейс пользователя (TUI). Наиболее очевидный выбор для реализации удаленных CLI и TUI – это Telnet (RFC 854). Данная статья и посвящена встраиванию функционала сервера Telnet в Java-приложения.

TelnetD

Инструмент, способный помочь в решении поставленной задачи, был быстро найден. Это TelnetD – встраиваемый многопоточный сервер Telnet, реализованный на языке Java Дитером Вимбергером (Dieter Wimberger). TelnetD поставляется в виде исходных текстов по модифицированной лицензии BSD. На момент написания статьи была доступна версия 1.0.

Архитектура TelnetD

Центральным объектом TelnetD является сервер (Daemon по терминологии разработчика), который создает все другие необходимые для работы объекты – слушателей (Listeners), менеджер оболочек (ShellManager) и менеджер терминалов (TerminalManager).

Слушатели принимают и управляют сетевыми соединениями от клиентских программ.

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

Поддержка конкретных реализаций терминалов (таких как ANSI, VT100 или xterm), возложена на менеджер терминалов.

Все перечисленные компоненты TelnetD конфигурируются с помощью объектов java.util.Properties, а значит настройки можно хранить в обычных текстовых файлах.

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

Начнем…

Для работы с TelnetD нам понадобятся:

  • дистрибутив TelnetD, включающий исходные тексты (файл telnetd-1.0.zip) и документацию (файл telnetd-1.0_docs.zip);
  • инструментарий Apache Ant. (В задачи данной статьи не входит описание работы с Ant. Будем предполагать, что Ant уже установлен и настроен.)

Развернем дистрибутив и скомпилируем TelnetD, выполнив в каталоге установки команду:

ant jar

При компиляции будут выданы предупреждения об использовании устаревшего (deprecated) метода java.lang. Thread.stop(), которые могут быть безболезненно проигнорированы. В результате компиляции в каталоге build будет создан файл telnetd.jar. (Обратите внимание на его малый размер. В моем случае – всего 86060 байт.)

Теперь скопируем все файлы из каталога src et wimpi elnetd esources в каталог build, в нем выполним команду:

java -classpath telnetd.jar net.wimpi.telnetd.TelnetD

которая запустит сервер TelnetD на выполнение в демонстрационном режиме, и попытаемся установить соединение с сервером с помощью команды:

telnet localhost 6666

Voila! Наш сервер работает.

Пример использования TelnetD

Как было сказано выше, для встраивания TelnetD в программу необходимо создать свой класс для интерпретатора командной строки. Этот класс должен реализовывать интерфейс Shell из пакета net.wimpi.telnetd.shell.

Следует отметить, что интерфейс Shell является расширением интерфейса ConnectionListener из пакета net. wimpi.telnetd.net. А это значит, что интерпретатор командной строки должен предоставлять методы, реагирующие на события соединения.

Мы создадим максимально простой интерпретатор, который будет ждать ввода пользователем какого-либо символа и выполнять соответствующее действие. Набор действий невелик – это выдача дополнительной информации о сеансе, завершение сеанса и останов сервера. Итак, код:

package telnetdtest;

import net.wimpi.telnetd.*;

import net.wimpi.telnetd.io.*;

import net.wimpi.telnetd.net.*;

import net.wimpi.telnetd.shell.*;

public class Test1Shell implements Shell {

  // Создание экземпляра интерпретатора командной строки (оболочки). Этот метод, хотя и не описан в интерфейсе Shell, должен быть

  // реализован, так как он используется менеджером оболочек для создания экземпляра конкретной оболочки

  public static Shell createShell() {

    return new Test1Shell();

  }

  // Собственно интерпретатор

  public void run(Connection con) {

    connection = con;

    termIO = (TerminalIO) connection.getTerminalIO();

    // Регистрируем этот объект в качестве обработчика событий соединения

    this.connection.addConnectionListener(this);

    // Выводим баннер, содержащий номер версии TelnetD

    termIO.write("TelnetD testbed 1"

      + CRLF + "TelnetD version: "

      + TelnetD.getReference().getVersion() + CRLF);

    termIO.flush();

    while (true) {

      // Выводим подсказку

      termIO.write(CRLF

        + "Press [i] for information," + CRLF

        + "[x] for exit," + CRLF + "[s] for shutdown"

        + CRLF + CRLF);

      termIO.flush();

      // Ждем ввода от пользователя

      int ch = termIO.read();

      // Реагируем соответствующим образом

      switch (ch) {

        case 120: // x - завершение сеанса

          termIO.write("Goobye!");

          termIO.flush();

          connection.close();

          return;

        case 115: // s - останов сервера

          termIO.write("Requiescat in pace!");

          termIO.flush();

          TelnetD.getReference().setServing(false);

          TelnetD.getReference().shutdown();

          System.exit(0);

          return;

        case 105: // i - информация о сервере и сеансе

          // Получаем данные о соединении

          ConnectionData data =

            connection.getConnectionData();

          // Выводим данные на экран

          termIO.write("Additional info:" + CRLF);

          termIO.write("Connected from: "

            + data.getHostName()

            + " [" + data.getHostAddress() + ":"

            + data.getPort() + "]" + CRLF);

          termIO.write("Guessed locale: "

            + data.getLocale() + CRLF);

          termIO.write("Negotiated terminal type: "

            + data.getNegotiatedTerminalType() + CRLF);

          termIO.write("Scrolling support: "

            + (termIO.getTerminal().supportsScrolling() ?

              "yes" : "no") + CRLF);

          termIO.write("Graphics rendition support: "

            + (termIO.getTerminal().supportsSGR() ?

              "yes" : "no") + CRLF);

          termIO.write("Negotiated columns: "

            + data.getTerminalColumns() + CRLF);

          termIO.write("Negotiated rows: "

            + data.getTerminalRows() + CRLF);

          termIO.write("Login shell: "

            + data.getLoginShell() + CRLF);

          termIO.flush();

          break;

      }

    }

  }

  // Реакция на событие соединения: таймаут

  public void connectionTimedOut(ConnectionEvent ce) {

    termIO.write("*** Connection timedout" + CRLF);

    termIO.flush();

    connection.close();

  }

  // Реакция на событие соединения: бездействие

  public void connectionIdle(ConnectionEvent ce) {

    termIO.write("*** Connection idle" + CRLF);

    termIO.flush();

  }

  // Реакция на событие соединения: запрос на разрыв соединения (Ctrl+D)

  public void connectionLogoutRequest(ConnectionEvent ce) {

    termIO.write("*** Connection logout request" + CRLF);

    termIO.flush();

  }

  // Реакция на событие соединения: разрыв соединения

  public void connectionBroken(ConnectionEvent ce) {

    termIO.write("*** Connection broken" + CRLF);

    termIO.flush();

  }

  // Реакция на событие соединения: сигнал NVT BREAK

  public void connectionSentBreak(ConnectionEvent ce) {

    termIO.write("*** Connection break" + CRLF);

    termIO.flush();

  }

  private Connection connection;

  private TerminalIO termIO;

  private final String CRLF = BasicTerminalIO.CRLF;

}

Конфигурационный файл для нашего примера описывает параметры ведения протоколов работы TelnetD, перечни известных терминалов, оболочек и слушателей с указанием реализующих их классов и параметров:

#=====================================================

# Параметры системных журналов

#=====================================================

syslog=on

syslog.media=terminal

syslog.stampformat=[yyyy-MM-dd hh:mm:ss z]

syslog.path=

#=====================================================

# Параметры отладочных журналов

#=====================================================

debuglog=off

debuglog.media=terminal

debuglog.stampformat=[yyyy-MM-dd hh:mm:ss z]

debuglog.path=

#=====================================================

# Терминалы

#=====================================================

# Известные терминалы

terminals=vt100,ansi,windoof,xterm

# Классы терминалов

term.vt100.class=net.wimpi.telnetd.io.terminal.vt100

term.ansi.class=net.wimpi.telnetd.io.terminal.ansi

term.windoof.class=net.wimpi.telnetd.io.terminal.Windoof

term.xterm.class=net.wimpi.telnetd.io.terminal.xterm

# Алиасы терминалов

term.vt100.aliases=default,vt100-am,vt102,dec-vt100

term.ansi.aliases=color-xterm,xterm-color,vt320,vt220,linux

term.windoof.aliases=

term.xterm.aliases=

#=====================================================

# Оболочки

#====================================================

# Известные оболочки

shells=myShell

#-------------------------------------------

# Параметры оболочки myShell

#-------------------------------------------

# Класс оболочки

shell.myShell.class=telnetdtest.Test1Shell

#=====================================================

# Слушатели

#=====================================================

# Известные слушатели

listeners=myListener

#-------------------------------------------

# Параметры слушателя myListener

#-------------------------------------------

# Порт, на котором слушатель ожидает соединения

myListener.port=7241

# Максимальное количество запросов на соединение (защита от флуда)

myListener.floodprotection=5

# Максимальное количество активных соединений

myListener.maxcon=10

# Максимальное количество соединений, ожидающих активизации

myListener.maxqueued=0

# Время бездействия соединения

myListener.time_to_warning=300000

# Время таймаута соединения

myListener.time_to_timedout=100000

# Периодичность проверки бездействия и таймаута

myListener.housekeepinginterval=1000

# Режим ввода

myListener.inputmode=character

# Имя оболочки, запускаемой слушателем

myListener.loginshell=myShell

# Класс фильтра соединений

myListener.connectionfilter=

Теперь создадим программу, которая запустит наш Telnet-сервер:

package telnetdtest;

import java.io.*;

import java.util.*;

import net.wimpi.telnetd.*;

public class Test1 {

  public static void main(String[] args)

    throws Exception {

    System.out.println("TelnetD testbed 1");

    // Загружаем настройки TelnetD из файла

    Properties settings = new Properties();

    settings.load(new FileInputStream("test1.properties"));

    // Создаем сервер

    TelnetD daemon = TelnetD.createTelnetD(settings);

    // Разрешаем серверу работать

    daemon.setServing(true);

  }

}

Интерфейс пользователя

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

Пакет net.wimpi.telnetd.io.toolkit предоставляет инструменты для организации более развитого диалога с пользователем. В число этих инструментов входят как активные (взаимодействующие с пользователем каким-либо образом), так и пассивные (только лишь отображающие что-либо) компоненты.

Активные компоненты представлены однострочным (класс Editfield) и многострочным (класс Editarea) редакторами, меню (класс Selection), флажком (класс Checkbox) и пейджером (класс Pager). Для работы с активным компонентом необходимо создать нужный объект, вызвать соответствующие методы для настройки параметров, вызвать метод run(), приводящий к активизации компонента, и, наконец, вызвать метод getValue() для получения результатов работы компонента.

Пассивные компоненты – это метка (класс Label), строки заголовка (класс Titlebar) и статуса (класс Statusbar). Для пассивного компонента после создания и настройки параметров необходимо вызывать метод draw(), что приведет к отображению компонента.

Посмотрим некоторые из этих компонентов в деле. Для этого изменим код метода run(…) в интерпретаторе из первого примера. Теперь он выглядит так:

  public void run(Connection con) {

    connection = con;

    termIO = (TerminalIO) connection.getTerminalIO();

    // Регистрируем этот объект в качестве обработчика событий соединения

    this.connection.addConnectionListener(this);

    while (true) {

      // Очищаем экран

      termIO.eraseScreen();

      // Создаем и отображаем строки заголовка и статуса

      Titlebar title = new Titlebar(termIO, "");

      title.setTitleText("TelnetD testbed 2");

      title.setForegroundColor(ColorHelper.YELLOW);

      title.setBackgroundColor(ColorHelper.BLUE);

      title.setAlignment(Titlebar.ALIGN_CENTER);

      title.draw();

      Statusbar status = new Statusbar(termIO, "");

      status.setStatusText("TelnetD version: "

        + TelnetD.getReference().getVersion());

      status.setForegroundColor(ColorHelper.YELLOW);

      status.setBackgroundColor(ColorHelper.BLUE);

      status.setAlignment(Statusbar.ALIGN_RIGHT);

      status.draw();

      // Выводим подсказку к меню

      termIO.homeCursor();

      termIO.moveCursor(BasicTerminalIO.DOWN, 5);

      termIO.write(CRLF + "Use arrow keys to select "

        + "command, [Enter] or [Tab] to run command"

        + CRLF + "Command:" + CRLF);

      termIO.flush();

      // Создаем меню выбора и активизируем его

      Selection menu = new Selection(termIO, "");

      menu.addOption("Editfield demo");

      menu.addOption("Editarea demo");

      menu.addOption("Exit");

      menu.addOption("Shutdown");

      menu.run();

      // Получаем выбор пользователя

      int cmd = menu.getSelected();

      // Реагируем соответствующим образом

      switch (cmd) {

        case 0: // демонстрация возможностей однострочного редактора

          termIO.eraseScreen();

          termIO.homeCursor();

          termIO.write("Editfield demo" + CRLF

            + "--------------" + CRLF);

          final int MAX_EDITFIELD_CHARS = 20;

          // Выводим подсказку к редактору

          termIO.write(CRLF + "Type any text (max "

            + MAX_EDITFIELD_CHARS + " chars), press "

            + "[Enter] or [Tab] to finish typing"

            + CRLF + "Text:" + CRLF);

          termIO.flush();

          // Создаем однострочный редактор

          Editfield editfield = new Editfield(termIO,

            "", MAX_EDITFIELD_CHARS);

          // Ждем ввода от пользователя

          editfield.run();

          // Показываем пользователю результат

          termIO.write(CRLF + "Your input: " + CRLF

            + editfield.getValue() + CRLF);

          termIO.flush();

          break;

        case 1: // демонстрация возможностей многострочного редактора

          termIO.eraseScreen();

          termIO.homeCursor();

          termIO.write("Editarea demo" + CRLF

            + "-------------" + CRLF);

          final int MAX_EDITAREA_ROWS = 4;

          // Выводим подсказку к редактору

          termIO.write(CRLF + "Type any text (max "

            + MAX_EDITAREA_ROWS + " rows), press [Tab] "

            + "to finish typing" + CRLF + "Text:" + CRLF);

          termIO.flush();

          // Создаем многострочный редактор

          Editarea editarea = new Editarea(termIO, "",

            MAX_EDITAREA_ROWS, MAX_EDITAREA_ROWS);

          // Ждем ввода от пользователя

          editarea.run();

          // Показываем пользователю результат

          termIO.write(CRLF + "Your input: " + CRLF

            + editarea.getValue() + CRLF);

          termIO.flush();    

          break;

        case 2: // завершение сеанса

          connection.close();

          return;

        case 3: // останов сервера

          TelnetD.getReference().setServing(false);

          TelnetD.getReference().shutdown();

          System.exit(0);

          return;

      }

      // Ждем, пока пользователь нажмет любую клавишу

      termIO.write(CRLF + "Press any key");

      termIO.flush();

      termIO.read();

    }

  }

Заключение

Итак, используя TelnetD, можно с минимальными затратами обеспечить возможность удаленного управления Java-приложением по протоколу Telnet.

Здесь, однако, следует отметить, что протокол Telnet передает все данные «открытым текстом», а значит, использование его в публичных сетях небезопасно. В качестве альтернативы протоколу Telnet для применения в публичных сетях можно предложить нестандартные расширения протокола Telnet или протокол SSH.

Ссылки:

  1. http://www.faqs.org/rfcs/rfc854.html – спецификация протокола Telnet.
  2. http://telnetd.sourceforge.net – домашняя страница TelnetD.
  3. http://ant.apache.org – домашняя страница Jakarta Ant.
  4. http://www.pvv.ntnu.no/~asgaut/crypto/thesis/thesis.html – вариант модификации протокола Telnet с целью повышения безопасности.
  5. http://www.ietf.org/html.charters/secsh-charter.html – материалы рабочей группы IETF Secure Shell по стандартизации протокола SSH.

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru