Итак, начнем с головного - почему для удаленного администрирования собственной программки следует использовать конкретноTelnet? Ответ на этот вопрос довольно прост:
УтилитаTelnetесть на любом компьютере с операционной системой Windows, UNIX, AIX и т.п., потому ее не требуется писать либо устанавливать
Telnet является штатным средством удаленного администрирования.
Telnet предполагает текстовый обмен, потому его очень просто поддерживать в собственной программке
Способностей текстового терминала обычно довольно для управления программкой, ее опции и администрирования
Разглядим незначительно теории. Утилиту Telnet легче всего запустить черезStart->Run(Запуск ->Выполнить). После пуска нужно произвести соединение с удаленным хостом, зачем производится употребляется меню "Connect->Remote System". При всем этом выводится меню соединения, в каком нужно указать три параметра: хост, порт и тип терминала. В качестве хоста указывается имя удаленного компьютера (или его IP адрес), порт можно задать 2-мя способами - выбором/вводом символического имени (например, telnet), либо вводом номера порта. Мы будем воспользоваться вторым методом, т.е. будем использовать неординарные номера портов. Тип терминала оставимvt100.
Утилита Telnet поддерживает характеристики командой строчки:
telnet [remote_host] [port]
где
remote_host представляет собой имя либо IP адресок удаленной машины.
port номер порта. Если соединение идет по стандартному порту, то этот параметр опускается.
Пример:
telnet zaitsevov либо telnet zaitsevov 5000
Протокол Telnet очень прост - поначалу устанавливается TCP/IP соединение с удаленной машиной. Потом, когда юзер вводит знак, происходит его передача удаленному хосту. Для простоты будем именовать его сервером.
Дальше может быть два режима работы - с локальным эхом либо без локального эха (режим по умолчанию). Если работа ведется с локальным эхом, то каждый вводимый юзером знак немедля отображается на дисплее. При работе без локального эха сервер должен создавать эхо, дублирую принимаемые данные клиенту. Это позволят тестировать канал (каждый знак проходит по кругу) и организовывать ввод данных без эха (например, для ввода пароля). Мои примеры нацелены на работу без локального эха.
При приеме хоть какой инфы от сервера утилита Telnet немедля показывает его на дисплее. Это позволяет серверу организовывать эхо и выводить всякую информацию в текстовом виде. При всем этом поддерживатся некие управляющие коды, к примеру, код "забой", стирающий один знак.
Итак, приступим к разработке приложения. Сделаем пустой проект и поместим на форму компонентServerSocket1типаTServerSocket. Зададим ему порт, к примеру 5000. Припоминаю, что:
номер порта должен быть необычным, чтоб не пересекаться с другими программками. При всем этом лучше считывать его из INI файла, что даст возможность опции по мере надобности.
Свойство Active должно быть false и устанавливаться в true при запуске программки. По другому приложение упадет при попытке пуска 2-ой копии либо при отсутствии сети. Установку Active:= true следует делать в блоке try... except
Итак, в обработчике 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;
При всем этом я желаю выделить особенность - нормально поддерживается одно соединение, для нескольких нужны некие усложнения и мых их пока опустим.
Особенности: Выводить информацию при соединении лучше на британском языке. Это дает возможность избежать ситуации, когда на компьтере админа не окажется поддержки российского языка и Telnet выведет ему бессмыслицу. У меня это наблюдается повсевременно на британской NT 4 - приходится всякий раз лазить в опции Telnet и задавать российскийCharSet.
При соединении следует спросить пароль. По другому каждый, кому нечего делать, залезет в программку и будет там колупаться (из практики - преценденты были).
Переменная Connected отмечает, что юзер еще не объединился с программкой (т.е. не провел свою идентификацию). Разглядим сходу обработчикOnClientDisconnect, он еще проще:
Код: // Поддержка связи по TCP/IP для удаленного конфигурирования - деяния при выключении
procedure TMain.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Connected:= false;
Memo1.Lines.Add('Соединение разорвано');
end;
Итак, сейчас пришло время для самого увлекательного - написания обработчикаOnClientRead. Этот обработчик вызывается каждый раз, когда от клиента приходят данные. Т.е. в свете приведенных выше теоретических замечаний это будет происходить при вводе каждого отдельного знака. Задачки обработчика:
Создавать (при необходимости) эхо для всех принимаемых знаков. Разумеется, что при вводе паролей эхо создавать не надо. При созании эха нужно учесть, что знак с кодом FF (буква "я") должен повторяться два раза, по другому он будет погложен Telnet - ом как служебный и не отобразится
Копить вводимые знаки, ждя прихода признака конца команы. Обычно, признаком конца команды считают перевод код строчки (следует увидеть, что здесь разработчик сам для себя эталон, но отклоняться от принятых правил не рекомендуется. Для скопления принимаемой инфы стоит завести буферную переменную, в моем случае она будет называться TelnetS.
При получении знака с кодом 08h ("BackSpace") нужно не помещать ее в буфер, а стереть из буфера последний знак. Но в виде эха его выслать нужно, т.к. это приведет к стиранию знака на дисплее Telnet (при угнетении эха он остается на дисплее, но сотрется в буфере программки, что приведет к путанице).
При обнаружении знака перевода строчки (код $0D) следует считать содержимое буфера командой и интерпретировать. Как - это отдельный разговор
Все вышеперечисленное реализует приблизительно последующий код:
Код: // Поддержка связи по 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;
Настоящая система команд естественно определяется разработчиком, но рекомендуется предугадать последующие команды:
?, HLP, HELP для вывода справочной инфы (практика показала, что при поддерке 20-30 команд более половины забываются за месяц)
EXIT - окончание обмена
И, в конце концов, в окончании необходимо подчеркнуть одну особенность - юзер может окончить обмен корректно (путем ввода команды 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;
|