Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Итак, начнем с главного - почему для удаленного администрирования своей программы следует использовать именно Telnet? Ответ на этот вопрос достаточно прост:
Рассмотрим немного теории. Утилиту Telnet легче всего запустить через Start->Run (Пуск -> Выполнить). После запуска необходимо произвести соединение с удаленным хостом, для чего выполняется используется меню "Connect->Remote System". При этом выводится меню соединения, в котором необходимо указать три параметра: хост, порт и тип терминала. В качестве хоста указывается имя удаленного компьютера (или его IP адрес), порт можно задать двумя путями - выбором/вводом символического имени (например, telnet), или вводом номера порта. Мы будем пользоваться вторым путем, т.е. будем использовать нестандартные номера портов. Тип терминала оставим vt100.
Утилита Telnet поддерживает параметры командой строки:
telnet [remote_host] [port]
где
Пример:
telnet zaitsevov или telnet zaitsevov 5000
Протокол Telnet очень прост - сначала устанавливается TCP/IP соединение с удаленной машиной. Затем, когда пользователь вводит символ, происходит его передача удаленному хосту. Для простоты будем называть его сервером.
Далее возможно два режима работы - с локальным эхом или без локального эха (режим по умолчанию). Если работа ведется с локальным эхом, то каждый вводимый пользователем символ немедленно отображается на экране. При работе без локального эха сервер обязан создавать эхо, дублирую принимаемые данные клиенту. Это позволят тестировать канал (каждый символ проходит по кругу) и организовывать ввод данных без эха (например, для ввода пароля). Мои примеры ориентированы на работу без локального эха.
При приеме любой информации от сервера утилита 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 halt; 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;
И в завершении хочется сказать, что подобная система внедрена в несколько моих программ, испрользуемых в ОАО Смоленскэнерго и отлично себя зарекомендовала, т.к. предприятие большое и возможность удаленной настройки/управления в ряде случаев освобождает разработчика от ненужной беготни.