Программирование на Delphi - обмен опытом / Управление программой через Telnet интерфейс

© Зайцев Олег 1998-2004
Лучшая портативная техника. Плееры Камеры Телефоны Компьютеры
Покупателям, пришедшим на www.porta.ru по этой ссылке - дополнительная скидка 1%
Железо | Система | WEB | Компоненты | Графика | Ссылки | Мультимедиа | Сети | Прочее | Реестр | Литература

Статистика

Управление программой через Telnet интерфейс

Рекомендую:
Главная страница \ WEB программирование \ Управление программой через Telnet интерфейс

  • Управление программой через Telnet интерфейс

    Управление программой через Telnet интерфейс

    Общие вопросы Задать вопрос Наверх
    Итак, начнем с главного - почему для удаленного администрирования своей программы следует использовать именно Telnet ? Ответ на этот вопрос достаточно прост:

    Рассмотрим немного теории. Утилиту Telnet легче всего запустить через Start->Run (Пуск -> Выполнить). После запуска необходимо произвести соединение с удаленным хостом, для чего выполняется используется меню "Connect->Remote System". При этом выводится меню соединения, в котором необходимо указать три параметра: хост, порт и тип терминала. В качестве хоста указывается имя удаленного компьютера (или его IP адрес), порт можно задать двумя путями - выбором/вводом символического имени (например, telnet), или вводом номера порта. Мы будем пользоваться вторым путем, т.е. будем использовать нестандартные номера портов. Тип терминала оставим vt100.
    Утилита Telnet поддерживает параметры командой строки:

    telnet [remote_host] [port]
    
    где
    remote_host представляет собой имя или IP адрес удаленной машины.
    port номер порта. Если соединение идет по стандартному порту, то этот параметр опускается. Пример:
    telnet zaitsevov или telnet zaitsevov 5000
    
    Протокол Telnet очень прост - сначала устанавливается TCP/IP соединение с удаленной машиной. Затем, когда пользователь вводит символ, происходит его передача удаленному хосту. Для простоты будем называть его сервером.
    Далее возможно два режима работы - с локальным эхом или без локального эха (режим по умолчанию). Если работа ведется с локальным эхом, то каждый вводимый пользователем символ немедленно отображается на экране. При работе без локального эха сервер обязан создавать эхо, дублирую принимаемые данные клиенту. Это позволят тестировать канал (каждый символ проходит по кругу) и организовывать ввод данных без эха (например, для ввода пароля). Мои примеры ориентированы на работу без локального эха.
    При приеме любой информации от сервера утилита Telnet немедленно отображает его на экране. Это позволяет серверу организовывать эхо и выводить любую информацию в текстовом виде. При этом поддерживатся некоторые управляющие коды, например, код "забой", стирающий один символ.

    Пример Telnet интерфейса Задать вопрос Наверх

    Итак, приступим к разработке приложения. Создадим пустой проект и поместим на форму компонент ServerSocket1 типа TServerSocket. Зададим ему порт, например 5000. Напоминаю, что:

    Итак, в обработчике OnCreate формы пишем
    begin
     try
      ServerSocket1.Active := true;
     except
      ShowMessage('Ошибки при активации ServerSocket');
     end;
    end;
    
    Далее необходимо научиться определять моменты соединения и отключения клиента. Для этого следует создать обработчики OnClientConnect и OnClientDisconnect. Сразу отмечу, что при подключении клиента обычно принято выдывать ему заголовок, ообщающий о том, что он соединился с программой *** версии NN. С учетом этого обработчик OnClientConnect будет иметь вид:
    procedure TMain.ServerSocket1ClientConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
     Socket.SendText('Connected. Программа Telnet1 Example на проводе.'+#$0D+#$0A);
     Socket.SendText('Enter password : ');
     Connected := false;
     Memo1.Lines.Add('Произошло соединение с пользователем');
    end;
    
    При этом я хочу подчеркнуть особенность - нормально поддерживается одно соединение, для нескольких необходимы некоторые усложнения и мы их пока опустим.
    Особенности: Переменная Connected отмечает, что пользователь еще не соединился с программой (т.е. не провел свою идентификацию). Рассмотрим сразу обработчик OnClientDisconnect, он еще проще:
    // Поддержка связи по TCP/IP для удаленного конфигурирования - действия при отключении
    procedure TMain.ServerSocket1ClientDisconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
       Connected := false;
       Memo1.Lines.Add('Соединение разорвано');
    end;
    
    Итак, теперь настало время для самого интересного - написания обработчика OnClientRead. Этот обработчик вызывается всякий раз, когда от клиента приходят данные. Т.е. в свете приведенных выше теоретических замечаний это будет происходить при вводе каждого отдельного символа. Задачи обработчика: Все вышеописанное реализует примерно следующий код:
    // Поддержка связи по TCP/IP для удаленного конфигурирования - действия при получении данных
    procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    var
     s, st : string;
    begin
     s := Socket.ReceiveText;
     // Это код перевода строки ? Если да, то выполняем команду и передаем ее ответ клиенту
     if ord(s[1]) = $0D then begin
      st := ExecuteCMD(TelnetS);
      if st <> '' then
       st := #$0D + #$0A + st;
      st := st + #$0D + #$0A + '>';
      TelnetSendText(Socket, st);
      TelnetS := '';
      exit;
     end;
     // Это код клавиши BackSpace. Если да, то передадим его клиенту
     // и удалим последний символ из буфера
     if ord(s[1]) = $08 then begin
      Delete(TelnetS, length(TelnetS), 1);
      TelnetSendText(Socket, s);
      exit;
     end;
     // Добавим очередной символ к буферу
     TelnetS := TelnetS + s;
     // Передадим его клиенту для организации эха
     if connected then
      TelnetSendText(Socket, s);
    end;
    
    Как легко заметить, приведенный выше код реализует эхо, обрабатывает BackSpace и дожидается ввода команды, считая код $OD (Enter) признаком завершения ввода команды. При обнаружении этого кода вызывается функция пользователя ExecuteCMD, которая должна разобрать и проанализировать команду, выполнить ее и вернуть (при необходомости) ответ пользователю. Эта же функция занимается проверкой вводимого пользователем пароля. Так ка передача ответа/эха имеет некоторые особенности, например, необходимость удвоения символа с кодом FF и подавления передачи для реализации невидимого ввода, имеет смысл выполнить ее в виде отдельной функции:
    // Передача ответа/эха клиенту
    function TForm1.TelnetSendText(Socket: TCustomWinSocket; AText: string): boolean;
    var
     i  : integer;
     St : string;
    begin
     Result := false;
     if Not(connected) then
      exit;
     St := '';
     for i := 1 to length(AText) do
      if AText[i] <> #$FF then st := st + AText[i]
       else st := st + #$FF + #$FF;
     Socket.SendText(st);
    end;
    
    В моем примере функция ExecuteCMD имеет вид:
    // Интерретатор команд
    function TForm1.ExecuteCMD(ACmd: string): string;
    var
     UCmd, Params  : string;
    begin
     Result := '';
     Memo1.Lines.Add('Выполняется : '+ACmd);
     if Not(connected) then begin
      if UpperCase(ACmd) = '123' then begin
       Connected := true;
       Result := 'Пользователь идентифицирован !';
      end;
      exit;
     end;
     // Выделение команды
     UCmd := ACmd;
     Params := '';
     if pos(' ', UCmd) > 0 then begin
      Params := Copy(UCmd, pos(' ', UCmd)+1, Length(UCmd));
      UCmd := Copy(UCmd, 1, pos(' ', UCmd)-1);
     end;
     UCmd := Trim(UpperCase(UCMD));
     Memo1.Lines.Add('Выделена команда : '+UCmd);
     // ? или HLP или HELP - вывод справки
     if (UCmd = '?') or (UCmd = 'HLP') or (UCmd = 'HELP') then begin
      Result :=
       'Краткая справка по командам Telnet интерфейса'+CRLF+
       '  ?, HLP, HELP - вызов справки'+CRLF+
       ' EXIT - завершение работы по Telnen интерфейсу'+CRLF+
       ' HALT - немедленный останов программы'+CRLF+
       ' VER - версия программы'+CRLF+
       ' MESS <собщение> - вывод сообщения для пользователя'+CRLF+
       ' INP <собщение> - вывод сообщения для пользователя и возврат его ответа';
      exit;
     end;
     if (UCmd = 'EXIT') then begin
      ServerSocket1.Socket.Connections[0].Close;
      exit;
     end;
     if (UCmd = 'VER') then begin
      Result := 'Версия 1.00 от 27.01.2001 (C) Зайцев Олег';
      exit;
     end;
     if (UCmd = 'HALT') then begin
      halt;
     end;
     if (UCmd = 'MESS') then begin
      ShowMessage(Params);
      exit;
     end;
     if (UCmd = 'INP') then begin
      Result := InputBox(Params,'Введите ответ','');
      exit;
     end;
      Result := 'Неизвестная команда '+ACmd;
    end;
    
    Реальная система команд естественно определяется разработчиком, но рекомендуется предусмотреть следующие команды: И, наконец, в завершении следует отметить одну особенность - пользователь может завершить обмен корректно (путем ввода команды EXIT (если таковая поддерживается) или выбором опции "Отключить" в Telnet; и некорректно - путем закрытия Telnet во время обмена. В этом случае в программе будет ошибка сокета 10054. Ее имеет смысл поймать и подавить при помощи обработчика OnClientError следующего вида:
      
    procedure TForm1.ServerSocket1ClientError(Sender: TObject;
      Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    begin
     // Обработка события "разрыв соединения"
     if ErrorCode = 10054 then begin
      Socket.Close;
      ErrorCode := 0;
     end;
    end;
    
    И в завершении хочется сказать, что подобная система внедрена в несколько моих программ, испрользуемых в ОАО Смоленскэнерго и отлично себя зарекомендовала, т.к. предприятие большое и возможность удаленной настройки/управления в ряде случаев освобождает разработчика от ненужной беготни. Исходные тексты примера, обсуждаемого в данной статье, можно скачать - examples\telnet1.zip (4 кб).


    © Зайцев Олег, "Программирование на Delphi - обмен опытом" 1999-2004. При использовании любых материалов данного сайта необходимо указывать источник информации. Дата обновления: 22.11.2004. Сайт размещен на хостинге AGAVA - Хостинг от AGAVA.ru