Причиной побудившей меня написать эту статью была нестабильность работы соединения с Интернетом, которое довольно часто разрывалось. А стандартная функция ОС Windows ХР не справлялась с возложенной на нее обязанностью по восстановлению разорванного соединения. Плюс к этому, не так уж удобно «листать» системный журнал в поиске причины разрыва, или времени разрыва, или других каких логов.(Стоит только вспомнить эти ужасные времена, когда у «стрима» было аж по 4 обрыва. Сколько нерв попортили они людям) … прошу прощения, отвлекся Приступим к решению этого вопроса. В качестве язык программирования будем использовать Delphi 7 версии.
Первое что нам надо рассмотреть, это средства для работы с системой установки удаленного доступа (RAS). Данное средство представляет собой набор API-функций, которые хранятся в системной библиотеке rasapi.dll, используемой ОС.
Это достаточно мощная библиотека, и для решения нашей проблемы нам понадобятся не все функции. Поэтому мы рассмотрим только необходимые нам. Перечислю их:
RasEnumConnections – функция для проверки наличия установленных соединений;
RasEnumEntries – функция получения списка соединений(зарегистрированных) в системе;
RasDial – функция установки соединения.
Что ж, теперь необходимо сформулировать тех. задание решения поставленной задачи. И так:
Необходимо определить/получить список соединений (зарегистрированных в системе)
Реализовать процедуру дозвона
Реализовать обработчик-таймер, который будет проверять, через определенный интервал времени, статус установленного соединения
Написать процедуру ведения лога – пользовательский журнал.
Кроме того, чтобы программы была более гибкой, то мы реализуем дружественный интерфейс, в котором позволим пользователю выбирать из списка соединений (которых может быть более одного ). Поэтому начнем мы с последнего.
Для этого запустим Delphi, создадим новый проект и сохраним его под названием, к примеру ControlConn. Скинем на форму:
2 ListBox’a;
3 кнопки;
1 поле ввода для указания интервала проверки статуса соединения.
Теперь проведем некоторое приготовление. Один ListBox назовем ActivCon – в него будут занесены сведения о существующих соединениях, а второй назовем ControlCon – в нем будут находится соединения которые необходимо контролировать. Одна из кнопок будет отвечать за запуск процесса контроля – и будет иметь не двусмысленное имя start, оставшиеся 2-е кнопки необходимы для манипулирования содержимым ListBox’ов, добавить в список контролируемых соединений и удалить из списка. Поле ввода назовем interval.
Создадим для формы событие OnActivate, на вкладке Event в инспекторе объектов. В нем мы опишем код, который будет получать список соединений. Кстати, замечу, для тех кто только начинает и тех кто этого не знал, но программирует уже давно, что хорошим стилем программирования является описание всех своих функций и процедур, используемых в проекте, в отдельном модуле. В данном случае у нас нет необходимости выносить код в отдельную функцию. Но стоит иметь в виду, что в этом есть два основных плюса:
сам проект не перегружен большим количеством программного кода,
в случае необходимости редактирования некоторых функция проекта, гораздо удобнее загрузив в редактор отдельный модуль.
При этом не возникнет ни каких проблем с согласованием с промежуточным кодом в самом проекте.
А теперь код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | procedure TForm1.FormActivate(Sender: TObject); var bSize : integer; //размер массива, который будут помещаться сведения об «удаленных соединениях» cCount : integer; //количество удаленных соединений cList : array[1..MaxEntries] of TRasEntryName; //массив, в который будут помещены сведения об удаленных соединениях // где MaxEntries – константа, представляющая максимально возможное количество соединений, объявлена в модуле RasUnit.pas x,rslt : integer; begin cList[1].dwSize:=SizeOf(TRasEntryName); bSize:=SizeOf(TRasEntryName)*MaxEntries; //получаем список соединений rslt:=RasEnumEntries(nil, nil, @cList[1], bSize, cCount); //если функция выполнилась успешно и есть соединения if (rslt=0) and (cCount>0) then begin // то заполняем список соединений ActivCon for x:=1 to cCount do ActivCon.Add(cList[x].szEntryName); end; end; |
Теперь при запуске программы у нас в списке ActivCon будут перечисленный все зарегистрированные в системе ras-соединения. Далее нужно добавить возможность манипулирования списком соединений. Следующий код для кнопки «Добавить»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | procedure TForm1.Button1Click(Sender: TObject); var i,j:integer; begin if ActivCon.SelCount=0 then exit; for j:=0 to ActivCon.SelCount-1 do if search(ActivCon.Items.Strings[ActivCon.ItemIndex], ControlCon) = -1 then begin ControlCon.Items.Add(ActivCon.Items.Strings[ActivCon.ItemIndex]); ActivCon.Items.Delete(ActivCon.ItemIndex); end else begin Application.MessageBox('Такое соединение в списке уже есть!','Внимание'); end; end; |
Для кнопки «Удалить»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | procedure TForm1.Button2Click(Sender: TObject); var i,j:integer; begin if ControlCon.SelCount=0 then exit; for j:=0 to ControlCon.SelCount-1 do if search(ControlCon.Items.Strings[ControlCon.ItemIndex], ActiveCon) = -1 then begin ActiveCon.Items.Add(ControlCon.Items.Strings[ControlCon.ItemIndex]); ControlCon.Items.Delete(ControlCon.ItemIndex); end else begin Application.MessageBox('Такое соединение в списке уже есть!','Внимание'); end; end; |
В обоих листингах используется функция search, которой в качестве параметра передается строка, наличие которой необходимо проверить в списке соединений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | function search(str:string; lsb:TListBox):integer; var i,j:integer; begin for i:=0 to lsb.Count-1 do begin j:=i; if lsb.Items[i] = str then begin search:=j; exit; end; end; search:=-1; end; |
Да, кстати, самое главное то мы забыли. Нам еще нужен таймер, добавим его. Кроме того нам необходимо объявить глобальные переменные и структуру состояния соединения, объявим их:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | type TMyDialParam = Record AMsg:Integer; AState:TRasConnState; AError:Integer; End; var MyDialParam:TMyDialParam; hRas: ThRASConn; |
Кроме глобальных переменных, нам нужно определить CallBack-процедуру, которая не обходима для вызова функции RasDial().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | procedure RasCallback(msg: Integer; state: TRasConnState; error: Integer); stdcall; begin MyDialParam.AMsg:=msg; MyDialParam.AState:=state; MyDialParam.AError:=error; { Здесь можно описать обработчик полученных сообщений о статусе установки соединения. } end; |
Теперь создадим для Таймера событие onTimer и напишем в нем следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | procedure TForm1. Timer1Timer(Sender: TObject); var rcArray: array[1..100]of TRASCONN; rStatus: TRASCONNSTATUS; dwRet: integer; rcsize: integer; dw2: integer; i,j,tr:integer; s:string; begin tr:=0; //отключаем таймер Timer1.Enabled:=false; if Edit1.Text = ‘’ then // Если интервал не указан Timer1.Interval:=5000; // то устанавливаем значение по умолчанию 5 секунд else Timer1.Interval:=StrToint(Edit1.Text)*1000; //производим подготовку для получения списка работающих соединений(уже установленных) rcsize:= sizeof(rcArray); ZeroMemory(@rcArray, rcsize); rcArray[1].dwSize := sizeof(TRASCONN); dwRet := RasEnumConnections(@rcArray, rcsize, dw2); ZeroMemory(@rStatus, sizeof(TRASCONNSTATUS)); rStatus.dwSize := sizeof(TRASCONNSTATUS); // если количество работающих соединений равно 0, тогда if dw2 = 0 then //запускаем их поочередно begin if ControlCon.Count <> 0 then // проверяем не пуст ли список контролируемых соединений for i:=1 to ControlCon.Count-1 do begin MemError.Lines.Add(TimeToStr(Time)+' соединение не установлено '+ ControlCon.items[i]+'. Запускаю.'); //заносим в журнал сообщение о разрыве //и вызываем функцию установки соединения ConnectEntry(ControlCon.Items[i],i); end; end else begin //если же было определено что соединение хоть одно но запущено if ControlCon.Count <> 0 then // то мы опять же проверяем список begin for i:=0 to ControlCon.Count-1 do // после чего пробегаем по всему списку и сверяемся с полученным списком рабочих соединений, если в полученном списке какие то соединения отсутствуют, то мы их немедленно устанавливаем begin for j:=1 to dw2 do begin if ControlCon.Items[i] = rcArray[j].szEntryName then begin tr:=1; MemError.Lines.Add(TimeToStr(Time)+' соединение не установлено '+ ControlCon.items[i]+'. Запускаю.'); //заносим в журнал сообщение о разрыве end; end; if tr = 1 then // устанавливаем соединение ConnectEntry(ControlCon.Items[i],i); end; end; end; //запускаем таймер Timer1.Enabled:=true; end; |
Теперь опишем процедуру установки соединения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | procedure ConnectEntry(nameEntry: string); var fl : LongBool; r : Integer; DialParams : TRasDialParams; hR: ThRASConn; begin //забиваем DialParams нулями FillChar(DialParams, SizeOf(TRasDialParams), 0); //Инициализируем переменную DialParams, заполнив поля dwSize и szEntryName with DialParams do begin dwSize:=Sizeof(TRasDialParams); StrPCopy(szEntryName, nameEntry); end; //Теперь вызовим функцию для заполнения оставшихся полей структуры DialParams r:=RasGetEntryDialParams(nil, DialParams, fl); //если все прошло успешно if r=0 then begin //вызываем функцию «дозвона» r:=RasDial(nil, nil, DialParams, 0, @RasCallback, hRas); end; end; |
На этом все. Статью заканчивал в спешке, поэтому в случае обнаружения каких либо не точностей, не поняток или ошибок просьба сильно не ругаться, а отписываться по всем замечания и не только ;). Всем спасибо!
Здорово! Интересно! Меня эта проблема тоже мучает давно (нестабильность работы соединения с интернетом).
Было бы неплохо добавить информацию по расшариванию подключения (для случая когда машинка является «сервером») и ссылку на исходник (в котором будет уже RasUnit.pas) :
Отличное предложение!
Сегодня же займусь этим вопросом…
а исходник проги есть? Что-то не получается повторить код (ругается)
Да, у нас есть наша реализация, ее можно скачать от сюда. Версия вполне рабочая, но требующая тестирования. Вот и протестируй
Кстати, советую тебе зарегаться на нашем форуме и в случае возникновения вопросов отписываться там;)
Обязательно зарегистрируюсь! Сейчас катастрафически не хватает времени.
Исходники и статья совершенно разные!
Да, есть такое дело. В статье просто используется старая версия, но замечу рабочая. А то что выложено в «Наши проекты», на то что дал тебе ссылку уже более отточены, но увы требуют доработки и расширения. Поэтому и отличаются. Не вижу смысла редактировать статью ибо в ней хоть и старая версия, но смысл тот же. В целом то ничего не изменилось
Я к тому, что статья и ссылка на исходники должны быть идентичны.
Чтоб кто прочитал статью мог воспользоваться исходником. Оттачивать проги будут своих приложений…
Контроль не так часто нужен, как безопасность самого соединения. Контролировать можно только то, что требует такого контроля. Статья хорошая, иногда может понадобится.