Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.118.102.225] |
|
Сообщ.
#1
,
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Мониторинг сетей в Delphi
Вступление:В этой статье мы рассмотрим некоторые функции, которые предоставят нам возможность контроля локальной сети. Так как материал довольно обширный, я не буду пускаться в отвлеченные рассуждения, и буду давать только самую суть. Обратите внимание на места с подчеркнутым текстом, так как там находятся именно те подводные камни, о которые часто спотыкается неопытный программист. Материал будет даваться по следующему принципу:
К статье прилагается программа, демонстрирующая совместную работу всех рассматриваемых функций, и её полный исходный код. Совет: прочитайте внимательно всю статью, так как те места, которые прокомментированы в начале статьи, я не буду объяснять повторно в ее конце или середине. Примечание:
Надеюсь, после прочтение данного материала у вас исчезнут большинство вопросов "Как". Возможно, появятся вопросы "Почему", в этом случае я всегда готов помочь, так как только из-за обилия вопросов "Как" и практически полного отсутствия "Почему", я и взялся за написание этой статьи. ;) Итак, начнем. Краткая таблица приведения типов:А начнем мы с небольшого отступления. Кроме тех функций, которые будут приведены в данной статье, существует еще много полезных, достойных вашего изучения. Есть одно "Но". Описания этих функций даны в MSDN с учетом синтаксиса C++, а не Delphi, я попытаюсь исправить этот недостаток, и приведу небольшую таблицу, цель которой помочь вам в трансляции описания функций и структур на Delphi. Таблица не может претендовать на полноту, возможно я где-то напутал (надеюсь что нет) с диапазоном значений, но основной принцип приведения здесь показан правильно.
Определение доступных ресурсовИтак, рассмотрим функции, предоставляющие нам информацию о наших локальных ресурсах и возможность их контроля. Объявление функции для Windows 9х - Ме: var NetShareEnum :function (pszServer : PChar; sLevel : Cardinal; pbBuffer : PChar; cbBuffer : Cardinal; pcEntriesRead, pcTotalAvail : Pointer ):DWORD; stdcall; Параметры: Объявление функции для Windows NT: var NetShareEnum :function (ServerName :PWChar; Level :DWORD; Bufptr :Pointer; Prefmaxlen :DWORD; EntriesRead, TotalEntries, resume_handle:LPDWORD): DWORD; stdcall; Параметры: В случае успешного выполнения результат обеих функций равен нулю. Обратите внимание, на то, что функция использующаяся в Windows 9x-Me получает именно указатель на массив структур, в то время как другая функция получает адрес указателя, это критично!!! Результаты выполнения будут сохранены в массиве структур переданных функции при ее вызове. Существует 6 типов структур передаваемых функции NetShareEnum
Я остановлюсь на двух из них (по одной для каждого типа Windows :) Структура share_info_50: Объявление структуры: type TShareInfo50 = packed record shi50_netname : array [0..12] of Char; shi50_type : Byte; shi50_flags : Word; shi50_remark : PChar; shi50_path : PChar; shi50_rw_password : array [0..8] of Char; shi50_ro_password : array [0..8] of Char; end; Поля: Реально получить значение двух последних полей можно только при получении информации о своём компьютере, в остальных случаях они остаются пустыми. Структура SHARE_INFO_2: Объявление структуры: type TShareInfo2 = packed record shi2_netname : PWChar; shi2_type : DWORD; shi2_remark : PWChar; shi2_permissions : DWORD; shi2_max_uses : DWORD; shi2_current_uses : DWORD; shi2_path : PWChar; shi2_passwd : PWChar; end; PShareInfo2 = ^TShareInfo2; TShareInfo2Array = array [0..512] of TShareInfo2; PShareInfo2Array = ^TShareInfo2Array; Поля: Итак, теперь у вас есть вся информация для написания первой программы. Создадим новый проект. Добавим на форму два элемента - ListBox, назовем его lbxShares и Button, назовем ее btnGetShares. В интерфейсную часть модуля добавим описания структур и функций, так, как они были даны выше. Единственное отличие функцию для NT мы назовем NetShareEnumNT, для того чтобы не было двух одинаковых функций с разными параметрами. У вас должно получится что-то вроде этого: unit Main; interface uses ...; type TMainForm = class(TForm) lbxShares: TListBox; btnGetShares: TButton; private { Private declarations } public { Public declarations } end; type TShareInfo50 = packed record ... end; type TShareInfo2 = packed record ... end; PShareInfo2 = ... var NetShareEnumNT:function (...): DWORD; stdcall; NetShareEnum:function (...): DWORD; stdcall; var MainForm: TMainForm; Implementation {$R *.dfm} end. Теперь реализуем вызов функций. Первоначально нам нужно определится, под какой системой мы работаем, чтобы узнать какую часть кода (для NT или нет) использовать в данный момент. Для этого напишем небольшую функцию, которая и будет определять тип системы. function TMainForm.IsNT(var Value: Boolean): Boolean; var Ver: TOSVersionInfo; BRes: Boolean; begin Ver.dwOSVersionInfoSize := SizeOf(TOSVersionInfo); BRes := GetVersionEx(Ver); if not BRes then begin Result := False; Exit; end else Result := True; case Ver.dwPlatformId of VER_PLATFORM_WIN32_NT : Value := True; //Windows NT - подходит VER_PLATFORM_WIN32_WINDOWS : Value := False; //Windows 9x-Me - подходит VER_PLATFORM_WIN32s : Result := False; //Windows 3.x - не подходит end; end; Если данная функция вернула результат True, значит, определение версии системы прошло успешно, и тип системы будет храниться в переменной Value (True - значит NT, False - 9x, Me), в противном случае определение типа системы неудачно или система является Windows 3.x, в этом случае придется завершить выполнение программы. Данная функция будет у нас практически самой главной, так как она будет указывать нашей программе какую часть кода исполнять. Теперь обратите внимание, наши функции только объявлены, но за ними не закреплено никакого исполняемого кода. Если бы мы использовали статическое связывание и заранее определили библиотеки и имена библиотечных функций, гарантированно получили бы в процессе выполнения ошибку, так как библиотека SVRAPI отсутствует в Windows NT, а NETAPI32 отсутствует в Windows 9x-Me. Для этого мы должны после определения типа системы загрузить требуемую библиотеку, и уже только после этого связать наши функции с библиотечными. Итак, наши действия: определяем, тип системы, загружаем требуемую библиотеку, получаем адреса функций и выполняем функцию. procedure TMainForm.btnGetSharesClick(Sender: TObject); var i:Integer; FLibHandle : THandle; ShareNT : PShareInfo2Array; //<= Перемеменные entriesread,totalentries:DWORD; //<= для Windows NT Share : array [0..512] of TShareInfo50; //<= Переменные pcEntriesRead,pcTotalAvail:Word; //<= для Windows 9x-Me OS: Boolean; begin lbxShares.Items.Clear; if not IsNT(OS) then Close; //Определяем тип системы if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); //Загружаем библиотеку if FLibHandle = 0 then Exit; //Связываем функцию @NetShareEnumNT := GetProcAddress(FLibHandle,'NetShareEnum'); if not Assigned(NetShareEnumNT) then //Проверка begin FreeLibrary(FLibHandle); Exit; end; ShareNT := nil; //Очищаем указатель на массив структур //Вызов функции if NetShareEnumNT(nil,2,@ShareNT,DWORD(-1), @entriesread,@totalentries,nil) <> 0 then begin //Если вызов неудачен выгружаем библиотеку FreeLibrary(FLibHandle); Exit; end; if entriesread > 0 then //Обработка результатов for i:= 0 to entriesread - 1 do lbxShares.Items.Add(String(ShareNT^[i].shi2_netname)); end else begin //Код для 9х-Ме FLibHandle := LoadLibrary('SVRAPI.DLL'); //Загружаем библиотеку if FLibHandle = 0 then Exit; //Связываем функцию @NetShareEnum := GetProcAddress(FLibHandle,'NetShareEnum'); if not Assigned(NetShareEnum) then //Проверка begin FreeLibrary(FLibHandle); Exit; end; if NetShareEnum(nil,50,@Share,SizeOf(Share), @pcEntriesRead,@pcTotalAvail) <> 0 then //Вызов функции begin //Если вызов неудачен выгружаем библиотеку FreeLibrary(FLibHandle); Exit; end; if pcEntriesRead > 0 then //Обработка результатов for i:= 0 to pcEntriesRead - 1 do lbxShares.Items.Add(String(Share[i].shi50_netname)); end; FreeLibrary(FLibHandle); //Не забываем выгрузить библиотеку end; При выполнении этого кода ListBox заполнится названиями общих ресурсов, которые берутся из массива структур переданных функции. Как вы поняли, массив заполняется в результате выполнения функции. Немного модифицировав код (см. описание структур) вы получите комментарии к ресурсу, пароли, пути к папкам общих ресурсов и т.д. Вы можете заметить, при возникновении ошибок, я просто завершал работу процедуры оператором Exit. Это сделано только для того, чтобы не загромождать код обработчиками, в реальной программе такое, естественно, не допустимо. Примечание: обычно в NT используются функции NetApiBufferAllocate и NetApiBufferFree - при помощи них выделяется и освобождается память под массив структур. Я вам показал немного другое объявление структуры TShareInfo2Array = array [0..512] of TShareInfo2; Здесь память уже выделена. Я предпочитаю пользоваться именно таким объявлением структуры, поэтому эти функции в данной статье рассмотрены не будут. При желании вы можете посмотреть в MSDN принцип их использования. Закрытие локального ресурсаПолучать список общих ресурсов вы научились. Теперь рассмотрим функцию NetShareDel которая позволит нам закрыть выбранный общий ресурс. Обьявление функции для 9х - Ме Windows: var NetShareDel:function (pszServer, pszNetName :PChar; usReserved :Word ): DWORD; stdcall; Параметры:
ServerName - должен содержать имя удаленного компьютера, если закрываем свои ресурсы то данному параметру нужно присвоить NIL. Как вы заметили, обе функции не используют никаких структур. Нас интересует только второй параметр, содержащий имя закрываемого ресурса (подробнее по остальным параметрам см. MSDN). В качестве имени передается не путь к ресурсу, а именно имя ресурса которое мы определили при помощи кода данного выше. В случае успешного выполнения функций, их результат будет равен нулю. Итак, добавим к нашей программе еще одну кнопку и назовем ее btnCloseShares. Обработчик этой кнопки будет содержать код, определяющий текущий выбранный элемент в списке текущих общих ресурсов, и будет передавать его имя (Strings[xx]) в качестве второго параметра функции. (Не забудьте добавить объявления функций в интерфейсную часть программы, и как и в прошлый раз функция для NT будет иметь название NetShareDelNT(:) ) procedure TMainForm.btnCloseSharesClick(Sender: TObject); var OS:Boolean; FLibHandle : THandle; Name9x:array [0..12] of Char; NameNT:PWChar; i:Integer; ShareName: String; begin if not IsNT(OS) then Close; //Определяем тип системы if lbxShares.Items.Count = 0 then Exit; for i:= 0 to lbxShares.Items.Count -1 do if lbxShares.Selected[i] then Break; //Ищем выбранный элемент if not lbxShares.Selected[i] then Exit; //Если не найден уходим ShareName := lbxShares.Items.Strings[i]; if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetShareDelNT := GetProcAddress(FLibHandle,'NetShareDel'); if not Assigned(NetShareDelNT) then //Проверка begin FreeLibrary(FLibHandle); Exit; end; i:= SizeOf(WideChar)*256; GetMem(NameNT,i); //Выделяем память под переменную StringToWideChar(ShareName,NameNT,i); //Преобразуем в PWideChar NetShareDelNT(nil,NameNT,0); //Удаляем ресурс FreeMem(NameNT); //Освобождаем память end else begin //Код для 9х-Ме FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle = 0 then Exit; @NetShareDel := GetProcAddress(FLibHandle,'NetShareDel'); if not Assigned(NetShareDel) then //Проверка begin FreeLibrary(FLibHandle); Exit; end; FillChar(Name9x, SizeOf(Name9x), #0); //Очищаем массив move(ShareName[1],Name9x[0],Length(ShareName)); //Заполняем массив NetShareDel(nil,@Name9x,0); //Удаляем ресурс end; FreeLibrary(FLibHandle); end; Заметьте, что я опять не делаю никаких проверок на результат выполнения функции, как я объяснял выше, во первых, чтобы не загромождать демонстрационный код, а во вторых - в данной процедуре не используются результаты выполнения функций (они не критичны), но в реальной программе вы обязаны обработать результаты и в случае неуспешного выполнения (результат не равен нулю) уведомить об этом пользователя. Обратите внимание, что в версии кода для Windows 9x-Me я преобразую имя ресурса ShareName в массив Char и передаю указатель на него. Это обязательное действие, так как PСhar(ShareName) попросту не сработает. Подробнее о том почему выбран именно такой размер массива смотрите в MSDN Открытие локального ресурсаТеперь нам следует научится открывать общий ресурс. Для этого воспользуемся функцией NetShareAdd Объявление функции для 9х - Ме Windows: var NetShareAdd: function ( pszServer :Pchar; SLevel :Cardinal; PbBuffer :PChar; CbBuffer :Word):DWORD; stdcall; Параметры: Обьявление функции для Windows NT: var NetShareAdd: function ( servername : PWideChar; level : DWORD; buf : Pointer; parm_err : LPDWORD): DWORD; stdcall; Параметры: Заметьте что в функции для NT также используется указатель а не адрес указателя. Обе функции используют структуры описанные выше. Для демонстрации их возможностей воспользуемся уже разобранными нами структурами share_info_50 и SHARE_INFO_502. Параметр parm_err в рамках этой статьи я разбирать не буду, подробности в MSDN, можете присвоить ему nil. Итак, добавьте объявления функций в интерфейсную часть модуля (не забудьте про переименование процедуры для NT), добавьте еще одну кнопку на форму и измените ее имя на btnAddShares. В обработчик onclick этой кнопки мы поместим код позволяющий нам выбрать папку, общий доступ к которой мы хотим открыть. Для этого нам нужно написать еще одну небольшую функцию, показывающую диалог выбора папки. Вот она: function TMainForm.SelectDirectory: String; var lpItemID : PItemIDList; BrowseInfo : TBrowseInfo; DisplayName : array[0..MAX_PATH] of Char; TempPath : array[0..MAX_PATH] of Char; begin FillChar(BrowseInfo, sizeof(TBrowseInfo), #0); BrowseInfo.hwndOwner := Handle; BrowseInfo.pszDisplayName := @DisplayName; BrowseInfo.lpszTitle := 'Specify a directory'; BrowseInfo.ulFlags := BIF_RETURNONLYFSDIRS; lpItemID := SHBrowseForFolder(BrowseInfo); if Assigned(lpItemId) then begin SHGetPathFromIDList(lpItemID, TempPath); GlobalFreePtr(lpItemID); end else Result := ''; Result := String(TempPath); end; Для выполнения данной функции добавьте в раздел Uses модули ShellAPI и ShlObj. Вот сам код открытия ресурса: procedure TMainForm.btnAddSharesClick(Sender: TObject); const STYPE_DISKTREE = 0; ACCESS_ALL = 258; SHI50F_FULL = 258; var FLibHandle : THandle; Share9x : TShareInfo50; ShareNT : TShareInfo2; TmpDir, TmpName: String; TmpDirNT, TmpNameNT: PWChar; OS: Boolean; TmpLength: Integer; begin TmpDir := SelectDirectory; //Определяем путь к будущему ресурсу TmpName := InputBox('Share name','Enter name','Test'); //Определяем имя под которым он будет виден в сети if TmpDir = '' then Exit; if not IsNT(OS) then Close; //Выясняем тип системы if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetShareAddNT := GetProcAddress(FLibHandle,'NetShareAdd'); if not Assigned(NetShareAddNT) then begin FreeLibrary(FLibHandle); Exit; end; TmpLength := SizeOF(WideChar)*256; //Определяем необходимый размер GetMem(TmpNameNT, TmpLength); //Конвертируем в PWChar StringToWideChar(TmpName, TmpNameNT, TmpLength); ShareNT.shi2_netname := TmpNameNT; //Имя ShareNT.shi2_type := STYPE_DISKTREE; //Тип ресурса ShareNT.shi2_remark := ''; //Комментарий ShareNT.shi2_permissions := ACCESS_READ; //Доступ ShareNT.shi2_max_uses := DWORD(-1); //Кол-во максим. подключ. ShareNT.shi2_current_uses := 0; //Кол-во тек подкл. GetMem(TmpDirNT, TmpLength); StringToWideChar(TmpDir, TmpDirNT, TmpLength); ShareNT.shi2_path := TmpDirNT; //Путь к ресурсу ShareNT.shi2_passwd := nil; //Пароль NetShareAddNT(nil,2,@ShareNT,nil); //Добавляем ресурс FreeMem (TmpNameNT); //освобождаем память FreeMem (TmpDirNT); end else begin //Код для 9x FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle = 0 then Exit; @NetShareAdd := GetProcAddress(FLibHandle,'NetShareAdd'); if not Assigned(NetShareAdd) then begin FreeLibrary(FLibHandle); Close; end; FillChar(Share9x.shi50_netname, SizeOf(Share9x.shi50_netname), #0); move(TmpName[1],Share9x.shi50_netname[0],Length(TmpName)); //Имя Share9x.shi50_type := STYPE_DISKTREE; //Тип ресурса Share9x.shi50_flags := SHI50F_RDONLY; //Доступ FillChar(Share9x.shi50_remark, SizeOf(Share9x.shi50_remark), #0); //Комментарий FillChar(Share9x.shi50_path, SizeOf(Share9x.shi50_path), #0); Share9x.shi50_path := PAnsiChar(TmpDir); //Путь к ресурсу FillChar(Share9x.shi50_rw_password, SizeOf(Share9x.shi50_rw_password), #0); //Пароль полного доступа FillChar(Share9x.shi50_ro_password, SizeOf(Share9x.shi50_ro_password), #0); //Пароль для чтения NetShareAdd(nil,50,@Share9x,SizeOf(Share9x)); end; FreeLibrary(FLibHandle); end; Обратите внимание на то, что переменные TmpNameNT и TmpDirNT освобождаются только после выполнения функции. Это критично, в противном случае структура переданная функции будет с двумя "незаполненными" полями. Также обратите внимание на то что в версии кода для Windows 9х все поля - представляющие собой массив Char элементов, вначале очищаются функцией FillChar, во избежание искажения данных (знак окончания строки равен #0). Данный код открывает новый ресурс на полный доступ. Рассматривать все виды открытия в данной статье я не имею возможности, за подробностями опять же отсылаю к MSDN. Скрытие и показ ресурсовЭта глава будет маленькая, никакого кода приводить не буду. Только объясню как можно добиться скрытия и показа ресурсов. Вы наверное уже обратили внимание на ресурсы в имени которых в конце стоит знак доллара. Это так называемые системные ресурсы. Манипулировать с ними я крайне не советую. Объясню почему. Во первых, ко всем этим ресурсам стоит полный доступ. Во вторых, при закрытии некоторых из системных ресурсов доступ к вашему компьютеру по сети может исчезнуть. В третьих, однажды я проэксперементировал с активацией и скрытием над системными ресурсами, после этого возникли неожиданные осложнения. Я попытался сделать активным ресурс "C$" в Windows XP, после этого я его, естественно, скрыл и перезагрузил систему. Каково же было мое удивление, когда я увидел что у меня появился ресурс "C" ( "C$" тоже присутствовал) дающий полный доступ к моему системному диску. Повторное его закрытие ничего не дало, ресурс снова появлялся после каждой перезагрузки системы. Проблема решилась тотальной правкой реестра (ключей уже к сожалению не помню). И это еще не самое страшное что может произойти. Как вы уже поняли скрыть ресурс можно простым добавлением к его имени знака доллара. Активировать, обратной операцией. Это естественно не правильно, но работает. Второй вариант скрыть ресурс, это удалить его и создать копию его но с правами SHI50F_SYSTEM или указанием shi502_security_descriptor (для этого нужно использовать структуру SHARE_INFO_502). У вас может возникнуть вопрос: "Для чего скрывать ресурсы?" Объясню, в моей домашней локальной сети ресурсы скрываются для того чтобы о них никто кроме нескольких избранных не знал. К примеру папки с исходниками, которые остальным "пользователям - не программистам" ни к чему. Некоторые таким образом прячут "экстремальные" фильмы ;) от детей своих "друзей - сетевиков". Получение списка текущих сессийС управлением ресурсами мы разобрались, теперь самое время перейти ко второй части, сетевым сессиям, т.е. с определением кто, когда подключился к нам (или удаленному компьютеру). Для определения пользователей подключенных к нашему компьютеру воспользуемся функцией NetSessionEnum Объявление функции для Windows 9х - Ме: var NetSessionEnum:function( pszServer : PChar; sLevel : DWORD; pbBuffer : Pointer; cbBuffer : DWORD; pcEntriesRead, pcTotalAvial : Pointer):integer; stdcall; Параметры: Объявление функции для Windows NT: var NetSessionEnum:function( ServerName, UncClientName, Username : PWChar; Level : DWORD; bufptr : Pointer; prefmaxlen : DWORD; entriesread, totalentries, resume_handle : LPDWORD):DWORD; stdcall; Параметры: Существует также 6 типов структур передаваемых функции NetSessionEnum. Я опять рассмотрю только две из них как наиболее полные по своей сути. Структура session_info_50: Описание структуры: type TSessionInfo50 = packed record sesi50_cname : PChar; sesi50_username : PChar; sesi50_key : Cardinal; sesi50_num_conns : Word; sesi50_num_opens : Word; sesi50_time : Cardinal; sesi50_idle_time : Cardinal; sesi50_protocol : Byte; pad1 : Byte; end; Поля: Структура SESSION_INFO_502 Описание структуры: type TSessionInfo502 = packed record Sesi502_cname : PWideChar; Sesi502_username : PWideChar; Sesi502_num_opens : DWORD; Sesi502_time : DWORD; Sesi502_idle_time : DWORD; Sesi502_user_flags : DWORD; Sesi502_cltype_name : PWideChar; Sesi502_transport : PWideChar; End; PSessionInfo502 = ^TSessionInfo502; TSessionInfo502Array = array[0..512] of TSessionInfo502; PSessionInfo502Array = ^TSessionInfo502Array; Поля: Немного объясню значение полей time и idle_time. Что означает когда сессия не активна? Представьте, вы к примеру открыли какой-то ресурс на удаленной машине, просто посмотреть, что в нем находится (к примеру имена файлов). В то время когда вы ничего не делаете, не копируете, не запускаете, не открываете файлы, сессия не активна, она ждет ваших действий. Как только вы начинаете копировать файл (опять же к примеру) с удаленной машины, сессия становится активной до окончания копирования. Обратите внимание на поле key структуры TSessionInfo50, оно содержит уникальный идентификатор при помощи которого мы сможем завершить сессию. MSDN советует сохранить значение этого поля во временной переменной, что мы и сделаем. Создадим в разделе public массив SessionCloseKey: array [0..512] of SmallInt; В нем мы будем хранить все ключи для закрытия сессий в Windows 9x-Me. Тут очень интересный момент. Если вы посмотрите на тип key то заметите что он является Cardinal (unsigned long), а массив для хранения ключей я сделал с типом SmallInt (Short). Дело в том, что в функции NetSessionDel, которую мы рассмотрим чуть позже, значение, в которое мы должны будем передать ключ для закрытия сессии, имеет тип SmallInt (Short). Я так и не понял почему это так, либо недосмотр программистов Microsoft, либо это они просто так шутят ;). Теперь добавим на форму ListView и назовем его lwSessions. Перейдем в режим редактирования и создадим пять колонок со следующими значениями Caption - Cname, UserName, Num_Opens, Time, Idle_Time. Соответственно вы поняли что в них будут отображаться имя компьютера, имя пользователя, кол-во открытых файлов, активное и неактивное время полученных нами сессий. Заметьте что активное и неактивное время сессий будет даваться нам в виде кол-ва секунд (тип Cardinal). Предлагаю написать небольшую функцию, задача которой будет преобразовывать кол-во секунд в более привычную форму отображения. function TMainForm.CardinalToTimeStr(Value:Cardinal):String; var d,h,m,s: Real; begin d:=0; h:=0; m:=0; s:=Value; if s > 59 then begin m:=int(s / 60); s:=s-(m*60); end; if m > 59 then begin h:=int(m/60); m:=m-(h*60); end; if h > 23 then begin d:=int(h/24); h:=h-(d*24); end; Result:=''; if (d>0) then Result:=Result+floattostr(d)+' d. '; if (h<9) then Result:=Result+'0'+floattostr(h)+':' else Result:=Result+floattostr(h)+':'; if (m<9) then Result:=Result+'0'+floattostr(m)+':' else Result:=Result+floattostr(m)+':'; if (s<9) then Result:=Result+'0'+floattostr(s) else Result:=Result+floattostr(s); end; Теперь добавим на форму кнопку и назовем ее btnGetSessions. Не забудьте добавить объявления функций и структур (про переименование функции для NT помните?). Вот сам код получения текущих сессий: procedure TMainForm.btnGetSessionsClick(Sender: TObject); var OS: Boolean; FLibHandle : THandle; SessionInfo50: array [0..512] of TSessionInfo50; SessionInfo502 : PSessionInfo502Array; TotalEntries,EntriesReadNT: DWORD; EntriesRead,TotalAvial: Word; i:integer; begin lvSessions.Items.Clear; if not IsNT(OS) then Close; //Выясняем тип системы if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetSessionEnumNT := GetProcAddress(FLibHandle, 'NetSessionEnum'); if not Assigned(NetSessionEnumNT) then begin FreeLibrary(FLibHandle); Exit; end; SessionInfo502 := nil; if NetSessionEnumNT(nil,nil,nil,502,@SessionInfo502,DWORD(-1),@entriesreadNT, @totalentries, nil)=0 then for i:=0 to EntriesReadNT-1 do begin with lvSessions.Items.Add do //Заполнение данными из структуры begin Caption := string(SessionInfo502^[i].sesi502_cname); //Имя компьютера SubItems.Add(SessionInfo502^[i].sesi502_username); //Имя пользователя SubItems.Add(IntToStr(SessionInfo502^[i].sesi502_num_opens)); //Открытых ресурсов SubItems.Add(CardinalToTimeStr(SessionInfo502^[i].Sesi502_Time)); //Время активное SubItems.Add(CardinalToTimeStr(SessionInfo502^[i].sesi502_idle_time)); //Время не активное end; end; end else begin //Код для Windows 9x-Me FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle <> 0 then Exit; @NetSessionEnum := GetProcAddress(FLibHandle, 'NetSessionEnum'); if not Assigned(NetSessionEnum) then begin FreeLibrary(FLibHandle); Exit; end; if NetSessionEnum (nil,50,@SessionInfo50,SizeOf(SessionInfo50),@EntriesRead,@TotalAvial) = 0 then for i:=0 to EntriesRead-1 do begin with lvSessions.Items.Add do //Заполнение данными из структуры begin Caption := string(SessionInfo50[i].Sesi50_cname); //Имя компьютера SubItems.Add(SessionInfo50[i].Sesi50_username); //Имя пользователя SubItems.Add(IntToStr(SessionInfo50[i].sesi50_num_opens)); //Открытых ресурсов SubItems.Add(CardinalToTimeStr(SessionInfo50[i].Sesi50_Time)); //Время активное SubItems.Add(CardinalToTimeStr(SessionInfo50[i].sesi50_idle_time)); //Время не активное SessionCloseKey[i]:= SessionInfo50[i].sesi50_key; //Уникальный идентификатор для закрытия end; end; end; FreeLibrary(FLibHandle); end; Обратите внимание на то, как я сохраняю ключи для закрытия сессий. Я их просто заношу в массив в том порядке, в каком были получены сами сессии. Это сделано для простоты. Если в списке, отображающем наши сессии, не применять сортировки, то порядковый номер выделенной для закрытия сессии и ее идентификатор в массиве совпадут. Завершение сессийНу что же, теперь настало время рассмотреть механизм завершения открытых сессий. Для этого мы будем использовать функцию NetSessionDel. Объявление функции для 9х - Ме Windows: var NetSessionDel:function( pszServer : PChar; PszClientName : PChar; SReserved : SmallInt):DWORD; stdcall; Параметры: Объявление функции для Windows NT: var NetSessionDel:function( ServerName, UncClientName, Username :PWChar):DWORD; stdcall; Параметры: Как вы можете заметить, никаких структур данные функции не используют. Напишем процедуру которая будет завершать выбранную нами в lvSessions сессию по имени клиента. Для этого поместите на форму еще одну кнопку и назовите ее btnCloseSession. При нажатии на эту кнопку мы будем искать выделенную сессию и брать значение находящееся в колонке CName, к полученному нами значению прибавим '\\' (в варианте для NT, в варианте для 9x-Me оставим как есть) и передадим в качестве второго параметра функции. В качестве третьего параметра в версии кода для NT передадим NIL, а в версии кода для Windows 9x-Me тот самый уникальный ключ, сохраненный нами ранее в массиве. Он будет находится по номеру соответствующему порядковому номеру сессии в lvSessions (если вы конечно не применяли сортировку). Вот как этот код выглядит: procedure TMainForm.btnCloseSessionclick(Sender: TObject); var OS: Boolean; FLibHandle : THandle; CNameNT: PWideChar; CName9x: PAnsiChar; Key:SmallInt; i: Integer; begin if not IsNT(OS) then Close; //Выясняем тип системы if not Assigned(lvSessions.Selected) then Exit; i:= lvSessions.Selected.Index; //Определяем номер выбранной сессии if OS then begin FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetSessionDelNT := GetProcAddress(FLibHandle, 'NetSessionDel'); if not Assigned(NetSessionDelNT) then begin FreeLibrary(FLibHandle); Exit; end; //Преобразуем данные в требуемый вид CNameNT := PWChar(WideString('\\'+lvSessions.Items.Item[i].Caption)); NetSessionDelNT(nil,CNameNT,nil); end else begin FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle = 0 then Exit; @NetSessionDel := GetProcAddress(FLibHandle, 'NetSessionDel'); if not Assigned(NetSessionDel) then begin FreeLibrary(FLibHandle); Exit; end; //Преобразуем данные в требуемый вид CName9x := PAnsiChar(lvSessions.Items.Item[i].Caption); key := SessionCloseKey[i]; //Берем ключ из массива NetSessionDel(nil,CName9x,Key); end; FreeLibrary(FLibHandle); end; Думаю комментировать здесь нечего, поэтому сразу переходим к третьей части статьи. |
Сообщ.
#2
,
|
||||
|
Получение списка открытых файловТеперь я приведу способ получения списка открытых файлов на компьютере. Для этого рассмотрим функцию NetFileEnum. Объявление функции для 9х - Ме Windows: var NetFileEnum:function( pszServer, pszBasePath : PChar; sLevel : DWORD; pbBuffer : Pointer; cbBuffer : DWORD; pcEntriesRead, pcTotalAvail : Pointer):Integer; stdcall; Параметры: Объявление функции для Windows NT: var NetFileEnum:function( servername, basepath, username : PWChar; level : DWORD; bufptr : Pointer; prefmaxlen : DWORD; entriesread, totalentries, resume_handle : LPDWORD):DWORD; stdcall; Параметры: Существует три типа структур использующихся функцией: Рассмотрим две из них: Структура file_info_50: Описание структуры: type TFileInfo50 = packed record fi50_id : Cardinal; fi50_permissions : WORD; fi50_num_locks : WORD; fi50_pathname : PChar; fi50_username : PChar; fi50_sharename : PChar; end; Поля: Структура FILE_INFO_3 Описание структуры: type TFileInfo3 = packed record fi3_id : DWORD; fi3_permissions : DWORD; fi3_num_locks : DWORD; fi3_pathname : PWChar; fi3_username : PWChar; end; PFileInfo3 = ^TFileInfo3; TFileInfo3Array = array[0..512] of TFileInfo3; PFileInfo3Array = ^TFileInfo3Array; Поля: Не поленитесь и посмотрите в MSDN о всех отмеченых полях, я не буду рассматривать их подробно, так как нам они не понадобятся. Продолжим написание кода. Добавьте на форму ListView с именем lvFiles и создайте на нем три колонки с названиями ID, PathName, UserName. Также добавьте кнопку и назовите ее btnGetFiles. Не забудьте добавить обьявления функций и структуры в интерфейсную часть. Теперь напишем код который покажет нам список открытых файлов на нашем компьютере. Вот он: procedure TMainForm.btnGetFilesClick(Sender: TObject); var OS: Boolean; FLibHandle : THandle; FileInfoNT: PFileInfo3Array; FileInfo9x: array [0..512] of TFileInfo50; TotalEntries,EntriesReadNT: DWORD; EntriesRead,TotalAvial: Word; i:integer; begin lvfiles.Items.Clear; if not IsNT(OS) then Close; //Выясняем тип системы if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetFileEnumNT := GetProcAddress(FLibHandle, 'NetFileEnum'); if not Assigned(NetFileEnumNT) then begin FreeLibrary(FLibHandle); Exit; end; FileInfoNT := nil; if NetFileEnumNT(nil,nil,nil,3,@FileInfoNT,DWORD(-1),@entriesreadNT, @totalentries, nil)=0 then for i:=0 to EntriesReadNT-1 do begin with lvFiles.Items.Add do //Заполнение данными из структуры begin Caption := string(IntToStr(FileInfoNT^[i].fi3_id)); //Идентификатор SubItems.Add(FileInfoNT^[i].fi3_pathname); //Путь к файлу SubItems.Add(FileInfoNT^[i].fi3_username); //Имя пользователя end; end; end else begin //Код для Windows 9x-Me FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle = 0 then Exit; @NetFileEnum := GetProcAddress(FLibHandle, 'NetFileEnum'); if not Assigned(NetFileEnum) then begin FreeLibrary(FLibHandle); Exit; end; if NetFileEnum (nil, nil,50,@FileInfo9x,SizeOf(FileInfo9x),@EntriesRead,@TotalAvial) = 0 then for i:=0 to EntriesRead-1 do begin with lvFiles.Items.Add do //Заполнение данными из структуры begin Caption := string(IntToStr(FileInfo9x[i].fi50_id)); //Идентификатор SubItems.Add(FileInfo9x[i].fi50_pathname); //Путь к файлу SubItems.Add(FileInfo9x[i].fi50_username); //Имя пользователя end; end; end; FreeLibrary(FLibHandle); end; Без комментариев :) Закрытие открытого файлаТеперь рассмотрим функции NetFileClose и NetFileClose2, при помощи которых мы будем закрывать открытые по сети файлы. Функция NetFileClose2 - используется только в Windows 9x-Me var NetFileClose2:function( pszServer :PChar; UlFileId :LongWord):DWORD; stdcall; Поля: Функция NetFileClose - используется только в Windows NT var NetFileClose:function( ServerName :PWideChar; FileId :DWORD):DWORD; stdcall; Поля: Как вы видите с этими функциями все предельно просто, в качестве первого параметра передадим NIL (закрывает у себя), в качестве второго, идентификатор файла отображающийся в поле ID. Поместите на форму кнопку, измените ее имя на btnCloseFile, не забудьте добавить объявления функций (для NT переименовывать не надо). Вот пример кода: procedure TMainForm.btnCloseFileClick(Sender: TObject); var OS: Boolean; FLibHandle : THandle; i: Integer; begin if not IsNT(OS) then Close; //Выясняем тип системы if not Assigned(lvFiles.Selected) then Exit; i:= lvFiles.Selected.Index; //Определяем номер выбранного файла if OS then begin //Код для NT FLibHandle := LoadLibrary('NETAPI32.DLL'); if FLibHandle = 0 then Exit; @NetFileClose := GetProcAddress(FLibHandle, 'NetFileClose'); if not Assigned(NetFileClose) then begin FreeLibrary(FLibHandle); Exit; end; NetFileClose(nil,StrToInt(lvFiles.Items.Item[i].Caption)); //Закрываем end else begin //Код для Windows 9x-Me FLibHandle := LoadLibrary('SVRAPI.DLL'); if FLibHandle = 0 then Exit; @NetFileClose2 := GetProcAddress(FLibHandle, 'NetFileClose2'); if not Assigned(NetFileClose2) then begin FreeLibrary(FLibHandle); Exit; end; NetFileClose2(nil,StrToInt(lvFiles.Items.Item[i].Caption)); //Закрываем end; FreeLibrary(FLibHandle); end; Этот код также оставлю без комментариев. Определение входящего и исходящего трафикаВот мы и подобрались к заключительной части статьи. Часто (практически всегда) можно услышать советы использовать собственноручно написанный прокси - сервер для определения трафика сети (чего там скрывать, и сам этим грешу, не охота объяснять по 15 раз на день прописные истины ;) Узнать трафик можно и не используя прокси - сервер. Для этого достаточно использовать всего лишь одну функцию библиотеки IPHLPAPI.DLL, которая поставляется со всеми версиями Windows. Её то мы и рассмотрим: Объявление функции (все версии Windows): var GetIfTable:function( pIfTable: PMibIfTable; pdwSize : PULONG; bOrder : Boolean ): DWORD; stdcall; Параметры: В качестве первого параметра функция использует указатель на структуру, вот само описание структуры type TMibIfTable = packed record dwNumEntries : DWORD; Table : TMibIfArray; end; PMibIfTable = ^ TMibIfTable; Поля: Структура сама по себе крайне неинформативна, нас интересует второе ее поле, также представляющее собой структуру, даю её описание: type TMibIfRow = packed record wszName : array[0..255] of WideChar; dwIndex : DWORD; dwType : DWORD; dwMtu : DWORD; dwSpeed : DWORD; dwPhysAddrLen : DWORD; bPhysAddr : array[0..7] of Byte; dwAdminStatus : DWORD; dwOperStatus : DWORD; dwLastChange : DWORD; dwInOctets : DWORD; dwInUcastPkts : DWORD; dwInNUCastPkts : DWORD; dwInDiscards : DWORD; dwInErrors : DWORD; dwInUnknownProtos : DWORD; dwOutOctets : DWORD; dwOutUCastPkts : DWORD; dwOutNUCastPkts : DWORD; dwOutDiscards : DWORD; dwOutErrors : DWORD; dwOutQLen : DWORD; dwDescrLen : DWORD; bDescr : array[0..255] of Char; end; TMibIfArray = array [0..512] of TMibIfRow; PMibIfRow = ^TMibIfRow; PmibIfArray = ^TmibIfArray; Поля: Как вы видите в этой структуре содержится уйма информации, которую мы и будем использовать (часть её ;) Заметьте, интерфейсом является не обязательно некое физическое устройство (например, сетевая карта), но на этом я останавливаться не буду. Если кому-то это интересно, посмотрите, что об этом говорит MSDN. Итак, добавьте на форму ListView, назовем его lvTraffic, создайте в ней четыре колонки со следующими именами (Caption) - bDescr, bPhysAddr (MAC), dwInOctets и dwOutOctets. В них мы будем выводить наименование интерфейса, его МАС адрес, общее кол-во принятых и отправленных байт. Добавьте описания структур и функции в интерфейсную часть модуля. Примечание, если вы добавляете структуры в том виде, в каком они даны, поменяйте очередность их объявления, т.е. TMibIfRow должна быть объявлена первой. Теперь добавьте на форму таймер, он будет отвечать за ежесекундное обновление информации о трафике, назовем его tmrTraffic. Вот сам код определения текущего входящего - исходящего трафика: procedure TMainForm.tmrTrafficTimer(Sender: TObject); // Вспомогательная функция, преобразующая МАС адрес к "нормальному" виду //Определяем специальный тип, чтобы можно было передать в функцию массив type TMAC = array [0..7] of Byte; //В качестве первого значения массив, второе значение, размер данных в массиве function GetMAC(Value: TMAC; Length: DWORD): String; var i: Integer; begin if Length = 0 then Result := '00-00-00-00-00-00' else begin Result := ''; for i:= 0 to Length -2 do Result := Result + IntToHex(Value[i],2)+'-'; Result := Result + IntToHex(Value[Length-1],2); end; end; //Сама процедура var FLibHandle : THandle; Table : TMibIfTable; i : Integer; Size : Integer; begin tmrTraffic.Enabled := False; //Приостанавливаем на всякий случай таймер lvTraffic.Items.BeginUpdate; lvTraffic.Items.Clear; //Очищаем список FLibHandle := LoadLibrary('IPHLPAPI.DLL'); //Загружаем библиотеку if FLibHandle = 0 then Exit; @GetIfTable := GetProcAddress(FLibHandle, 'GetIfTable'); if not Assigned(GetIfTable) then begin FreeLibrary(FLibHandle); Close; end; Size := SizeOf(Table); if GetIfTable(@Table, @Size, False ) = 0 then //Выполняем функцию for i:= 0 to Table.dwNumEntries-1 do begin with lvTraffic.Items.Add do begin //Выводим результаты Caption := String(Table.Table[i].bDescr); //Наименование интерфейса SubItems.Add(GetMAC(TMAC(Table.Table[i].bPhysAddr), Table.Table[i].dwPhysAddrLen)); //MAC адрес SubItems.Add(IntToStr(Table.Table[i].dwInOctets)); //Всего принято байт SubItems.Add(IntToStr(Table.Table[i].dwOutOctets)); //Всего отправлено байт end; end; lvTraffic.Items.EndUpdate; FreeLibrary(FLibHandle); tmrTraffic.Enabled := True; //Не забываем активировать таймер end; Заметьте, я определяю новый тип данных TMAC для передачи массива, в котором содержится сам MAC адрес в функцию для преобразования его в более привычный вид. Обратите внимания на код TMAC(Table.Table[i].bPhysAddr), это передача массива, обязательно нужно указать, что массив передается как тип TMAC, в противном случае компилятор выдаст ошибку несовместимости типов. Вот кажется и все, что я хотел вам рассказать. Поэкспериментируйте с данным кодом, я раскрыл не все возможности приведенных мной функций. Дерзайте ;) ЗаключениеВ заключение хочу сказать. Вся информация была получена в результате тщательного изучения MSDN и выяснением особо острых моментов под С#. Если у вас возникнут какие либо вопросы по статье, сперва посмотрите как это реализовано в прилагаемой к статье программе. Не стесняйтесь пройтись по коду под отладкой. Посмотрите также сам MSDN. Кстати некоторые (и что самое удивительное не один и тот же человек) присылали мне письма с вопросом, что такое MSDN. Отвечаю: MSDN - сокращение от MicroSoft Developer Network, огромная база содержащая практически всю (если не всю) информацию о продуктах компании Microsoft. Распространяется обычно на трех - четырех компакт дисках, или доступна в интернете по адресу http://msdn.microsoft.com Код проверялся под следующими системами: Windows 98, Windows 98 SE, Windows Me, Windows NT 4.0 Server, Windows 2000 Professional, Windows 2000 Server и Windows XP Professional. Код совместим с Delphi 5.0 Professional, Delphi 6.0 Enterprise SP1, Delphi 7.0 Enterprise (остальных версий Delphi просто не оказалось в наличии :) Отдельно хочется выразить благодарность Анатолию Подгорецкому и Игорю Шевченко за время потраченное на изучение статьи и указание на допущенные мной ошибки.
С уважением, Александр (Rouse_) Багель
|