Пользователь
0,0
рейтинг
10 октября 2012 в 16:40

Разработка → Web-приложение на C/C++ с помощью FastCGI — это просто из песочницы tutorial

Добрый день.
В этой статье я бы хотел рассказать про протокол FastCGI и способы работы с ним. Не смотря на то, что сам протокол и его реализация появились ещё в 1996 году, подробных руководств по этому протоколу просто нет — разработчики так и не написали справки к собственной библиотеке. Зато года два назад, когда я только начал пользоваться этим протоколом, часто встречались фразы типа «я не совсем понимаю, как пользоваться этой библиотекой». Именно этот недостаток я и хочу исправить — написать подробное руководство по использованию данного протокола в многопоточной программе и рекомендации по выбору различных параметров, которым могли бы воспользоваться все желающие.

Хорошая новость — способ кодирования данных в FastCGI и в CGI одинаковый, меняется только способ их передачи: если CGI-программа использует интерфейс стандартного ввода-вывода, то FastCGI-программа — сокеты. Другими словами, нужно всего лишь разобраться с несколькими функциями библиотеки для работы с FastCGI, а дальше просто воспользоваться опытом написания CGI-программ, примеров которых, к счастью, очень много.

Итак, в этой статье мы рассмотрим:
— Что такое FastCGI и чем отличается от протокола CGI
— Зачем мне нужен FastCGI, когда уже есть много языков для разработки под веб
— Какие реализации протокола FastCGI существуют
— Что такое сокеты
— Описание функций библиотеки FastCGI
— Простой пример многопоточной FastCGI-программы
— Простой пример конфигурации Nginx
К сожалению, очень сложно написать статью одинаково понятной новичкам и интересной опытным старожилам, поэтому я буду стараться осветить все моменты как можно подробнее, а Вы можете просто пропустить неинтересные Вам разделы.

Что такое FastCGI?


Про FastCGI можно прочитать в Википедии. Если в двух словах, это CGI-программа, запущенная в цикле. Если обычная CGI-программа заново запускается для каждого нового запроса, то в FastCGI-программе используется очередь запросов, которые обрабатываются последовательно. А теперь представьте: на Ваш 4-8-ядерный сервер поступило 300-500 одновременных запросов. Обычная CGI-программа будет запущена на выполнение эти самые 300-500 раз. Очевидно, такого количества процессов слишком много — Ваш сервер физически не сможет отработать их все сразу. Значит, у Вас получится очередь процессов, ожидающих свой квант процессорного времени. Обычно планировщик будет распределять процессорное равномерно (так так в данном случае приоритеты всех процессов одинаковые), а значит у Вас будет 300-500 «почти готовых» ответов на запросы. Звучит как-то не очень оптимистично, не правда ли? В FastCGI-программе все эти проблемы решаются простой очередью запросов (то есть применяется мультиплексирование запросов).

Зачем мне FastCGI, когда уже есть PHP, Ruby, Python, Perl и т.п.?


Пожалуй, главная причина — компилируемая программа будет работать быстрее интерпретируемой. Для PHP, например, существует целая линейка акселераторов, среди которых — APC, eAccelerator, XCache, которые уменьшают время интерпретации кода. Но для C/C++ всё это просто не нужно.
Второе, о чём Вы должны помнить — динамическая типизация и сборщик мусора занимают много ресурсов. Иногда — очень много. Например, массивы целых чисел в PHP занимают примерно в 18 раз больше памяти (до 35 раз в зависимости от различных параметров компиляции PHP), чем в C/C++ для того же объема данных, поэтому задумайтесь о накладных расходах для сравнительно больших структур данных.
Третье — FastCGI-программа может хранить общие для разных запросов данные. Например, если PHP каждый раз начинает обработку запроса с «чистого листа», то FastCGI-программа может сделать ряд подготовительных действий ещё до поступления первого запроса, например выделить память, загрузить часто используемые данные и т.п. — очевидно, всё это может повысить общую производительность системы.
Четвёртое — масштабируемость. Если mod_php предполагает, что веб-сервер Apache и PHP находятся на одной и той же машине, то FastCGI-приложение может использовать TCP-сокеты. Другими словами, у Вас может быть целый кластер из нескольких машин, связь с которыми осуществляется по сети. При этом FastCGI также поддерживает Unix domain sockets, что позволяет при необходимости эффективно запускать FastCGI-приложение и веб-сервер на одной и той же машине.
Пятое — безопасность. Вы не поверите, но с настройками по умолчанию Apache позволяет выполнять всё на свете. Например, если злоумышленник загрузит на сайт вредоносный скрипт exploit.php.jpg под видом «невинной картинки» и потом откроет её в браузере, Apache «честно» выполнит вредоносный php-код. Пожалуй, единственное достаточно надежное решение — удалять или изменять все потенциально опасные расширения из имен загружаемых файлов, в данном случае — php, php4, php5, phtml и т.п. Такой приём используется, например, в Drupal — ко всем «дополнительным» расширениям добавляется символ подчеркивания и получается exploit.php_.jpg. Правда следует отметить, что системный администратор может добавить любое дополнительное расширение файла в качестве обработчика php, так что какое-нибудь .html может вдруг превратиться в ужасную дыру в безопасности только из-за того, что .php выглядело некрасиво, было плохо для SEO или не нравилось заказчику. Итак, что же нам даёт в плане безопасности FastCGI? Во первых, если использовать вместо Apache веб-сервер Nginx, то он будет просто отдавать статические файлы. Точка. Другими словами, файл exploit.php.jpg будет отдан «как есть», без какой-либо обработки на стороне сервера, так что запустить вредоносный скрипт просто не получится. Во вторых, FastCGI-программа и веб-сервер могут работать из под разных пользователей, а значит и права на файлы и папки у них будут разные. Например, веб-сервер может только читать загруженные файлы — для отдачи статических данных этого достаточно, а FastCGI-программа может только читать и изменять содержимое папки с загружаемыми файлами — этого достаточно для загрузки новых и удаления старых файлов, но доступа непосредственно к самим загруженным файлам иметь не будет, а значит выполнить вредоносный код тоже не сможет. В третьих, FastCGI-программа может работать в chroot'е, отличном от chroot'а веб-сервера. Сам по себе chroot (смена корневой директории) позволяет сильно ограничить права программы, то есть повысить общую безопасность системы, потому что программа просто не сможет получить доступ к файлам за пределами указанного каталога.

Какой веб-сервер с поддержкой FastCGI лучше выбрать?


Если коротко — я пользуюсь Nginx. Вообще, серверов с поддержкой FastCGI довольно много, в том числе коммерческих, так что позвольте рассмотреть несколько альтернатив.
Apache — пожалуй, это первое, что приходит в голову, правда он потребляет гораздо больше ресурсов, чем Nginx. Например, на 10 000 неактивных HTTP keep-alive соединений Nginx расходует около 2,5M памяти, что вполне реально даже для сравнительно слабой машины, а Apache вынужден создавать новый поток для каждого нового соединения, так что 10 000 потоков — просто фантастика.
Lighttpd — главный недостаток этого веб-сервера в том, что он обрабатывает все запросы в одном потоке. Это значит, что могут быть проблемы с маштабируемостью — Вы просто не сможете задействовать все 4-8 ядер современных процессоров. И второе — если по какой-то причине подвиснет поток веб-сервера (например из-за длительного ожидания ответа от жесткого диска), у вас «зависнет» весь сервер. Другими словами, все остальные клиенты перестанут получать ответы из-за одного медленного запроса.
Еще один кандидат — Cherokee. По заявлениям разработчиков, в ряде случаев работает быстрее Nginx и Lighttpd.

Какие есть реализации протокола FastCGI?


На данный момент есть две реализации протокола FastCGI — библиотека libfcgi.lib от создателей протокола FastCGI, и Fastcgi++ — библиотека классов на С++. Libfcgi разрабатывалась с 1996 года и, по заявлениям Open Market, является очень стабильной, к тому же более распространена, поэтому пользоваться в этой статье будем ей. Хочется отметить, что библиотека написана на C, встроенную «обертку» C++ нельзя назвать высокоуровневой, поэтому будем использовать C-интерфейс.
Думаю, на установке самой библиотеки останавливаться смысла нет — в ней есть makefile, так что проблем быть не должно. Кроме того, в популярных дистрибутивах эта библиотека доступна из пакетов.

Что такое сокеты?


Общее понятие о сокетах можно получить в Википедии. Если в двух словах, сокеты — это способ межпроцессного взаимодействия.
Как мы помним, во всех современных операционных системах каждый процесс использует собственное адресное пространство. За непосредственный доступ к оперативной памяти отвечает ядро операционной системы, и если программа обратиться по несуществующему (в контексте данной программы) адресу памяти, ядро вернет segmentation fault (ошибка сегментирования) и закроет программу. Это замечательно — теперь ошибки в одной программе просто не могут повредить другим — они находятся как бы в других измерениях. Но раз у программ разное адресное пространство, от общих данных или обмена данными тоже быть не может. А если очень нужно передать данные из одной программы в другую, как тогда? Собственно, для решения этой проблемы и разрабатывались сокеты — два или более процесса (читайте: программы) подключаются к одному и тому же сокету и начинают обмен данными. Получается этакое «окно» в другой мир — через него можно получать и отправлять данные в другие потоки.
В зависимости от типа использования соединения сокеты бывают разные. Например, есть TCP-сокеты — они используют обычную сеть для обмена данными, то есть программы могут работать на разных компьютерах. Второй наиболее распространенный вариант — доменные сокеты Unix (Unix domain socket) — пригодны для обмена данными только в рамках одной машины и выглядят как обычный путь в файловой системе, но реально жесткий диск не используется — весь обмен данными происходит в оперативной памяти. Из-за того, что не нужно использовать сетевой стек, работают несколько быстрее (примерно на 10%), чем TCP-сокеты. Для ОС Windows данный вариант сокетов называется named pipe (именованный канал).
Примеры использования сокетов для ОС GNU/Linux можно найти в этой статье. Если Вы еще не работали с сокетами, я бы рекомендовал с ней ознакомиться — это не является обязательным, но улучшит понимание изложенных здесь вещей.

Как пользоваться библиотекой Libfcgi?


Итак, мы хотим создать многопоточное FastCGI-приложение, поэтому разрешите описать ряд наиболее важных функций.
Прежде всего, библиотеку нужно инициализировать:
int FCGX_Init(void);

Внимание! Эту функцию нужно вызывать перед любыми другими функциями этой библиотеки и только один раз (всего один раз, для любого количества потоков).

Далее нам нужно открыть слушающий сокет:
int FCGX_OpenSocket(const char *path, int backlog);

Переменная path содержит строку подключения к сокету. Поддерживаются как доменные сокеты Unix, так и TCP-сокеты, всю необходимую работу по подготовке параметров и вызова функции библиотека сделает сама.
Примеры строк подключения для доменных сокетов Unix:
"/tmp/fastcgi/mysocket"
"/tmp/fcgi_example.bare.sock"

Думаю, тут всё понятно: нужно просто передать уникальный путь в виде строки, при этом все взаимодействующие с сокетом процессы должны иметь к нему доступ. Еще раз повторюсь: этот способ работает только в рамках одного компьютера, но несколько быстрее, чем TCP-сокеты.
Примеры строк подключения для TCP-сокетов:
":5000"
":9000"

В этом случае открывается TCP-сокет на указанном порту (в данном случае — 5000 или 9000 соответственно), при этом запросы будут приниматься с любого IP-адреса. Внимание! Данный способ потенциально небезопасен — если Ваш сервер подключен к сети Internet, то Ваша FastCGI-программа будет принимать запросы от любого другого компьютера. Значит, любой злоумышленник сможет отправить Вашей FastCGI-программе «пакет смерти». Разумеется, ничего хорошего в этом нет — в лучшем случае Ваша программа может просто «упасть» и получится отказ в обслуживании (DoS-атака, если хотите), в худшем — удаленное выполнение кода (это если совсем уж не повезёт), поэтому всегда ограничивайте доступ к таким портам при помощи файервола (межсетевого экрана), при этом доступ нужно предоставлять только тем IP-адресам, которые реально используются при штатной работе FastCGI-программы (принцип «запрещено все, что явно не разрешено»).
Следующий пример строк подключения:
"*:5000"
"*:9000"

Способ полностью аналогичен предыдущему: открывается TCP-сокет с приёмом соединений от любого IP-адреса, поэтому в этом случае так же необходимо со всей тщательностью настраивать файервол. Единственный плюс от такой строки подключения сугубо административный — любой читающий конфигурационные файлы программист или системный администратор поймёт, что Ваша программа принимает соединения с любого IP-адреса, поэтому при прочих равных условиях лучше предпочесть данных вариант предыдущему.
Более безопасный вариант — явно указать IP-адрес в строке подключения:
"5.5.5.5:5000"
"127.0.0.1:9000"

В этом случае запросы будут приниматься только от указанного IP-адреса (в данном случае — 5.5.5.5 или 127.0.0.1 соответственно), для всех остальных IP-адресов данный порт (в данном случае — 5000 или 9000 соответственно) будет закрыт. Это повышает общую безопасность системы, поэтому по возможности всегда используйте этот формат строки подключения к TCP-сокетам — а вдруг системный администратор «просто забудет» настроить файервол? Прошу обратить внимание на второй пример — там указан адрес той же машины (localhost). Это позволяет создать TCP-сокет на одной и той же машине, если по каким-то причинам Вы не можете использовать доменные сокеты Unix (например, потому что chroot веб-сервера и chroot FastCGI-программы находятся в разных папках и не имеют общих файловых путей). К сожалению, Вы не можете указать два или более разных IP-адреса, поэтому если Вам действительно нужно принимать запросы от нескольких веб-серверов, расположенных на разных компьютерах, то придётся или полностью открыть порт (см. предыдущий способ) и положиться на настройки Вашего файервола, или использовать несколько сокетов на разных портах. Так же библиотека libfcgi не поддерживает IPv6-адреса — в далёком 1996 году этот стандарт только-только появился на свет, так что придётся ограничить свои аппетиты обычными IPv4-адресами. Правда, если Вам действительно необходима поддержка IPv6, её сравнительно просто добавить, пропатчив функцию FCGX_OpenSocket — лицензия библиотеки это позволяет.
Внимание! Использование функции указания IP-адреса при создании сокета не является достаточной защитой — возможны атаки IP-спуфинга (подмены IP-адреса отправителя пакета), поэтому настройка файервола всё равно обязательна. Обычно в качестве защиты от IP-спуфинга файервол проверяет соответствие между IP-адресом пакета и MAC-адресом сетевой карты для всех хостов нашей локальной сети (точнее — для широковещательного домена с нашим хостом), и отбрасывает все поступающие из Интернета пакеты, обратный адрес которых находится в зоне частных IP-адресов или локального хоста (маски 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, 127.0.0.0/8 и ::1/128). Тем не менее, всё же лучше использовать данную возможность библиотеки — в случае неверно настроенного файервола отправить «пакет смерти» с подделанного IP-адреса гораздо сложнее, чем с любого, так как TCP-протокол имеет встроенную защиту от IP-спуфинга.
Последний вид строки подключения — использовать доменное имя хоста:
"example.com:5000"
"localhost:9000"

В этом случае IP-адрес будет получен автоматически на основе доменного имени указанного Вами хоста. Ограничения всё те же — хосту должен соответствовать один IPv4-адрес, иначе возникнет ошибка. Правда, учитывая что сокет создается один раз в самом начале работы с FastCGI, вряд ли этот способ будет очень полезен — динамически менять IP-адрес всё равно не получится (точнее, после каждой смены IP-адреса придётся перезапускать Вашу FastCGI-программу). С другой стороны, возможно это будет полезно для сравнительно большой сети — запомнить доменное имя всё же легче, чем IP-адрес.

Второй параметр функции backlog определяет длину очереди запросов сокета. Специальное значение 0 (нуль) означает длину очереди по умолчанию для данной операционной системы.
Каждый раз, когда приходит запрос от веб-сервера, новое соединение ставится в эту очередь в ожидании обработки нашей FastCGI-программой. Если очередь полностью заполнится, все последующие запросы на соединение будут заканчиваться неудачей — веб-сервер получит ответ Connection refused (в подключении отказано). В принципе, ничего плохого в этом нет — у веб-сервера Nginx есть своя очередь запросов, и если свободных ресурсов нет, то новые запросы будут ожидать своей очереди на обработку уже в очереди веб-сервера (по крайней мере до тех пор, пока не истечёт время ожидания). Кроме того, если у Вас несколько серверов с работающей FastCGI-программой, Nginx может передать такой запрос на менее загруженный сервер.
Итак, давайте попробуем разобраться, какая длина очереди будет оптимальной. Вообще, этот параметр лучше настраивать индивидуально исходя из данных нагрузочного тестирования, но мы попробуем оценить наиболее подходящий диапазон для этой величины. Первое, что нужно знать — максимальная длина очереди ограничена (определяется настройками ядра операционной системы, обычно — не более 1024 подключений). Второе — очередь потребляет ресурсы, копеечные, но всё же ресурсы, поэтому необоснованно длинной её делать не стоит. Далее, допустим у нашей FastCGI-программы есть 8 рабочих потоков (вполне реально для современных 4-8-ядерных процессоров), и каждому потоку нужно собственное подключение — задачи обрабатываются параллельно. Значит, в идеале, у нас уже должно быть 8 запросов от веб-сервера, чтобы сразу же, без ненужных задержек, обеспечить работой все потоки. Другими словами, минимальный размер очереди запросов — это количество рабочих потоков FastCGI-программы. Можно попробовать увеличить эту величину на 50%-100%, чтобы обеспечить некоторый запас по загрузке, так как время передачи данных по сети конечно.
Теперь давайте определимся с верхней границей этой величины. Тут нужно знать, сколько запросов мы реально можем обработать и ограничить очередь запросов этой величиной. Представьте, что Вы сделали эту очередь слишком большой — настолько, что Вашим клиентам просто надоедает ждать своей очереди и они просто уходят с Вашего сайта так и не дождавшись ответа. Очевидно, ничего хорошего в этом нет — веб-сервер должен был отправить запрос на открытие соединения, что само по себе дорого, а потом ещё и закрыть это соединение только лишь по тому, что FastCGI-программе не хватило времени на обработку этого запроса. Одним словом, мы только тратим процессорное время впустую, а ведь его нам как раз и не хватает! Но это еще не самое страшное — хуже, когда клиент отказался от получения информации с Вашего сайта уже поле начала обработки запроса. Получается, что мы должны будем полностью обработать в сущности уже никому не нужный запрос, что, согласитесь, только ухудшит ситуацию. Теоретически может возникнуть ситуация, когда большая часть клиентов так и не дождется ответа при 100% загрузке Вашего процессора. Нехорошо.
Итак, допустим один запрос мы можем обработать за 300 миллисекунд (то есть 0,3 секунды). Далее нам известно, что в среднем 50% посетителей покидают ресурс, если веб-страница грузится более 30 секунд. Очевидно, что 50% недовольных — это слишком много, поэтому ограничим максимальное время загрузки страницы в 5 секунд. При этом имеется ввиду уже полностью готовая веб-страница — после применения каскадных таблиц стилей и выполнения JavaScript'ов — этот этап на среднестатистическом сайте может занимать 70% от общего времени загрузки веб-страницы. Итак, на загрузку данных по сети осталось не больше 5*0,3 = 1,5 секунд. Дальше следует вспомнить, что html-код, таблицы стилей, скрипты и графика передаются в разных файлах, причём сначала — html-код, а потом уже всё остальное. Правда, после получения html-кода браузер начинает запрашивать оставшиеся ресурсы параллельно, так что можно оценить время загрузки html-кода как 50% от общего времени получения данных. Итак, в нашем распоряжении осталось не более 1,5*0,5 = 0,75 секунды на обработку одного запроса. Если в среднем один поток обрабатывает запрос за 0,3 секекунды, то в очереди должно быть 0,75/0,3 = 2,5 запроса на поток. Так как у нас 8 рабочих потоков, то результирующий размер очереди должен составлять 2,5*8 = 20 запросов. Хочется отметить условность приведенных расчетов — при наличии конкретного сайта используемые в расчете величины можно определить гораздо точнее, но всё же он дает отправную точку для более оптимальной настройки производительности.

Итак, мы получили дескриптор сокета, после этого необходимо выделить память под структуру запроса. Описание этой структуры следующее:
typedef struct FCGX_Request { 
    int requestId;
    int role; 
    FCGX_Stream *in; 
    FCGX_Stream *out; 
    FCGX_Stream *err; 
    char **envp; 
   struct Params *paramsPtr; 
    int ipcFd;
    int isBeginProcessed;
    int keepConnection;
    int appStatus; 
    int nWriters;
    int flags; 
    int listen_sock; 
    int detached; 
} FCGX_Request;

Внимание! После получения нового запроса все предыдущие данные будут утеряны, поэтому при необходимости длительного хранения данных применяйте глубокое копирование (копируйте сами данные, а не указатели на данные).
Вы должны знать об этой структуре следующее:
— переменные in, out и err играют роль соответственно потоков ввода, вывода и ошибок. Поток ввода содержит данные POST-запроса, в поток вывода нужно отправить ответ FastCGI-программы (например, http-заголовки и html-код веб-страницы), а поток ошибок просто добавит запить в лог ошибок веб-сервера. При этом потоком ошибок можно вообще не пользоваться — если Вам действительно нужно логгировать ошибки, то, пожалуй, для этого лучше использовать отдельный файл — передача данных по сети и их последующая обработка веб-сервером потребляет дополнительные ресурсы.
— переменная envp содержит значения переменных окружения, устанавливаемых веб-сервером, и http-заголовки, например: SERVER_PROTOCOL, REQUEST_METHOD, REQUEST_URI, QUERY_STRING, CONTENT_LENGTH, HTTP_USER_AGENT, HTTP_COOKIE, HTTP_REFERER и так далее. Эти заголовки определяются соответственно стандартами CGI и HTTP протоколов, примеры их использования можно найти в любой CGI-программе. Сами данные хранятся в массиве строк, при этом последний элемент массива содержит нулевой указатель (NULL) в качестве обозначения конца массива. Каждая строка (каждый элемент массива строк) содержит одно значение переменной в формате НАЗВАНИЕ_ПЕРЕМЕННОЙ=ЗНАЧЕНИЕ, например: CONTENT_LENGTH=0 (в данном случае означает, что у данного запроса нет POST-данных, так как их длина равна нулю). Если в массиве строк envp нет нужного Вам заголовка, значит он не был передан. Если Вы хотите получить все переданные FastCGI-программе значения переменных, просто прочитайте в цикле все строки массива envp до тех пор пока не встретите указатель на NULL.
Собственно, на этом с описанием этой структуры мы закончили — всё остальные переменные Вам не понадобятся.

Память выделили, теперь нужно выполнить инициализацию структуры запроса:
int FCGX_InitRequest(FCGX_Request *request, int sock, int flags);

Параметры функции следующие:
request — указатель на структуру данных, которую нужно инициализировать
sock — дескриптор сокета, который мы получили после вызова функции FCGX_OpenSocket. Хочется отметить, что вместо уже готового дескриптора можно передать 0 (нуль) и получить сокет с настройками по умолчанию, но для нас данный способ совершенно не интересен — сокет будет открыт на случайном свободном порте, а, значит, мы не сможем правильно настроить наш веб-сервер — нам неизвестно заранее, куда именно нужно отправлять данные.
flags — флаги. Собственно, в эту функцию можно передать только один флаг — FCGI_FAIL_ACCEPT_ON_INTR — не вызывать FCGX_Accept_r при разрыве.

После этого нужно получить новый запрос:
int FCGX_Accept_r(FCGX_Request *request);

В неё нужно передать уже инициализированную на прошлом этапе структуру request. Внимание! В многопоточной программе необходимо использовать синхронизацию при вызове данной функции.
Собственно, эта функция выполняет всю работу по работе с сокетами: сначала она отправляет ответ веб-серверу на предыдущий запрос (если таковой был), закрывает предыдущий канал передачи данных и освобождает все связанные с ним ресурсы (в том числе — переменные структуры request), потом получает новый запрос, открывает новый канал передачи данных и подготавливает новые данные в структуре request для их последующей обработки. В случае ошибки получения нового запроса функция возвращает код ошибки, меньший нуля.

Далее Вам наверняка потребуется получить переменные окружения, для этого можно или самостоятельно обработать массив request->envp, или воспользоваться функцией
char *FCGX_GetParam(const char *name, FCGX_ParamArray envp);

где name — строка, содержащая название переменной окружения или http-заголовка, значение которого Вы хотите получить,
envp — массив переменных окружения, которые содержатся в переменной request->envp
Функция возвращает значение нужной нам переменной окружения в виде строки. Пусть внимательного читателя не пугает несоответствие типов между char ** и FCGX_ParamArray — эти типы объявлены синонимами (typedef char **FCGX_ParamArray).
Кроме того, Вам наверняка понадобится отправить ответ веб-серверу. Для этого нужно воспользоваться потоком вывода request->out и функцией
int FCGX_PutStr(const char *str, int n, FCGX_Stream *stream);

где str — буффер, содержащий данные для вывода, без завершающего нуля (то есть буффер может содержать двоичные данные),
n — длинна буффера в байтах,
stream — поток, в который мы хотим вывести данные (request->out или request->err).

Если Вы пользуетесь стандартами C-строками с завершающим нулём, удобнее будет использовать функцию
int FCGX_PutS(const char *str, FCGX_Stream *stream);

которая просто определит длину строки функцией strlen(str) и вызовет предыдущую функцию. Поэтому, если Вам заранее известна длина строки (например, Вы пользуетесь C++-строками std::string), лучше используйте предыдущую функцию по соображениям эффективности.
Хочется отметить, что эти функции прекрасно работают с UTF-8-строками, так что с многоязычными веб-прилодениями проблем быть не должно.
Вы так же можете вызывать эти функции несколько раз во время обработки одного и того же запроса, в ряде случаев это может повысить производительность. Например, Вам нужно отправить какой-то большой файл. Вместо того, чтобы загружать весь этот файл с жёсткого диска, а потом уже отправить его «одним куском», Вы можете сразу же начать отправлять данные. В результате клиент вместо белого экрана браузера начнёт получать интересующие его данные, что чисто психологически заставит его ещё немного подождать. Другими словами, Вы как бы выигрываете немного времени для загрузки страницы. Так же хочется отметить, что большинство ресурсов (каскадные таблицы стилей, JavaScript'ы и т.п.) указываются в начале веб-страницы, то есть браузер сможет проанализировать часть html-кода и начать загрузку этих ресурсов раньше — ещё один повод выводить данные по частям.

Следующее, что Вам может понадобиться — это обработать POST-запрос. Для того, что бы получить его значение, нужно прочитать данные из потока request->in при помощи функции
int FCGX_GetStr(char * str, int n, FCGX_Stream *stream);

где str — указатель на буффер,
n — размер буффера в байтах,
stream — поток, из которого мы читаем данные.
Размер передаваемых данных в POST-запросе (в байтах) можно определить с помощью переменной окружения CONTENT_LENGTH, значение которой, как мы помним, можно получить с помощью функции FCGX_GetParam. Внимание! Создавать буффер str на основании значения переменной CONTENT_LENGTH без каких-либо ограничений очень плохая идея: любой злоумышленник может отправить любой, сколь угодно большой POST-запрос, и у Вашего сервера может просто закончиться свободная оперативная память (получится DoS-атака, если хотите). Вместо этого лучше ограничить размер буффера какой-то разумной величиной (от нескольких килобайт до нескольких мегабайт) и вызывать функцию FCGX_GetStr несколько раз.

Последняя важная функция флеширует потоки вывода и ошибок (отправляет клиенту всё ещё не отправленные данные, которые мы успели поместить в потоки вывода и ошибок) и закрывает соединение:
void FCGX_Finish_r(FCGX_Request *request);

Хочется особо отметить, что эта функция не является обязательной: функция FCGX_Accept_r так же отправляет клиенту данные и закрывает текущее соединение перед получением нового запроса. Спрашивается: тогда зачем же она нужна? Представьте, что Вы уже отправили клиенту все необходимые данные, и сейчас Вам нужно выполнить какие-то завершающие операции: записать статистику в базу данных, ошибки в лог-файл и т.п. Очевидно, что соединение с клиентом уже больше не нужно, но клиент (в смысле, браузер) всё ещё ждёт от нас информацию: а вдруг мы отправим что-нибудь еще? При этом очевидно, что мы не можем вызвать FCGX_Accept_r раньше времени — после этого нужно будет начать обрабатывать следующий запрос. Как раз в этом случае Вам понадобится функция FCGX_Finish_r — она позволит закрыть текущее соединение до получения нового запроса. Да, мы сможем обработать такое же число запросов в единицу времени, как и без использования этой функции, но клиент получит ответ раньше — ему уже не придется ждать конца выполнения наших завершающих операций, а ведь именно из-за большей скорости обработки запросов мы и используем FastCGI.
На этом, собственно, заканчивается описание функций библиотеки и начинается обработка полученных данных.

Простой пример многопоточной FastCGI-программы


Думаю, в примере всё будет понятно. Единственное, печать отладочных сообщений и «засыпание» рабочего потока сделаны исключительно в демонстрационных целях. При компилировании программы не забудьте подключить библиотеки libfcgi и libpthread (параметры компилятора gcc: -lfcgi и -lpthread).

#include <pthread.h> 
#include <sys/types.h> 
#include <stdio.h> 

#include "fcgi_config.h" 
#include "fcgiapp.h" 


#define THREAD_COUNT 8 
#define SOCKET_PATH "127.0.0.1:9000" 

//хранит дескриптор открытого сокета 
static int socketId; 

static void *doit(void *a) 
{ 
    int rc, i; 
    FCGX_Request request; 
    char *server_name; 

    if(FCGX_InitRequest(&request, socketId, 0) != 0) 
    { 
	    //ошибка при инициализации структуры запроса 
        printf("Can not init request\n"); 
	    return NULL; 
    } 
    printf("Request is inited\n"); 
 
    for(;;) 
    { 
        static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; 

        //попробовать получить новый запрос 
        printf("Try to accept new request\n"); 
        pthread_mutex_lock(&accept_mutex); 
        rc = FCGX_Accept_r(&request); 
        pthread_mutex_unlock(&accept_mutex); 

        if(rc < 0) 
        { 
	        //ошибка при получении запроса 
            printf("Can not accept new request\n"); 
            break; 
        } 
        printf("request is accepted\n"); 

        //получить значение переменной 
        server_name = FCGX_GetParam("SERVER_NAME", request.envp); 

        //вывести все HTTP-заголовки (каждый заголовок с новой строки) 
        FCGX_PutS("Content-type: text/html\r\n", request.out); 
        //между заголовками и телом ответа нужно вывести пустую строку 
        FCGX_PutS("\r\n", request.out); 
        //вывести тело ответа (например - html-код веб-страницы) 
        FCGX_PutS("<html>\r\n", request.out); 
        FCGX_PutS("<head>\r\n", request.out); 
        FCGX_PutS("<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title>\r\n", request.out); 
        FCGX_PutS("</head>\r\n", request.out); 
        FCGX_PutS("<body>\r\n", request.out); 
        FCGX_PutS("<h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>\r\n", request.out); 
        FCGX_PutS("<p>Request accepted from host <i>", request.out); 
        FCGX_PutS(server_name ? server_name : "?", request.out); 
        FCGX_PutS("</i></p>\r\n", request.out); 
        FCGX_PutS("</body>\r\n", request.out); 
        FCGX_PutS("</html>\r\n", request.out); 

        //"заснуть" - имитация многопоточной среды 
        sleep(2); 

        //закрыть текущее соединение 
        FCGX_Finish_r(&request); 
        
        //завершающие действия - запись статистики, логгирование ошибок и т.п. 
    } 
 
    return NULL; 
} 

int main(void) 
{ 
    int i; 
    pthread_t id[THREAD_COUNT]; 

    //инициализация библилиотеки 
    FCGX_Init(); 
    printf("Lib is inited\n"); 
    
    //открываем новый сокет 
    socketId = FCGX_OpenSocket(SOCKET_PATH, 20); 
    if(socketId < 0) 
    { 
	    //ошибка при открытии сокета 
	    return 1; 
    } 
    printf("Socket is opened\n"); 

    //создаём рабочие потоки 
    for(i = 0; i < THREAD_COUNT; i++) 
    { 
        pthread_create(&id[i], NULL, doit, NULL); 
    } 
    
    //ждем завершения рабочих потоков 
    for(i = 0; i < THREAD_COUNT; i++) 
    { 
        pthread_join(id[i], NULL); 
    } 

    return 0; 
}


Простой пример конфигурации Nginx


Собственно, простейший пример конфига выглядит так:

server { 
	server_name localhost; 

	location / { 
		fastcgi_pass 127.0.0.1:9000; 
		#fastcgi_pass  unix:/tmp/fastcgi/mysocket; 
		#fastcgi_pass localhost:9000; 
		 
		include fastcgi_params; 
	} 
}


В данном случае этого конфига достаточно для корректной работы нашей FastCGI-программы. Закоментированные строчки — это пример работы с соответственно доменными сокетами Unix и заданием доменного имени хоста вместо IP-адреса.
После компиляции и запуска программы, и настройки Nginx у меня по адресу localhost появилась гордая надпись:
FastCGI Hello! (multi-threaded C, fcgiapp library)

Спасибо всем, кто дочитал до конца.
@janagan
карма
24,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (103)

  • –34
    Дочитал до
    Зачем мне FastCGI, когда уже есть PHP...
    и дальше не стал.

    Вы сравниваете кислое с длинным — php замечательно работает с fastCGI, о чем вы можете прочитать, например вот тут
    • +1
      Перед тем как, что-то писать про FastCGI — прочитайте, что такое FastCGI и чем это отличается от CGI. А потом подумайте и скажите — почему то, что вы зазываете FastCGI не настоящий FastCGI. А автор же конечно хотел сказать, зачем ему C++. Любой из этих языков может работать в FastCGI режиме (PHP приэтом нигде так не запускается ибо надо думать головой)
  • +18
    Не портьте мне мой C++ вашим Web'ом!
    • +5
      Наоборот, отлично. Мгновенная обработка страниц на скромных серверах, значительное сокращение времени решения затратных задач, оформление часто изменяющихся параметров, как то счетчики.
      Никто не заставляет Вас писать на с++ весь сайт, выносим узкие моменты и радуемся.
      • +2
        >>Выносим узкие моменты и радуемся
        узким моментом зачастую бывает работа с БД
        • +5
          FastCGI + C++, какая БД, о чём вы?
          Всё в памяти процесса, только хардкор!: )
          • 0
            Я тоже так думал, пока небольшой проектик не стал кушать гигабайты. Пришлось изобретать динамическую загрузку/выгрузку, статистику использования, индексы… В какой-то момент я понял, что изобретаю очередной sqlite или что-то подобное. Не, но фана конечно было много.
            • 0
              Пара гигов памяти под небольшой, но требовательный к скорости проект — мелочи. Если же данных действительно так много, что в память не влезут никак — это уже совсем другая история и другие архитектурные решения :)
              • 0
                Любой проект требователен, вопрос в нагрузке. Задача была сделать систему комментариев/аттачей (как на хабре), после смешных 30к комментариев было решено свернуть лавочку. Т.е. данных в общем-то нет вообще, а память непонятно куда утекла (валгринд показывал, что ничего не течет в плохом смысле). Конечно, вопрос в архитектуре, которую я не осилил.
        • –4
          Не бывает, если БД построена на Redis
          • 0
            К сожалению, на больших нагрузках, редис любит залипать. Первый раз столкнулся, когда попытался заменить им мемкеш для кэширования запросов к базе в проекте с 150 хитов в секунду (5-6 запросов на страницу… к кэшу, естественно, в основном, а не базе).
            Судя по бенчмаркам — плевая нагрузка для редиса должна быть, но нет. Так что не нужно рассматривать его как панацею.
  • +1
    Почему не реализовать http интерфейс на с++? Зачем именно fastcgi?
    • +4
      иногда лучше «все лишнее» отдать Apache/nginx
      • +1
        Так отдавайте, nginx и http proxy_pass вам в помощь. Вопрос в том — почему apache/nginx должны связываться по fastcgi, а не http? Намного больше реализаций http интерфейсов, чем fastcgi.
        • +12
          Потому что FastCGI значительно лучше подходит для работы с backend приложениями чем HTTP. Через FastCGI можно передавать служебную информацию, иметь отдельный канал для ошибок, управлять приложением, делать мультиплексирование и многое другое. HTTP разрабатывался для общения клиент-сервер и там нет адекватного способа отделить служебную информацию от того что прислал клиент например.
          К сожалению ничего этого не будет работать при такой деревянной реализации FastCGI как описана в этой статье. Мы попытались реализовать весь этот потенциал в Helicon Zoo, но как выяснилось ни один из существующих backend фреймворков не поддерживает полноценно стек FastCGI, а реализует его так же деревянно как описано тут. Так что не для кого стараться пока.
          • 0
            Всё это может делать и http сервер.
            • 0
              мне кажется начинаются религиозные распри… [сколько людей — столько и мнений]

              ниже речь пойдет о реализации быстрого приложениия на Сях.

              HTTP хорош, надо парсить самому заголовки и делать много нудной работы, есть libevent ( ev_http) нам в помощь. nginx можно использовать как прокси.
              Придется самому реализовывать GET/POST маршрутизацию (имеется ввиду вызов необходимых функций в соответстввеее с урлом. ) D libevent HTTP реализован не в полном объеме (кажется нет обработки методов PUT, DELETE, но легко пропатчить ) что затруднительно реализовать RESTFull

              FastCGI — тоже хорош, и за тебя разобрали все заголовки, а nginx разберет урл и сделает необходимый роутинг, и все что надо и даже больше (например GeoIP) положили в окружение. Все есть как на блюдечки… Только реализовывай свой бэкенд…
              реализация FastCGI можно либо используя либу, можно самостоятельно используя libev/libevent

              Лично я предпочитаю scgi — он проще в реализации. Есть либа libscgi, на данный момент меня устраивает, но надо допиливать на неблокируемые соединения [знаю как — но нет времени].

              Область применения вышесказаннного — реализация AJAX счетчиков (SCGI), отдача баннернного контента (HTTP), в данном случае url на статический контент. И то и другое стояло за nginx.
              • 0
                1. Добрый вечер.

                2. Роутинг в nginx будет работать в обоих случаях — директивы реверс и fastcgi прокси являются уровня Location.

                3. Разобранные заголовки от frontend сервера им же складываются в поток байт для передачи по bsd или unix сокетам, что требует такого же парсинга на стороне бекэнда.

                4. Nginx в обоих случаях может добавлять к запросу необходимые данные в виде дополнительных заголовков
                или переменных fastcgi, что является равнозначным с.п. 3.

                5. Религия здесь отсутствует.
        • 0
          Все не правильно — надо связывать все по ZeroMQ как в Mongrel2 :)
          • 0
            а что будет класть данные в 0MQ?
      • 0
        А что такое «все лишнее», парсинг заголовка запроса? А что еще лишнее? Если головы на плечах не иметь, то и с либой fastcgi можно дыр таких сделать, что мало не покажется. Зато для использования обязательно нужна поддержка со стороны сервера, работать может только с сервером. А взять готовые решения для HTTP протокола и вуаля, вообще делать ничего не надо и может работать самостоятельно без всяких там серверов, а может и за реверс прокси висеть.
    • 0
      Кластеризация прежде всего.
      • 0
        Что кластеризация?
        • +4
          прежде всего
    • 0
      Потому что велосипеды изобретать нет смысла.
      • 0
        Причем тут велосипед? Я про разницу между fastcgi и http демоном на с++.
        • +2
          Ну как сказать… Писать свой демон HTTP — это либо сложно и долго, либо небезопасно и глючно. FastCGI в этом плане лучше подходит на роль интерфейса для программной логики. Исходя из этого я посчитал что лучше уж отдать обработку HTTP чему-то более приспособленному для таких целей.

          Производительность, конечно же будет выше в случае «монолитного» демона.
          • 0
            Почему свой? Есть уже тысячи реализаций, написанных до вас. Вопрос про разницу — взять готовую имплементацию fastcgi или готовую исплементацию http?
          • 0
            почти все сказано тут habrahabr.ru/post/154187/#comment_5264087
    • 0
      Потому что настоящий веб-сервер — штука сложная. Пусть постановкой запросов в очередь, безопасностью и т.д. занимается профессионально написанный сервер, а генерацией страниц — простенькая программа, ненамного сложнее PHP-скрипта.
      • 0
        все хором идем учить lua
  • 0
    Была идея писать на С++. Но когда во время работы постоянно сталкиваешься тем что на языке X есть такая-то библиотека для работы с тем-то а на С++ нет, то вскоре понимаешь что это плохая идея
    • +7
      на Си/С++ есть почти все, что нужно в современном WEB
      • 0
        Кстати, есть даже MVC-фреймворк, правда с немного дебильным названием: CPP CMS.
  • +1
    Вот так, с помощью нехитрых приспособлений буханку белого (или черного) хлеба можно превратить в троллейбус… но зачем?

    PS. Если уж так свербит писать web на c++ то возьмите хотя бы готовые фреймворки чтобы не изобретать велосипед — www.webtoolkit.eu/wt#/, cppcms.com/wikipp/en/page/main
    • +3
      Вы уверены, что реализация http-сервера в nginx более велосипедна, нежели в представленных Вами поделках?
      Почему Вы считаете FastCGI велосипедом, если это давно отработанная технология?
      А если на с++ мы напишем счетчик посещения highload проекта, а отображение страниц будет отдано php?
      • 0
        Никто не говоил что FastCGI велосипед, но вот писать с нуля web-обработчик FastCGI запросов на C++ это точно велосипед.
        Нет, если вам нужно просто разобраться в технологии вопрос другой, но использовать это в продакшене абослютно нецелесообразно. Что касается счетчика посещений, думаю что вопрос упрется больше в архитектуру нежели в язык реализации.
        • +1
          Велосипеды изобретать бывает полезно, чтобы понять, как они работают.
          • +1
            Не вопрос, если разобраться — я только за.
          • 0
            вот главный вопрос где необходимо остановиться, например, async request handling:
            — на уровне библиотеки высокого уровня;
            — на уровне библиотеки низкого уровня;
            — спуститься еще ниже и написать свой собственный кусок кода для OS;
            — написать свою OS.
            • 0
              все зависит от задачи…
              думаю что осей хватает под всевозможные задачи, но есть вероятность, что кусок кода под ось написать придется ;)
    • +1
      лично мне больше нравятся cppnetlib + ctemplate. обе открыты, предоставляют лишь базовые интерфейсы, мало жрут ну и в довесок проект можно собирать как под виндой так и под nix системами.
  • +7
    Спасибо, просто замечательное руководство для начинающего! Попадись оно мне раньше :)
    Сам писал как-то подобное в такой же связке (C++ + fastCGI + nginX). Так же рассчитывал высокую скорость работы и низкие затраты по памяти. Прошло время, и я понял, что это просто бессмысленно для 99% случаев. Java в сочетании с Jetty, например, помогает сделать всё это на два порядка проще, при этом в скорости не проигрываешь (разве что в памяти, но это отдельный разговор :) ).
    • 0
      Попробуйте Netty, он ещё быстрее и памяти меньше ест.
  • 0
    все замечательно и даже черезчур доходчиво,
    именно то что нужно начинающему разработчику

    но к сожалению, описание самого протокола FastCGI, я так и не нашел

    PS в своих проектах использую scgi, он проще в реализации
  • 0
    > а Apache вынужден создавать новый поток для каждого нового соединения, так что 10 000 потоков — просто фантастика

    Можно здесь по подробней (лучше даже конкретную цифру). На сколько я знаю, в Linux форки одного процеса (или потока) могут использовать смежную страницу памяти с кодом, что значит прирост памяти при создании нового потока — это лишь полезная нагрузка нового соединения.
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Понял.

        Просто в тексте звучит так, как будто именно памяти Apache не хватит.

        > Например, на 10 000 неактивных HTTP keep-alive соединений Nginx расходует около 2.5M памяти, что вполне реально даже для сравнительно слабой машины, а Apache вынужден создавать новый поток для каждого нового соединения, так что 10 000 потоков — просто фантастика.
    • +1
      Форки и потоки — это фундаментально разные вещи. Потоки шарят между собой все (и код и данные), а форки — это полностью отдельные процессы.

      Но суть не в этом, а в том, что 10 000 потоков (или процессов) — это грандиозные затраты на перключение контекста, способные поставить на колени даже сервер о 64х ядрах.

      Не зря не рекомендуется запускать потоков больше чем количество ядер.
      • +2
        Вы не совсем правы, если потоки много времени проводят в блокирующих сисколах — то их надо делать больше, чем ядер. Скажем, если каждый поток проводит 50% времени в блокирующих операциях, то надо минимум 48 потоков на 24-ядерной машине, чтобы её нагрузить. Иногда максимальную производительность удаётся получить получить только при нескольких тысячах потоков в тредпуле на 24 ядрах.
    • 0
      Новый поток — сами по себе — оверхед, дело не в том сколько он памяти занимает.
    • 0
      Если быть точным, то потоки одного процесса используют общее адресное пространство; форки же одного и того же процесса формально имеют независимые адресные пространства, но фактически ядро при форке не выполняет дорогостоящую операцию копирования всего АП, а использует механизм copy-on-write, при котором физическое копирование страницы в памяти из АП родительского процесса в АП дочернего происходит только при попытке записи на эту страницу.
    • 0
      Если отключить из апача всё что не надо и поставить mod_cas, то очень даже хорошо всё работает на C++, хоть и геморно.
  • +8
    И тут в тредик врывается github.com/lmovsesjan/Fastcgi-Daemon!

    Крайне рекомендую — тредпул и всё необходимое уже сделано, хэлперы для работы с запросом и ответом есть (чтение/установка хэдеров, работа с пост и гет параметрами, куками и т.д.). Для этого фреймворка приложение на плюсах — всего лишь 1 класс, унаследованный от базового. Легко пережёвывает большие нагрузки, своими глазами видел цифру в 30 000 запросов в секунду, и это было реальное приложение, которое делало полезную работу. Проверено десятком проектов Яндекса :)
    • 0
      Последнее изменение в том коде сделано достаточно давно. Просто нечего менять? И автор, похоже, в Яндексе уже не работает — мне письмо вернулось. Интересно, есть ли он на Хабре?
      • 0
        Да, менять особо нечего, редко-редко баги фиксятся и всё. Письма можете мне слать (мыло дам в личке, ежели надо), или сразу пулл-реквесты делать, я умею их принимать в той репке. Илья Голубцов ещё в Яндексе работает, а вот вася свалил, да.
  • –12
    На C++ веб-приложения и прикладные сервера долго не писали и не пишут по одной очень простой причине — неуправляемое окружение. Любой memoryleak, любой сегфолт — и вполне вероятно что ваше приложение рухнет вместе со всей операционкой. Вполне вероятно, что в продакшене. В 3 часа ночи.

    Поэтому, историчеки, веб работает на интерпретируемых языках или виртуальных машинах. C++ там лучше не использовать, как и прочие низкоуровневые языки.
    • +7
      А интерпретаторы этих языков пишутся на чем?..
      • +1
        Ключевой момент тут — квалификация писателей интерпретаторов.
        • 0
          А вы обычно нанимаете php-мартышек за еду?
      • +3
        Вы не видите разницы между интерпретаторами, которые пишутся по четким спецификациям и годами не выходят из бет, и бизнес-логикой которая переписывается по сто раз в день?
        • 0
          Если что-то переписывается по 100 раз в день, то из этого вполне может выйти интерпретатор (или даже виртуальная машина).
          • 0
            Ну и мы пришли к тому к чему начали — нужно управляемое и устойчивое окружение. Зачем тогда изобретать велосипед?
    • +1
      Такие вещи как Hight Availability systems и прочие Watchdog'и вам не знакомы? И то, что приложения на интерпретируемых языках не падают — прохладная история.
      • –6
        И на чем же их внезапно пишут? ;))))))))))))))
        Гугль говорит, что почему то в основном на вполне виртуальной Java. А местами до сих пор так и вообще на Коболе.

        Ключевой момент не в падении, а в последствиях для системы.
        Виртуальная машина почистит память и запустит процесс заново.
        Протухший указатель в плюсплюсном коде на Ubuntu 12.04 недавно повесил мне иксы.
        • +1
          Кого «их»? Если HA системы, то, например, на моём текущем месте работы — на plain С. Мне трудно представить, что может произойти, что вызовет большие последствия, нежели доли секунды простоя для части клиентов.

          Какое отношение имеет userspace Ubuntu 12.04 к HA/real-time системам — ни малейшего представления.
          • 0
            Вы же прекрасно понимаете, что ваше конкретное место работы — далеко не показатель, тем более без озвучивания конкретных задач и выделенных на их решение ресурсов. Ну и я сильно сомневаюсь, что вы там пишете сайты на fastcgi.

            Userspace убунты ни при чем. А протухшая память и ошибки разработчиков очень даже.
            • 0
              Ох, а мне было показалось на мгновение, что вы искренне и серьёзно.
              Прошу прощения, но придётся сократить рацион, всех благ.
              • 0
                Вы о чем?
                • 0
                  Слишком толсто, бро, кушаешь много и неаккуратно.
                  • 0
                    Отсутствие аргументов уже принято скрывать за обвинениями в троллинге?
          • 0
            Не говоря уже о том, что гугление «Accenture high avalaibility» выдает в основном ту же Java.
    • +1
      Да, стоимость разработчиков и время разработки на Ruby, PHP или Perl будет меньше. Но если нужна высокая производительность — то придется платить хорошему С++ программисту, который пишет код без утечек памяти и проверяет продукт утилитами, следящими за валидностью доступа к памяти. Дать этому программисту больше времени, ибо на С++ кода придется написать больше.
      Вопрос простой — что дешевле докупить оборудования (если продукт легко масштабируется, что бывает не всегда) или заплатить больше за разработку.
      • –1
        Кому нужна настолько высокая производительность на application-слое веб-приложения? ;)
        Не окупится никогда. Эти ресурсы нужно пускать на оптимизацию базы и фоновых процессов, эффекта будет гораздо больше.
        • 0
          Есть небольшая такая разница в аренде 100 серверов и 500 серверов.
          • 0
            Есть небольшая такая разница в нагрузке на разные слои. Вы не увидите разницы в несколько миллисекунд, если выборка из базы работает секунды.
            • +1
              А вы сделайте базу быстрой, тогда тормозить будет шаблонизатор.
    • +8
      > Любой memoryleak, любой сегфолт — и вполне вероятно что ваше приложение рухнет вместе со всей операционкой

      Либо у вас сервера работают под MSDOS либо вы еще не узнали что у современных операционок есть виртуальная память. Memory leak в пользовательском процессе не заставит рухнуть всю операционку.
      • –3
        Обратите внимание на слово «исторически». Ну и сегфолты пока никто не отменял.
    • +2
      image

      Вы, уважаемый вообще мышей не ловите, проходите мимо, не останвливаетесь.
      Продукты компании ISPsystem для автоматизации хостинг процессов написаны на чистом C++, например.

      Вы не сравнивайте всякий гавнокодинг скриптиков школотой и написание нормальных быстрых framework'ов и высоконагруженных сервисов.
      • +2
        Вы серьезно не видите разницы между «веб-приложениями и прикладными серверами» и автоматизацией хостинга? ;)))))
      • 0
        ISPsystem и толку с того, что продукты написаны на C++? Простая миграция акка между серверами оборачивается вознёй на несколько дней (из опыта). А вот к примеру cPanel (написана на Perl с большего) этот процесс протекает безболезненно.
        • 0
          Все эти проблемы — плата за гибкость, которой хочет клиент. У cPanel есть свой набор ПО, с которым они работают, а панели ISPsystem позволяют поставить то, что хочется использовать каждому в отдельности.
          При всё этом система переносов работает достаточно стабильно. Вы можете себе представить сколько различных случаев и проблем учитывается в ней, при свободе выбора ПО для работы?

          Вообще, это топик не о миграции в ISPmanager, а о языках.
  • +1
    где картинки? Как можно делать такие серьёзные заявления без картинок?
  • 0
    А где же обещанный в названии С++? Даже встретив struct я еще на что-то надеялся ;)

    Увы.
  • 0
    А g-wan кто-нибудь пробовал использовать?
    • 0
      К сожалению, протекает и падает, завести тоже не тривиально.
  • +1
    Ну как сказать, самому по долгу службы приходится иметь дело с FastCGI на C++. Но я бы хотел вас обрадовать, используя boost::asio реализация будет в разы проще, при этом это будет полноценный сервер, который работает по HTTP протоколу. Опять же модуль для nginx/apache реализовать не сложнее (хотя для nginx придется поковыряться в исходах), чем реализовать FastCGI демон, но при этом будет заметно эффективнее. Конечно nginx и apache не позволят работать многопоточно (но и часто нет смысла в многопоточности, сложность синхронизации даст о себе знать, а в неумелых руках может привести и к падению производительности, в сравнении с тем же кол-вом процессов).

    Опять же вся разница в FastCGI и собственной реализацией HTTP протокола заключается в некоторой дополнительной более или менее безопасной прослойке, которая распарсит частично заголовок и прокинет в виде переменных окружения, реализовать свой парсинг этих данных дело не более чем одного дня, а запрятать можно за тем же nginx. А если еще взять за основу пример из boost::asio который частично реализует работу с HTTP протоколом, будет совсем легко.

    И кстати да, где C++? Вижу только C.
  • 0
    Пользуясь случаем, вброшу соседний пост.
  • 0
    Взгляд зацепился за

    Второе, о чём Вы должны помнить — динамическая типизация и сборщик мусора занимают много ресурсов. Иногда — очень много. Например, массивы целых чисел в PHP занимают примерно в 18 раз больше памяти (до 35 раз в зависимости от различных параметров компиляции PHP), чем в C/C++ для того же объема данных, поэтому задумайтесь о накладных расходах для сравнительно больших структур данных.

    Массивы в PHP — это ведь хэш-таблицы со значениями — указателями? Динамическая типизация и сборщик мусора никакого отношения к тому, что они занимают больше памяти, не имеют. Массив чисел в C обычно в совсем другой структуре данных хранятся, вот и все. Хэш-таблица со значениями-указателями и в C/C++ будет много места занимать. Тут не в языке дело, а в выборе структуры данных.

    В динамических языках тоже есть возможность выбирать структуры данных (и создавать свои). Например, в питоне есть тип list — это массив указателей, по сути. А есть array.array — это одномерный массив чисел. И вот array.array будет столько же места в памяти занимать, сколько и соответствующий ему массив на C. На создание объекта array.array есть небольшие накладные расходы (порядка 50 байт, чтоб завернуть в объект), но это совершенно не важно именно для «больших структур данных» — какая разница, 50 + N*sizeof(elem) или N*sizeof(elem).

    В php наверное что-то похожее тоже есть, но не спец тут.
    • 0
    • 0
      Нет похожего в PHP. Все пользовательские структуры данных имеют динамически типизируемые элементы, а значит занимают много больше памяти, чем требуется для хранения значения элемента — как минимум хранение его типа необходимо, не говоря о резервировании памяти под значения большего размера, чтобы при изменении типа не выделять память по новой. А list и array.array в Python (если допустить что их по памяти хорошо оптимизировали), хранят значения только одного типа и на указание типа каждого элемента память не тратится (в случае list имею в виду указатели на значения, сами значения хранят свой тип).
  • 0
    Кроме fastcgi существует незаслуженно мало упоминаемый scgi. Это более лёгкая версия fastcgi, которая к тому же быстрее парсится. Тем, кто планирует действительно эффективные backend-ы, рекомендую обдумать этот вариант.
    • 0
      Подскажите, пожалуйста, где найти информацию по scgi? Интересует описание протокола, примеры использования, существующие реализации (желательно — на C, но другие языки тоже интересны).
  • +1
    Поправьте пожалуйста пост, правильные параметры компилятора: gcc: -lfcgi и -lpthread
    За статью спасибо.
    • 0
      Спасибо за уточнение, статью поправил.
  • 0
    Нельзя ли написать реализацию для нескольких ip адресов в библиотеке libfcgi?
    Соответственно, надо найти место где парсится адрес от порта в int FCGX_OpenSocket(const char* path, int) и наверняка адрес пишется в какую то переменную char*, вместо неё завести переменную set<char*>, далее запустить компиляцию libfcgi, далее идти по местам ошибок компиляции и действовать по смыслу, скорее всего заменять проверку на проверку в цикле.
    Сокеты Беркли для tcp позволяют узнать адрес инициатора у accept-сокета в аргументе cliaddr
    для udp — используя функцию recvfrom. По классике в сокетах Беркли recvfrom нет, Но она по-моему на всех платформах реализована, в линуха уж точно есть. И уж наверняка юзается в libfcgi. Они правда могли биндить и при биндинге указать допустимый адрес.
    Короче полюбасе надо бы такую штуку провернуть, заодно покурив мануалы по сокетам.
  • 0
    main.c:68:9: error: implicit declaration of function 'sleep' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
            sleep(2);
            ^
    main.c:17:13: error: unused variable 'i' [-Werror,-Wunused-variable]
        int rc, i;
                ^
    main.c:15:25: error: unused parameter 'a' [-Werror,-Wunused-parameter]
    static void *doit(void *a)
                            ^
    main.c:110:2: error: no newline at end of file [-Werror,-Wnewline-eof]
    }
     ^
    4 errors generated.
    


    А в целом, хороший пример для чайников вроде меня.
  • 0
    И вот я столкнулся с Libfcgi по работе.
    Необходимо написать сервис, который будет раздавать mpeg-ts по http.
    mpeg-ts — бесконечен, это просто онлайн трансляция чего либо.

    Но есть один нюанс: подключений может быть сколько угодно.

    А тут получается, что я не могу в одном потоке начать новый request (typeof FCGX_Request ) не закончив старый, так как завершить соединение я могу только когда клиент отрубился или сервис остановился. (я ведь отдаю части Mpeg-ts по этому request'у)
    Получается, что я должен заранее знать, сколько подключений будет и создать для каждого одновременного подключения свой поток со своим экземпляром FCGX_Request, что совсем не айс.

    Тупиковая ситуация.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.