diff --git a/README.md b/README.md index 4fdf243..290e580 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ 1. [Основы языка C++](labs/lab01) 2. [Система контроля версий Git](labs/lab02) 3. [Структурирование программ](labs/lab03) -4. Использование библиотек +4. [Использование библиотек](labs/lab04) ## Ресурсы diff --git a/labs/lab04/README.md b/labs/lab04/README.md new file mode 100644 index 0000000..47442aa --- /dev/null +++ b/labs/lab04/README.md @@ -0,0 +1,814 @@ +# Использование библиотек + +## Цель работы + +1. Уметь устанавливать и подключать к программе внешние библиотеки. +1. Уметь использовать типовые элементы API библиотек. +1. Уметь работать с параметрами командной строки программы. + +## Задание + +Добавить возможность построения гистограммы по данным из файла в сети. +Адрес файла задается аргументом командной строки программы. +Если адрес не задан, читать данные со стандартного ввода, как раньше. + +Пример запуска программы с загрузкой данных из файла на сайте кафедры: + +``` +lab34.exe http://uit.mpei.ru/study/courses/cs/lab03/marks.txt >marks.svg +``` + +Работу нужно вести на основе кода общего задания к ЛР № 3 в прежнем репозитарии. +По этой причине во всех примерах используется `lab34.exe`. + +Защита представляет собой демонстрацию выполненного индивидуального задания, +ответы на вопросы по коду и по релевантной теории (см. контрольные вопросы). + +## Указания + +Скачивать файл по сети будем с помощью библиотеки [cURL](https://curl.haxx.se). + +Получать сведения о системе будем с помощью API операционной системы. +Это тоже внешняя библиотека, но её не требуется устанавливать. + +### Ввод из произвольного потока + +Рефакторинг (refactoring) — это изменение кода программы +без изменения её функциональных характеристик +(интересующего пользователя поведения). + +Зачем менять код, если это не добавляет возможностей и не исправляет ошибок? +Например, для того, чтобы облегчить дальнейшее внесение изменений в него. +В нашем случае программа жестко завязана на то, +что данные поступают со стандартного ввода. +Ожидается, что они смогут поступать не только оттуда. +Код нужно переписать так, чтобы он мог работать с любым входящим потоком. +Тогда текущее поведение будет частным случаем нового, +когда поток — стандартный ввод (`cin`). + +![Желаемая схема работы программы.](assets/flow.svg) + +Что такое «работать с потоком ввода»? +Рассмотрим любой пример работы со стандартным потоком ввода: + +```cpp +cin >> number_count; +``` + +Здесь есть переменная `cin`, которая представляет поток, и операция `>>` с ней. +Работать с другим потоком значит использовать иную переменную вместо `cin`. + +Что такое «работать с любым потоком», если поток — это переменная в программе? +Это значит, в программе есть блок кода для ввода данных, +который принимает переменную-поток и работает с ней. +Но параметризованный блок кода — это же функция! +Вводом данных занимается функция `input_data()`. +Следовательно, у нее должен появится параметр — поток, из которого читать. + +```cpp +Input +input_data(istream& in) { + // Прежний код, но работающий с in вместо cin. +} +``` + +Это должна быть именно ссылка, потому что нельзя скопировать поток ввода — +он объективно один во всей программе. +Это не может быть константная ссылка, потому что функция меняет поток: +данные после вычитывания оттуда «исчезают». + +Вызов функции будет выглядеть так: + +```cpp +auto in = input_data(cin); +``` + +**Самостоятельно.** +Перепишите функцию `input_data()` и ее вызов, как предложено. +Убедитесь, что поведение программы не изменилось. +Сделайте коммит. + +**Самостоятельно.** +Добавьте `input_data()` новый параметр: `bool prompt`. +Если он равен `true`, то нужно выводить в `cerr` подсказки, +если `false`, то не нужно их выводить. +Сделайте коммит. + +### Загрузка данных по сети + +#### Установка cURL + +Страница библиотеки cURL: . + +На этой странице есть раздел *Download* со ссылкой на страницу загрузок: + +![](assets/curl-download.png) + +Вариантов загрузки очень много, потому что cURL собирается под много +операционных систем (Windows, Linux, различные Unix-системы), архитектур +процессора (Intel/AMD, ARM и других) и их вариантов (32 или 64 бита). +Windows находится в конце списка: + +![](assets/curl-windows.png) + +Для Windows следует выбрать ссылку с описанием `binary the curl project`: +Странице загрузок для Windows — ссылки, чтобы скачать архив для системы нужной +разрядности. Как выбрать разрядность? Это зависит не от версии Windows, +а от того, собирает компилятор 32-битные или 64-битные программы. По умолчанию +с CodeBlocks идет компилятор 32-битных программ mingw32, он же устанавливается +по инструкции для РПОСУ. Используемый компилятор можно увидеть в *Build log* +при сборке программы. Если там mingw64, значит, компилятор 64-битный. + +![](assets/curl-link.png) + +В архиве находится папка `curl-7.69.1-win32-mingw` +(версия cURL и разрядность компилятора та, которая выбрана для загрузки). +Ее нужно извлечь, +переименовать в `curl` для удобства +и переместить в каталог проекта. + +**Самостоятельно.** +Занесите эту папку в `.gitignore`. +Проверьте, что сделали это правильно, с помощью команды `git status`. + +Из папки `curl` далее понадобятся: + +* `bin/libcurl.dll` — динамическая библиотека (`libcurl-x64.dll` для 64 бит); +* `lib` — каталог со статическими библиотеками; +* `include` — каталог с заголовочными файлами. + +**Примечание.** +Есть инструменты, которые позволяют скачивать и устанавливать библиотеки +из командной строки, для Windows это *Chocolatey* и *vcpkg.* +Они автоматизируют и стандартизируют то, +что в рамках этой ЛР делается вручную, чтобы разобраться. + +#### Подключение cURL к проекту + +Каждая программа должна до первого вызова функций библиотеки cURL вызвать +функцию [`curl_global_init()`][curl_global_init]. +Например, это можно сделать первой строкой в `main()`: + +```cpp +curl_global_init(CURL_GLOBAL_ALL); +``` + +При попытке собрать программу возникнут ошибки, текст которых означает, +что константа `CURL_GLOBAL_ALL` и функция `curl_global_init()` не объявлены, +то есть неизвестны компилятору: + +``` +error: 'CURL_GLOBAL_ALL' was not declared in this scope +error: 'curl_global_init' was not declared in this scope +``` + +Если открыть документацию по ссылке, она начинается так: + +> SYNOPSIS +> +> #include +> +> CURLcode curl_global_init(long flags); + +«Synopsis» значит «сводка» (отсюда слово «синоптик»), это типовой раздел +справки к библиотекам, в котором кратко показывается, как функция объявлена +и какой заголовочный нужно подключить, чтобы ее использовать. В данном случае +нужно добавить в начало `main.cpp`: + +```cpp +#include +``` + +При компиляции все равно будет ошибка, но другая — что файл не найден: + +``` +fatal error: curl/curl.h: No such file or directory +``` + +Как известно из лекций, `#include <...>` ищет файл в неких «стандартных +каталогах», куда папка `include` из архива не входит. Чтобы добавить её +в список, нужно открыть диалог *Project → Build options...* и перейти +во вкладку *Search directories.* Там три дочерних вкладки: *Compile* +(пути для поиска `#include <...>`), *Linker* (пути для поиска библиотек) +и *Resource compiler* (не требуется). Нужно нажать кнопку *Add,* в открывшемся +диалоге указать `curl/include` и нажать *OK* (путь к папке можно также +выбрать через диалог рядом с полем ввода). Каталог `curl/include` добавится +в список, диалог можно закрыть кнопкой *OK.* + +**Внимание.** +Рекомендуется вводить пути и имена файлов в настройках как текст, +а не выбирать их с помощью диалогов. + +Ошибка сборки изменится: + +``` +undefined reference to `__imp_curl_global_init' +``` + +Обратите внимание, что это уже ошибка не компиляции, а компоновки (линкера). +Исходный текст программы правильный, все заголовочные файлы найдены, +появился `obj/Debug/main.o`. Но чтобы создать из него исполняемый файл +не хватает машинного кода функции `curl_global_init()`. + +Чтобы подключить библиотеку, нужно два действия: + +1. Вернуться к *Search directories,* теперь на дочернюю вкладку *Linker.* + Там нужно добавить каталог `curl/lib` аналогично предыдущему шагу. + +1. Указать, какую именно библиотеку подключить. Если просмотреть каталог + `curl/lib`, там есть `libcurl.a` и `libcurl.dll.a`. Сейчас выберем + `libcurl.dll.a`, разница будет пояснена в последующих пунктах. + На вкладке *Linker settings* (соседняя с *Search directories)* нужно + нажать *Add* и ввести `libcurl.dll.a`, после чего сохранить настройки + и покинуть диалог нажатиями *OK.* + +Если все сделано правильно, сборка должна проходить успешно. + +[curl_global_init]: https://curl.haxx.se/libcurl/c/curl_global_init.html + + +#### Установка динамических библиотек + +Перейдите в каталог `bin/Debug`, откройте там консоль и запустите программу: + +```cmd +lab34.exe +``` + +Будет ошибка (главное в ней — имя библиотеки `libcurl….dll`): + +![](assets/dll-not-found.png) + +Дело в том, что программа собрана с использованием *динамической* библиотеки. +В `lab34.exe` нет кода cURL, а только указание загрузить `libcurl-x64.dll`, +где этот код есть, и вызвать его. Но библиотеки нет в тех каталогах, +где Windows ищет библиотеки. Одним из таких каталогов является текущий. +Скопируйте `libcurl-x64.dll` в `bin/Debug` и попробуйте запустить программу +(`libcurl.dll` для 32 бит). + +**Самостоятельно.** +Убедитесь, что программа запускается. Сделайте коммит. + +--- + +**Врезка отсюда до горизонтальной черты не нужна для новых версий cURL. +Если после размещения `libcurl….dll` в `bin/Debug` программа запускается, +пропустите это дополнение.** + +Ошибка изменится, теперь не найдены будут `libcrypto-1_1.dll` +и `libssl-1_1.dll`, но их уже нет в поставке cURL. В интернете можно узнать, +что эти библиотеки из набора OpenSSL, нужного cURL для установки защищенных +соединений. Откуда их взять? + +Разумеется, в интернете найдется всё: + +![](assets/search.png) + +**Никогда не устанавливайте библиотеки из непроверенных источников!** + +Чтобы установить недостающие библиотеки, нужно посетить официальный сайт, +как правило, там есть ссылки для загрузки, и им можно доверять. Кроме того, +даже если скачанная библиотека будет подлинной (безопасной), раскладывать +отдельные библиотеки вручную ненадежно. Через OpenSSL wiki можно найти +[сайт с готовыми сборками](https://slproweb.com/products/Win32OpenSSL.html). + +Скачайте установочный пакет (MSI) по ссылке в первой или третьей строке +в зависимости от разрядности системы: + +**Внимание.** +Разрядность cURL и OpenSSL должны совпадать. + +В процессе установки: + +![](assets/installer.png) + +* При выборе *Copy OpenSSL DLLs to:* нужно выбрать *The Windows system + directory,* чтобы библиотеки были доступны при запуске программы + из любого каталога. +* На последнем экране автор сборки предлагает поддержать его труд 10 \$. Можно + просто снять флажок и продолжить. OpenSSL распространяется бесплатно. + +--- + +#### Статическая сборка программы + +Попробуем собрать программу статически. Чтобы поменять компоновку cURL +на статическую, необходимо: + +1. Вернуться в *Linker settings,* выбрать в списке `libcurl.dll.a`, + нажать *Edit,* изменить имя на `libcurl.a` и нажать *OK.* + +2. Перейти на вкладку *Compiler settings* (рядом с *Linker settings),* + выбрать дочернюю вкладку *#defines.* + Там в текстовое поле нужно ввести `CURL_STATICLIB` + и выйти из диалога кнопкой *OK.* + +**Примечание.** +Последний шаг специфичен для cURL, аналогичный шаг для других библиотек +обычно требуется только на Windows. + +При сборке будет множество ошибок: + +``` +curl\lib\libcurl.a(connect.o):(.text+0x1d1): undefined reference to `__imp_ntohs' +curl\lib\libcurl.a(connect.o):(.text+0x2ee): undefined reference to `__imp_getpeername' +curl\lib\libcurl.a(connect.o):(.text+0x2f8): undefined reference to `__imp_WSAGetLastError' +... +``` + +Причина в том, что код cURL использует функции из других библиотек, +поэтому программа должна линковаться с ними всеми. +Для этого нужно определять, +из какой библиотеки недостающая функция (обычно поиском в интернете), +устанавливать эту библиотеку (если она не системная), +добавлять её в *Linker settings* и пробовать собрать снова. +В рамках ЛР этого делать не нужно. +На практике у зависимостей основной библиотеки могут быть другие лицензии, +которые придется принимать во внимание. + +**Самостоятельно.** +Удалите все изменения, сделанные в попытках собрать программу статически, +при помощи git. + + +#### Получение аргументов программы + +Из ЛР № 1 известно, что программа может запускаться с аргументами, +которые перечисляются после ее имени через пробел: +`fc /N expected.txt actual.txt`. +Здесь `fc` (`fc.exe`) — исполняемый файл программы, +а `/N`, `expected.txt` и `actual.txt` — три аргумента. +Перенаправление ввода-вывода не является аргументом, например, +команда `lab01.exe NUL` (скачайте `marks.txt`) + +**Примечание.** +Необязательные аргументы, +которые имеют фиксированные имена (например, `/N` у `fc`), +иногда называют опциями. +Имена опций принято начинать с `-`, `--` или `/` (только в Windows). +Остальные аргументы называют *позиционными.* + +#### Загрузка файла по сети + +Весь код в этом пункте должен выполняться только в случае `argc > 1`. + +Для простых сценариев cURL имеет набор easy-функций, которые позволяют +скачать файл всего в несколько строк. Первой должна быть вызвана +[`curl_easy_init()`][curl_easy_init]: + +[curl_easy_init]: https://curl.haxx.se/libcurl/c/curl_easy_init.html + +```cpp +CURL* curl = curl_easy_init(); +``` + +Что за тип данных `CURL*`? Это так называемый *дескриптор (handle).* +Очень часто библиотека оперирует сложными объектами, +внутреннее устройство которых программе знать не нужно, +но с другой стороны, программа должна сообщать функциям библиотеки, +с каким объектом работать. +В случае cURL таким объектом выступает сетевое соединение и связанные данные. +Единственное, что можно делать с дескрипторами — +передавать их функциям библиотеки и сравнивать между собой. +Переменную типа `CURL*` нельзя разыменовать, хотя это и указатель. + +Как правило, если есть функция создания дескриптора, как `curl_easy_init()`, +есть и функция его освобождения (закрытия). +Действительно, документация гласит: + +> This call MUST have a corresponding call to curl_easy_cleanup when the +> operation is complete. + +Это означает, что вся работа с `curl` должна делаться +между написанной строкой и следующим вызовом: + +```cpp +curl_easy_cleanup(curl); +``` + +**Самостоятельно.** +Изучите пример (раздел *EXAMPLE*) документации на `curl_easy_init()`. +Перенесите пример в программу, но вместо фиксированного адреса +используйте первый аргумент командной строки (с индексом 1). +Убедитесь, что программа работает — +содержимое файла из сети печатается на стандартный вывод. +Код печати `argc` и `argv` удалите. +Сделайте коммит. + +Структура программы должна стать такой: + +```cpp +int +main(int argc, char* argv[]) { + if (argc > 1) { + // работа с cURL + return 0; + } + // прежний код построения гистограммы +} +``` + +#### Обработка ошибок + +В переменную `res` пример сохраняет результат `curl_easy_perform()`. +Значение `res` — числовой [код ошибки][libcurl-errors], +а если ошибок не было, `CURLE_OK` (равно 0). +Функция [`curl_easy_strerror()`][curl_easy_strerror] +принимает код ошибки и возвращает строку с текстом ошибки. + +**Самостоятельно.** +Проверяйте результат `curl_easy_perform()`, если произошли ошибка, +печатайте её текст и завершайте программу вызовом `exit(1)`. Сделайте коммит. + +[libcurl-errors]: https://curl.haxx.se/libcurl/c/libcurl-errors.html +[curl_easy_strerror]: https://curl.haxx.se/libcurl/c/curl_easy_strerror.html + +#### Работа с буфером, загруженным по сети + +Содержимое файла из сети нужно не печатать, +а разбирать, считывая из него те же данные, что и со стандартного ввода. +Проще всего сначала считать весь файл из сети в память. +Такое промежуточное хранилище называют *буфером*. +Затем можно организовать поток, +который будет читать не из стандартного ввода, а из буфера. + +Для загрузки файла выделим функцию `download()`, +которая принимает строку с адресом файла. +Файл `` подключает `stringstream` — +поток, который может читать из буфера в памяти. +Также в переменные типа `stringstream` можно записывать данные, +то есть заполнять буфер. + +```cpp +#include +#include + +// ...прежние функции... + +Input +download(const string& address) { + stringstream buffer; + + // TODO: заполнить буфер. + + return input_data(buffer, false); +} +``` + +Обратите внимание: `CURLOPT_URL` требует адрес в виде строки C (`const char*`). +Чтобы получить её из `address` типа `string` можно как `address.c_str()`. + +В зависимости от того, переданы ли программе параметры, +она получает исходные данные (`Input input`) +либо из сети, либо из стандартного ввода: + +```cpp +int +main(int argc, char* argv[]) { + Input input; + if (argc > 1) { + input = download(argv[1]); + } else { + input = input_data(cin, true); + } + + const auto bins = make_histogram(input); + show_histogram_svg(bins); +} +``` + +**Самостоятельно.** +Перенесите код работы с cURL в место, отмеченное комментарием `// TODO`. +Убедитесь, что программа компилируется +(запускать ее с параметром пока нельзя). + +#### Сохранение данных из сети в буфер + +Как же получить доступ к данным, которые cURL загружает и (пока) печатает? +Каждый раз, когда cURL получает из сети очередной блок данных, +библиотека может вызывать функцию, которую укажет программа. +Эта функция должна добавлять полученный блок данных в буфер. + +Такой элемент интерфейса библиотеки называется *callback-функцией,* +или *функцией обратного вызова.* + +Функция должна иметь такой вид: + +```cpp +size_t +write_data(void* items, size_t item_size, size_t item_count, void* ctx) { + // TODO: дописывать данные к буферу. + return 0; +} +``` + +Имя функции и параметров может быть любым, +но типы данных должны быть строго такими, как описано в документации. +Ошибку может быть очень сложно отладить. +К счастью, компилятор предупреждает о несоответствии. + +Параметры: + +* `void* items` — указатель на принятые данные: `item_count` блоков + по `item_size` каждый, итого `item_size * item_count` байтов. +* `size_t item_size` — размер одного блока данных; +* `size_t item_count` — количество блоков данных; +* `void* ctx` — пользовательские данные (поясняется ниже). + +Чтобы cURL продолжила загрузку, +функция должна возвращать размер принятых данных в байтах, +иначе `curl_easy_perform()` остановит работу с ошибкой. +Пока функция возвращает 0, просто чтобы возвращать какое-нибудь значение. + +Указать cURL, какую функцию вызывать, можно так: + +```cpp +curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); +``` + +**Самостоятельно.** +Внутри `write_data()` вычислите количество принятых байтов (см. описание +параметров), сохраните в переменную `data_size`. + +Далее необходимо внутри `write_data()` добавлять данные в буфер, для этого +есть специальный метод [`buffer.write()`][basic_ostream/write]: + +```cpp + buffer.write(items, data_size); +``` + +Но это не компилируется: переменная `buffer` объявлена в `download()`, внутри +`write_data()` она недоступна. +Перенести `buffer` внутрь `write_data()` — не выход: +`buffer` нужна и в `download()`. +Объявлять в `write_data()` еще один буфер тоже неправильно: +это должен быть *тот же самый* буфер, который затем использует `download()`. + +Заметим, что хотя `buffer` недоступна, она существует где-то в памяти, +потому что `write_data()` вызывается из `curl_easy_perform()`, +и пока этот вызов не закончен, не закончится и `download()`. +Задача в том, чтобы получить этот адрес, +так как имея адрес переменной (то есть указатель на нее), с ней можно работать. + +Это типовая необходимость и обычно библиотеки, в том числе cURL, +предоставляют в callback-функциях *пользовательские данные (user data).* +Перед тем, как позволить библиотеке вызывать функцию, +программа указывает также фиксированные данные (обычно один указатель), +которые будут передаваться библиотекой в функцию. +Именно для этого `write_data()` принимает параметр `ctx` (контекст). +Можно передавать в этом контексте адрес `buffer` так +(перед `curl_easy_perform()`): + +```cpp +curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); +``` + +Контекст имеет тип `void*`, но нам известно, что это указатель на `buffer`, +то есть реальный тип — `stringstream*`. +Это как раз тот случай, когда программист может и должен +принудительно указать компилятору тип данных (приведение типов из ЛР № 1): + +```cpp +stringstream* buffer = reinterpret_cast(ctx); +``` + +Поскольку `buffer` здесь указатель, +его нужно разыменовать перед вызовом метода: + +```cpp +(*buffer).write(items, data_size); +``` + +Осталась последняя ошибка: если прочитать документацию на `write()`, +она принимает первым параметром `const char*`, а `items` имеет тип `void*`. + +**Самостоятельно.** +Сделайте приведение типа для `items`, чтобы программа компилировалась. + +Конструкция `(*buffer).write(...)` выглядит громоздко, потому в C++ введен +специальный оператор стрелки, который позволяет вызвать метод по указателю +красивее (параметры `write()` прежние, они опущены для краткости): + +```cpp +buffer->write(...); +``` + +**Самостоятельно.** +Добейтесь компиляции и проверьте корректную работу программы с примером +из задания. + +[basic_ostream/write]: https://en.cppreference.com/w/cpp/io/basic_ostream/write + +### Индивидуальные задания + +Всю необходимую информацию по функциям cURL нужно изучить по официальной +документации и на защите предъявить ссылку на конкретную страницу. +В частности, если не указан конкретный параметр `curl_easy_setopt()`, +нужно подобрать его по описанию в списке. + +Во всех вариантах нужно обрабатывать ошибки cURL, +как это делается в основной программе. + +Если вариант требует ввода дополнительных данных, проверить работу с файлом +«из сети» можно через локальный файл и адрес вида +`file://C:/Users/kozlyuk/Desktop/marks.txt` для файла `marks.txt` на рабочем +столе пользователя `kozlyuk`. Полный путь к файлу можно получить, щелкнув +в адресной строке «Проводника» (так же, как перед запуском `cmd`). + + +#### Вариант 1 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +суммарное время (total), затраченное на загрузку файла по сети. + + +#### Вариант 2 {.unnumbered} + + +Добавьте программе опцию `-format`, которая определяет формат вывода: +`lab34.exe -format text` — текст, +`lab34.exe -format svg` — изображение. +Опция может стоять до или после URL: +`lab34.exe -format svg http://...` или `lab04.exe http://... -format svg`. +Если после `-format` не стоит `text` или `svg`, нужно печатать сообщение +с подсказкой, как запускать программу, и завершать работу. + + +#### Вариант 3 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +скорость загрузки файла по сети. + + +#### Вариант 4 {.unnumbered} + + +Добавьте программе опцию `-bins`, которая позволяет задать количество +корзин при запуске. Считывать его в `read_input()` при этом не нужно. +Например: `lab34.exe -bins 3`. +Опция может стоять до или после URL: +`lab34.exe -bins 3 http://...` или `lab04.exe http://... -bins 3`. +Если после `-bins` не стоит числа, нужно печатать сообщение с подсказкой, +как запускать программу, и завершать работу. + + +#### Вариант 5 {.unnumbered} + + +Добавьте программе опцию `-generate`, при указании которой программа будет +генерировать числа вместо их считывания. Количество столбцов считывать +по-прежнему нужно. Например, `lab34.exe -generate 100` генерирует 100 чисел. +Опция может стоять до или после URL: +`lab34.exe -generate 10 http://...` или `lab04.exe http://... -generate 10`. +Если после `-generate` не стоит числа, нужно печатать сообщение с подсказкой, +как запускать программу, и завершать работу. + + +#### Вариант 6 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +размер файла, загружаемого по сети. + + +#### Вариант 7 {.unnumbered} + + + +При помощи `CURLOPT_PROGRESSFUNCTION` добавьте в программу отображение +прогресса загрузки файла. Каждый раз, когда cURL рапортует прогресс, +печатайте строку вида: `Progress: 42%`. + +**Указание.** +Чтобы cURL рапортовала прогресс, нужно установить `CURLOPT_NOPROGRESS` в 0. + + +#### Вариант 8 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +IP-адрес сервера, с которого скачан файл (не local). + + +#### Вариант 9 {.unnumbered} + + +С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок +версию cURL и версию OpenSSL в строковом виде. + + +#### Вариант 10 {.unnumbered} + + +Добавьте программе опцию `-verbose`, при указании которой нужно включать +отладочный вывод cURL с помощью `CURLOPT_VERBOSE`. +Опция может стоять до или после URL: +`lab34.exe -verbose http://...` или `lab04.exe http://... -verbose`. +Если встретился иной аргумент, начинающийся с дефиса, нужно печатать сообщение +с подсказкой, как запускать программу, и завершать работу. + + +#### Вариант 11 {.unnumbered} + + +С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок +список протоколов, которые поддерживает cURL (`protocols`). + + +#### Вариант 12 {.unnumbered} + + +Добавьте программе опцию `-fill` для задания цвета заливки столбцов. Например, +`lab34.exe -fill red` или `lab04.exe -fill "#ff0000"` заливает их красным. +Опция может стоять до или после URL: +`lab34.exe -fill red http://...` или `lab04.exe http://... -fill red`. +Если после `-fill` не стоит еще одного аргумента, нужно печатать сообщение +с подсказкой, как запускать программу, и завершать работу. + + +#### Вариант 13 {.unnumbered} + + +Добавьте программе опцию `-stroke` для задания цвета границ столбцов. +Например, `lab34.exe -stroke red` или `lab04.exe -stroke "#ff0000"` делает их +красными. +Опция может стоять до или после URL: +`lab34.exe -stroke red http://...` или `lab04.exe http://... -fill red`. +Если после `-stroke` не стоит еще одного аргумента, нужно печатать сообщение +с подсказкой, как запускать программу, и завершать работу. + + +#### Вариант 14 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +размер запроса, который cURL отправила серверу (request size). + + +#### Вариант 15 {.unnumbered} + + +Установите callback-функцию `CURLOPT_HEADERDATA`, откуда печатайте +полученные данные на стандартный вывод ошибок. + + +#### Вариант 16 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +время, затраченное на установку соединения с сервером (connect). + + +#### Вариант 17 {.unnumbered} + + +Устанавливайте опцию `CURLOPT_FAILONERROR` и анализируйте результат +`curl_easy_perform()`, чтобы проверить результат загрузки файла, +а не выполнения запроса. Придумайте способ тестирования. + + +#### Вариант 18 {.unnumbered} + + +С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок +время, затраченное на получение IP-адреса сервера по его имени (name lookup). + + +## Контрольные вопросы + +1. Сколько аргументов в команде `git add -p file.txt`? Чему равно `argc`? +1. Из каких файлов и как собирается программа, использующая библиотеку? +1. Чем отличаются статические и динамические библиотеки? +1. Как расшифровывается термин «API» и что представляет собой API библиотеки? +1. Что такое дескриптор (handle), для чего он предназначен? +1. Что такое функция обратного вызова (callback)? +1. Зачем и как применяются пользовательские данные в callback-функциях? diff --git a/labs/lab04/assets/curl-download.png b/labs/lab04/assets/curl-download.png new file mode 100644 index 0000000..bc91dab Binary files /dev/null and b/labs/lab04/assets/curl-download.png differ diff --git a/labs/lab04/assets/curl-link.png b/labs/lab04/assets/curl-link.png new file mode 100644 index 0000000..927282e Binary files /dev/null and b/labs/lab04/assets/curl-link.png differ diff --git a/labs/lab04/assets/curl-windows.png b/labs/lab04/assets/curl-windows.png new file mode 100644 index 0000000..d780f33 Binary files /dev/null and b/labs/lab04/assets/curl-windows.png differ diff --git a/labs/lab04/assets/dll-not-found.png b/labs/lab04/assets/dll-not-found.png new file mode 100644 index 0000000..498d5ec Binary files /dev/null and b/labs/lab04/assets/dll-not-found.png differ diff --git a/labs/lab04/assets/flow.svg b/labs/lab04/assets/flow.svg new file mode 100644 index 0000000..ca88e3f --- /dev/null +++ b/labs/lab04/assets/flow.svg @@ -0,0 +1,22 @@ +Чтение данныхРасчет гистограммыОтображение гистограммытекстовый поток(стандартный ввод)массив чисел (numbers)и количество корзин (bin_count)массив количествчисел в корзинах (bins)текстовый поток(стандартный вывод) \ No newline at end of file diff --git a/labs/lab04/assets/flow.uml b/labs/lab04/assets/flow.uml new file mode 100644 index 0000000..37337c1 --- /dev/null +++ b/labs/lab04/assets/flow.uml @@ -0,0 +1,10 @@ +@startuml +(*) -d-> [текстовый поток\n(стандартный ввод)] "Чтение данных" + -r-> [массив чисел (numbers)\nи количество корзин (bin_count)] "Расчет гистограммы" + -r-> [массив количеств\nчисел в корзинах (bins)] "Отображение гистограммы" + -d-> [текстовый поток\n(стандартный вывод)] (*) +@enduml + +(*) -right--> [текст] +"Чтение данных" --right--> [числа] +"Расчет гистограммы" --right--> (*) diff --git a/labs/lab04/assets/installer.png b/labs/lab04/assets/installer.png new file mode 100644 index 0000000..b8c528c Binary files /dev/null and b/labs/lab04/assets/installer.png differ diff --git a/labs/lab04/assets/search.png b/labs/lab04/assets/search.png new file mode 100644 index 0000000..7a16c6e Binary files /dev/null and b/labs/lab04/assets/search.png differ