понедельник, 14 января 2013 г.

KickNetwork: "пинаем" глючный сетевой адаптер

Появился у меня глючный китайский PCI WiFi адаптер TP-LINK 150Mbps Wireless N Adapter (PCI\VEN_168C&DEV_002D). Не то чтобы совсем глючный, с большего он работает вполне себе хорошо, но совершенно не любит p2p соединения: так и норовит перестать видеть роутер при запущенном uTorrent'e. Другими словами, мой WiFi-адаптер, то и дело пишет "соединение ограничено", хотя на других устройствах WiFi работает замечательно и роутер ни при чём. Уж не знаю почему так происходит, вот только перестановка дров ничего не дала. Однако, было замечено, что достаточно переподключиться к беспроводной сети и проблема на время исчезает. Но есть загвоздка: т.к. соединение не рвётся, выключать и включать соединение надо вручную. Чтож, займёмся нашим любимым делом, проектированием "костылей".

Т.к. каждый раз лазить в  "Центр управления сетями и общим доступом" надоедает, был написан следующий скрипт:
netsh interface set interface name="wireless" admin=disable
netsh interface set interface name="wireless" admin=enable
         где name - имя сетевого подключения. По-умолчанию обычно "Беспроводное соединение".

 Т.е., когда сеть переставала работать, я просто запускал bat-файл, который выключал беспроводной интерфейс, затем включал его, тот на автомате цеплялся к моей точке доступа и сеть подымалась. Подробнее о данном срипте в этой статье .
Но, очень скоро мне надоело делать и это. К тому же, для поднимания сети требуется участие человека, что не совсем подходит, когда хочешь запустить торрент на ночь.
Значит, нам нужна программа, которая пинала бы адаптер (перезапускала интерфейс) в случае проблем. Исходя из функций, назовём эту программу "KickNetwork".

Программа KickNetwork должна выполнять следующие функции:
  • Висеть резедентно в фоновом режиме и через некоторый промежуток времени проверять доступность сети (пинговать роутер).
  • В случае, если все ОК, засыпать до следующей проверки.
  • Если совсем не ОК, выключать заданный сетевой интерфейс и затем его включать (перезапускать).
  • Для удобства настройки хранить в ini-файле.
Вот и всё ТЗ (техническое задание), приступаем :)

Индуский код
 Оппа, индус-стайл! 
Далее будет представлен код, который с большой долей вероятности является индусским. Я предупредил.
Кстати, программа писалась под консоль на Delphi XE3 :) Вот такое извращение. Кому не нравится - идут лесом.




Первым делом создадим файл "KickNetwork.ini". Файл с настройками для нашей программы. В нём будет только одна секция, назовем её [General] и 4 параметра:
  • HOST - адрес (или имя хоста для проверки связи);
  • PORT - номер TCP-порта для проверки;
  • WAIT - задержка между проверками (в миллисекундах);
  • NETNAME - имя сетевого интерфейса (который надо "пинать").
Т.е. файл  KickNetwork.ini будет иметь следующие содержимое:

[GENERAL]
HOST=192.168.1.1
PORT=80
WAIT=60000
NETNAME=Wireless
Собственно сам исходник на Delphi XE3 (пояснять не буду, кто понимает - хватит и комментариев в коде, благо код очень простой):

program KickNetwork;

{$APPTYPE CONSOLE}

{$R *.res}


uses
  System.SysUtils,
  WinApi.Windows,   //библиотека WinApi (нам нужна ф-ция CreateProcess)
  Web.Win.Sockets,  //библиотека класса TTcpClient
  System.Inifiles;  //библиотека класса TIniFile

//определение каталога Windows
function GetWindowsDir: string;
var
p : PChar;
begin
GetMem(p, MAX_PATH);
result := '';
if GetWindowsDirectory(p, MAX_PATH) > 0 then
result := string(p);
FreeMem(p);
end;

//функция выполнения консольной команды с ожиданием
function ExecAndWait(const FileName, Params: ShortString; const WinState: Word): boolean; export;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CmdLine: ShortString;
begin
  CmdLine :=Filename+ Params;
  FillChar(StartInfo, SizeOf(StartInfo), #0);
  with StartInfo do
  begin
    cb := SizeOf(StartInfo);
    dwFlags := STARTF_USESHOWWINDOW;
    wShowWindow := WinState;
  end;
  Result := CreateProcess(nil, PChar( String( CmdLine ) ), nil, nil, false,
                          CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil,
                          PChar(ExtractFilePath(Filename)),StartInfo,ProcInfo);
  if Result then
  begin
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);
    { Free the Handles }
    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);
  end;
end;

//функция проверки доступности хоста
function TryToConnect(host: string; port: integer): boolean;
var
TCPClient: TTcpClient; //простейший TCP-клиент
begin
TCPClient:=TTcpClient.Create(NIL);  //инициализируем компонент TCP-сервера
//задаём парамтры подключения

TCPClient.RemoteHost:=host;
TCPClient.RemotePort:=IntToStr(port);
//Пробуем поключиться
if TCPClient.Connect then Result:=True //Возвращаем TRUE в случае успеха
                     else Result:=False; //Возвращаем FALSE в случии неудачи
//разъединяемся

TCPClient.Disconnect;
TCPClient.Destroy;
end;

//процедура "пинания" сетевого интерфейса:

procedure KickNet(Network: string);
begin
//перезапускаем интерфейс:
ExecAndWait(GetWindowsDir+'\System32\netsh.exe interface set interface name="'+Network+'" admin=disable','',SW_HIDE);
ExecAndWait(GetWindowsDir+'\System32\netsh.exe interface set interface name="'+Network+'" admin=enable','',SW_HIDE);
end;

var
IFile: TIniFile; //переменная для работы с ini-файлом

PingHost: string; //хост для проверки соединения
PingPort: Integer; //порт хоста для проверки соединения
WaitTime: Integer; //таймаут между проверками
NetworkName: string; //имя сетевого интерфейса для "пинания"
begin
  try
  //читаем настройки из файла KickNetwork.ini
  IFile:=TIniFile.Create(GetCurrentDir+'\KickNetwork.ini');
  PingHost:=IFile.ReadString('GENERAL','HOST','192.168.1.1');
  PingPort:=StrToInt(IFile.ReadString('GENERAL','PORT','80'));
  WaitTime:=StrToInt(IFile.ReadString('GENERAL','WAIT','60000'));
  NetworkName:=IFile.ReadString('GENERAL','NETNAME','Wireless adapter');

  while True do //цикл
  begin
  //Подключаемся, если не выходит то пинаем
  if not TryToConnect(PingHost,PingPort) then KickNet(NetworkName);
  Sleep(WaitTime); //в любом случае засыпаем
  end;
    { TODO -oUser -cConsole Main : Insert code here }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Работает, круто! Осталось поставить в автозагрузку.

1 комментарий: