воскресенье, 22 января 2017 г.

Простой веб-сервер на С++

После столь долгого перерыва я решил написать статью про то как я писал веб-сервер на С++ и системных вызовах не используя никаких сторонних библиотек.
Фактически это был просто эксперимент, поэтому сразу предупреждаю что код ещё далёк от идеала по производительности и структуре.



Для начала в качестве предисловия

Идея написать свой веб-сервер на С++, пришла не просто так в мою голову, изначально это было просто тестовое задание на разработку веб-сервера для раздачи статического контента на С++ с поддержкой многопоточности. Там же мне пришла идея использовать наработки из С++11 (поэтому данный код собирается уверенно на компиляторах GCC 4.8.4 и старше)

В текущем состоянии кодовой базы на моей машине (Core i5-4210U 1.7 GHz) для тестов на запрос вкомпилированной страницы выдаются следующие результаты (фреймворк для тестирования Apache Benchmark):

Server Software:        simple
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /
Document Length:        103 bytes

Concurrency Level:      100
Time taken for tests:   4.470 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      18600000 bytes
HTML transferred:       10300000 bytes
Requests per second:    22371.59 [#/sec] (mean)
Time per request:       4.470 [ms] (mean)
Time per request:       0.045 [ms] (mean, across all concurrent requests)
Transfer rate:          4063.59 [Kbytes/sec] received


Для страницы расположенной на диске устройства:

Server Software:        simple
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /index.html
Document Length:        28914 bytes

Concurrency Level:      100
Time taken for tests:   6.048 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      2900100000 bytes
HTML transferred:       2891400000 bytes
Requests per second:    16535.44 [#/sec] (mean)
Time per request:       6.048 [ms] (mean)
Time per request:       0.060 [ms] (mean, across all concurrent requests)
Transfer rate:          468305.09 [Kbytes/sec] received


Что весьма не плохо с учётом того что никаких модулей кэширования не было применено в процессе разработки. Забор файлов идёт используя модуль fstream стандартной библиотеки С++

Общее устройство

Код сервера полностью открыт, лицензия BSD-v3 (поэтому можете копировать и использовать код под свои нужды). Ссылка на репозиторий гитхаб: https://github.com/no111u3/simple_web

Рабочий код разбит на модули:

simple_web.h(.cpp) - запуск веб-сервера, выполнение конфигурации
core.h(.cpp) - запуск ядра сервера
daemon.h - переключение в режим демона ОС Linux, в соответствии с рекомендациями
config.h(.cpp) - разбор настроек сервера и их хранение в течении всей работы
polling.h(.cpp) - ядро обслуживания мастер-сокета.
handle_message.h(.cpp) - ядро обслуживания клиентских сокетов

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

thread_pool.h(.cpp) - рабочий пул потоков с возможностью добавления задач и разделения их по очередям (локальная очередь потока и общая очередь).
function_wrapper.h - контейнер для корректного запуска/завершения задачи передаваемой в пул.
thread_safe_queue.h - общая очередь для разделения задач по потокам.
work_stealing_queue.h - очередь потока, с поддержкой заимствования

Также существует ряд сервисных модулей, без которых устройство кода было бы не таким компактным:

content_type_identify.h - статическая карта соотношений расширений файлов и их типа
join_threads.h - RAII контейнер потоков, для безопасного их завершения.
splinlock_mutex.h - мутекс основанный на атомарных переменных, по сути спинлок, так как нет приостановки потока и обращения к пространству ядра.
uint_to_str.h(.cpp) - быстрая конвертация беззнакового числа в строку (работает гораздо быстрей тех функций которые предоставляет система).

Код написан целиком с поддержкой C++11 как уже отмечалось выше, поэтому не все конструкции будут понятны человеку не знакомому с этим синтаксисом. В рабочем ядре используется системный модуль epoll и асинхронные сокеты. Для пула потоков использован пример из книги Энтони Уильямса - Concurrency in Action, конвертацию из беззнакового в строку взял на просторах интернета (приписывается разработчикам Facebook).

На этом всё