Введение.
BITS — Background Intelligent Transfer Service — это системный сервис в Win2k, WinXP, Win2k3, используемый для проведения транзакций файлов по сети. Изначально создавался специально для Windows Update, но позже MS решила документировать его подробно для общего пользования.
Вообще говоря, этот ход MS не понятен — создавать сервис такого рода и разрешать его использование произвольными программами, так как, по сути, это был (а на многих системах — есть) тривиальный способ произвести обмен данными по сети через доверенный процесс [svchost.exe].
Моя статья направлена на то, чтобы показать вам, как работать с этим сервисом, добавлять, удалять и просматривать задания.
Задания определяются как некая абстракция, базовый юнит службы BITS, содержащий в себе набор свойств файлов, предназначенных для передачи, и некоторые внутренние свойства задания, например, приоритет и прочие. Приоритет может быть низким, нормальным, высоким — при фоновой загрузке, и обычный, когда приложение использует для своих нужд все имеющиеся ресурсы сети. При фоновом же режиме, другие приложения могут остановить передачу, либо отобрать часть, а то и весь канал.
BITS является Unicode компонентом, таким как, например, GDI+, соответственно, все строки свойств должны быть в этом формате. Собственно, по этой причине сервис не доступен в Win9x и в NT 4.0 (в 9x, для начала, вообще нет служб в том виде, в котором они в NT системах). Поэтому все приложения с использованием BITS должны быть скомпилированы в формате Unicode (смотрим директивы препроцессора).
Итого, BITS — сервис, предназначенный для обмена данными по протоколу HTTP[S] в фоновом или обычном режиме.
Объекты.
Как и многие базовые компоненты COM, BITS создает главный объект, через который доступны остальные. Я перечислю здесь основные.
IBackgroundCopyManager | Основной объект, создает и перечисляет задания. |
IEnumBackgroundCopyJobs | Объект перечисления, используемый для получения свойств имеющихся заданий. |
IBackgroundCopyJob
IBackgroundCopyJob2
IBackgroundCopyJob3 | Объект задания. Почему три — каждой версии свой объект. Через такие объекты добавляются и удаляются файлы/списки файлов, изменяются свойства. |
IEnumBackgroundCopyFiles | Этот объект содержит в себе список файлов для передачи. |
IBackgroundCopyFile
IBackgroundCopyFile2 | Объект файла. |
IBackgroundCopyError | Объект ошибок, используемый для получения описаний возникающих ошибок соответственно. |
IBackgroundCopyCallback | Объект событий, должен быть определен и зарегистрирован в задании, если необходима большая интерактивность приложения. |
Практика.
Для простых приложений/функций, производящих скачку/закачку 1 файла, нам понадобятся всего-лишь три объекта из вышеперечисленных. Напишем функцию, принимающую 2 аргумента - ссылку на удаленный файл и путь к локальному.
bool BitsDownload(LPCWSTR RemoteFile, LPCWSTR LocalFile)
{
IBackgroundCopyManager* pCopyManager = NULL;
HRESULT hr;
bool State = FALSE;
// Инициализируем COM и указываем необходимую апартаментную модель
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager), (void**) &pCopyManager); // Создали объект для загрузки
if (SUCCEEDED(hr))
{
GUID JobId;
IBackgroundCopyJob* pJob = NULL;
// Чтобы произвести закачку (не советую - много гемора), замените BG_JOB_TYPE_DOWNLOAD
// BG_JOB_TYPE_UPLOAD или BG_JOB_TYPE_UPLOAD_REPLY (второе для того чтобы битс получил еще ответ сервера после загрузки).
hr = pCopyManager->CreateJob(L"DLJob", BG_JOB_TYPE_DOWNLOAD, &JobId, &pJob);
if (SUCCEEDED(hr))
{
hr = pJob->AddFile(RemoteFile, LocalFile); // Добавляем наш 1-единственный файл для закачки
if (SUCCEEDED(hr))
{
hr = pJob->Resume(); // Запускаем работу (в мсдн названо - перезапускаем)
if (SUCCEEDED(hr))
{
BG_JOB_STATE pJobState; // Объект состояния
while (true)
{
hr = pJob->GetState(&pJobState);
if (SUCCEEDED(hr) && (pJobState != BG_JOB_STATE_TRANSFERRED)) // Если не передано - ждем
{
printf("."); // Точечка)))
Sleep(100);
}
else
break; // Выходим из цикла по окончании соответственно
}
State = TRUE;
}
hr = pJob->Complete(); // Завершаем работу
if(hr != S_OK)
{
printf("\r\nJob finishing error! But file maybe transfered ok.");
State = FALSE;
}
}
else
{
printf("\r\nFile adding to job error!");
State = FALSE;
}
pJob->Release();
pJob = NULL; // Жестоко убиваем указатель))
}
else
{
printf("\r\nJob creating error!");
State = FALSE;
}
}
}
if (pCopyManager)
{
pCopyManager->Release();
pCopyManager = NULL; // Аналогичное зверство
}
CoUninitialize();
return State;
}
Внутри вызова CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) для потока нашего приложения было создано новое (скрытое) окно с именем «OleMainThreadWndName», класс которого «OleMainThreadWndClass». Если поток вызывает CoInitialize(NULL) или CoInitializeEx(NULL, COINIT_APARTMENTTHREADED), то создается так называемый Single-Threaded Apartment (однопоточный апартамент), или сокращенно STA. Если же поток вызывает CoInitializeEx(NULL, COINIT_MULTITHREADED), то он (поток) помещается в так называемый Multi-Threaded Apartment (многопоточный апартамент), или сокращенно MTA.
В одном процессе может существовать сколько угодно STA и только один MTA. Для каждого апартамента (будь то MTA или STA COM создает скрытое окно) это окно (вернее его очередь сообщений) нужно для синхронизации вызовов производимых к апартаменту. Один поток физически не может вызвать другой. Выполнение любого кода в потоке (в том числе и вызовы функций) производится именно в этом потоке, независимо от того, в какой области памяти процесса код располагается, и кем и как он был загружен. Совершенно невозможно из одного потока вызвать функцию из другого потока. Апартаментная модель предполагает, что вызовы к объектам (то есть к их коду) должны производиться из одного из потоков, входящих в этот апартамент.
Теперь нам необходимо проверить, запущен ли BITS, и, если нет - запустить его самим.
Функция проверки:
bool CheckBITSStatus(SC_HANDLE hManager)
{
SC_HANDLE hService = OpenService(hManager,L"BITS",SERVICE_QUERY_STATUS); // Открываем хэндл сервиса.
LPSERVICE_STATUS rServStatus = ( LPSERVICE_STATUS ) GlobalAlloc( GPTR, sizeof( SERVICE_STATUS ) );
bool Ret = QueryServiceStatus(hService,rServStatus); // Запрашиваем о текущем состоянии.
if(Ret!=NULL)
{
if(rServStatus->dwCurrentState == SERVICE_RUNNING) // Запущен
{
CloseServiceHandle(hService);
return TRUE;
}
else if(rServStatus->dwCurrentState == SERVICE_START_PENDING) // Запускается
{
printf("BITS is starting now. Waiting...\r\n");
while(rServStatus->dwCurrentState != SERVICE_RUNNING) // Ждем
{
Sleep(250);
Ret = QueryServiceStatus(hService,rServStatus);
}
CloseServiceHandle(hService);
return TRUE;
}
else
{
CloseServiceHandle(hService); // Не запущен
return FALSE;
}
}
else
{
printf("Warning: Cannot get information about service status\r\n"); // Не смогли получить инфу - очень странно
return TRUE;
}
}
Отлично, теперь мы знаем запущен сервис или нет. Осталось написать основную функцию, соединяющую эти две и запускающую, в случае чего, BITS.
wmain(int argc, wchar_t *argv[]) // У нас ведь юникод милостью МС))
{
printf("/*------- BITS Downloader Example by DeaDMonaX ----------\r\n| dm.xndcrew.org | Espessially for HackConnect.Ezine #2 |\r\n-------------------------------------------------------*/\r\n");
if(argc < 3)
{
if(argc == 2 && wcscmp(argv[1],(wchar_t*)"show")==NULL)
{
PrintJobs();
return 0;
}
else
{
printf("usage:\r\nDownload: bitsdl \r\nShow and describe jobs: bitsdl show\r\n"); // Лень писать парсилку чтобы имя автоматом делал анализируя урл
return -1;
}
}
else
{
if(argc > 3)
printf("\r\nToo many command-line arguments. Third and other after will be ignored.\r\n");
SC_HANDLE hManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(hManager==NULL)
{
printf("Warning: opening SCManager with full access failed.\r\n");
hManager = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
if(hManager!=NULL)
{
bool Ret = CheckBITSStatus(hManager);
if(Ret == FALSE)
{
printf("BITS is stopped and cannot be started. Exiting...");
return -1;
}
}
else
{
printf("Mystic: opening SCManager with enumerate services access also failed.\r\nTrying download skipping check.\r\n");
}
}
else
{
if(CheckBITSStatus(hManager) == FALSE)
{
printf("Warning: BITS is not started. Trying to run it.\r\n");
SC_HANDLE hService = OpenService(hManager, L"BITS", SERVICE_START);
if(StartService(hService,NULL,NULL))
{
printf("OK: BITS is running now\r\n");
CloseServiceHandle(hService);
}
else
{
printf("Mystic: cannot start BITS with full access!\r\n");
return -1;
}
}
else
printf("OK: BITS is running\r\n");
}
if(hManager!=NULL)
{
CloseServiceHandle(hManager);
}
Sleep(1000);
printf("\r\nLocal file: %ls\r\nRemote file: %ls\r\n\r\nDownloading",argv[2],argv[1]);
if(BitsDownload(argv[1],argv[2]))
printf(" done!");
}
return 0;
}
Теперь напишем, которая перечисляет все задания и файлы в них.
void PrintJobs()
{
IBackgroundCopyManager* pCopyManager = NULL;
IEnumBackgroundCopyJobs* pJobs = NULL;
IBackgroundCopyJob* pJob = NULL;
IEnumBackgroundCopyFiles* pFiles = NULL;
IBackgroundCopyFile* pFile = NULL;
BG_JOB_PRIORITY pPriority;
LPWSTR pRemoteFileName;
LPWSTR pLocalFileName;
LPWSTR pJobName;
ULONG cJobCount = 0;
ULONG cFileCount = 0;
ULONG idx = 0;
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // Та же тема с COM'ом
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager), (void**) &pCopyManager); // Создали объект для загрузки
if (SUCCEEDED(hr))
{
hr = pCopyManager->EnumJobs(BG_JOB_ENUM_ALL_USERS, &pJobs); // Чтобы перечислить задания только текущего пользователя, замените BG_JOB_ENUM_ALL_USERS нулем.
if (SUCCEEDED(hr))
{
pJobs->GetCount(&cJobCount); // Получаем количество текущих работ. Не знаю почему, но у меня всегда пишет 2.
printf("\r\nJobs total: %d\r\n",cJobCount);
for (idx=0; idxNext(1, &pJob, NULL); // Перечисляем работы
if (S_OK == hr)
{
printf("\r\nJob #%d\r\n",idx+1);
pJob->GetPriority(&pPriority); // Приоритет работы, название
pJob->GetDisplayName(&pJobName);
printf("Name: %ls\r\nPriority: %ls\r\n",pJobName,GetStrPrVal(pPriority));
hr = pJob->EnumFiles(&pFiles); // Перечисление файлов работы
if (SUCCEEDED(hr))
{
pFiles->GetCount(&cFileCount); // Сколько там их?)
printf("Total files: %d\r\n",cFileCount);
printf("-------------------------");
for (idx=0; idxNext(1, &pFile, NULL);
printf("\r\nFile #%d\r\n",idx+1);
if (S_OK == hr)
{
hr = pFile->GetRemoteName(&pRemoteFileName); // кристально ясно
if(SUCCEEDED(hr))
{
printf("Remote Name: %ls\r\n",pRemoteFileName);
}
hr = pFile->GetLocalName(&pLocalFileName);
if(SUCCEEDED(hr))
{
printf("Local Name: %ls\r\n",pLocalFileName);
}
}
else
{
printf("Warning: Could not retrieve information about %dd file in job\r\n",idx);
break;
}
}
pFiles->Release();
pFiles = NULL;
}
printf("-------------------------\r\n");
pJob->Release();
pJob = NULL;
}
else
{
printf("No jobs.\r\n");
break;
}
}
pJobs->Release();
pJobs = NULL;
}
}
}
}
В тексте этой функции упоминалась неизвестная вам функция GetStrPrVal. Эта функция получает дескриптор приоритета задания и возвращает текстовое значение, ему соответствующее.
wchar_t *GetStrPrVal(BG_JOB_PRIORITY pPriority)
{
if(BG_JOB_PRIORITY_FOREGROUND==pPriority)
return &L"Foreground";
else if(BG_JOB_PRIORITY_HIGH==pPriority)
return &L"High";
else if(BG_JOB_PRIORITY_NORMAL==pPriority)
return &L"Normal";
else
return &L"Low";
}
Voila! Вот и все что я хотел рассказать вам о технологии BITS. Ищите в стаффе полные сорцы сего чуда. // dm
|