#36
|
|||
|
|||
FAQ appendix 1: как писать сервера
RU.UNIX.PROG FAQ poster написал(а) к All в Nov 16 11:14:58 по местному времени:
From: "RU.UNIX.PROG FAQ poster" <netch@segfault.kiev.ua> RU.UNIX.PROG FAQ - приложение 1 $Id: FAQ.a1,v 1.20 2010/11/16 09:25:25 netch Exp $ >Q: Как писать сервера? A: (Lev Walkin, Dmitri Lenev, множественные дополнения, особенно от Igor Sysoev, Igor Khasilev, Sergue E. Leontiev) Возможны следующие варианты: 1. Сервер может использовать несколько процессов, каждый из которых обслуживает собственного клиента: Плюсы: + Простейшая и наиболее быстрая реализация среди всех описываемых вариантов - через [x]inetd (см. ниже) + Простая модель данных + Масштабируется с ростом числа процессоров. + Ошибки в одном процессе не приводят к отказу в обслуживании остальных клиентов. + Если пользователи идентифицируются системной базой пользователей, этот вариант единственный даёт возможность адекватной, полной и необратимой имперсонализации в пользователя. Минусы: - Процесс - это достаточно тяжелый объект OS, поэтому метод неприменим при большом количестве одновременно работающих клиентов (больше нескольких десятков или сотен). - Несмотря на масштабируемость, модель тяжела и в среднем гораздо менее эффективна, чем FSM (пункта 2 данного описания) или смешанные модели. - Неприменим или крайне дорог для серверов с одним серверным портом и транспортом без установления соединений (например, DNS-сервер - работа в основном по UDP). Примеры реализации: - Большинство MTA (sendmail, postfix, exim, qmail) - Традиционные POP3 сервера (qpopper, cucipop, popa3d) - И другие традиционные unix'овые сервисы (telnetd, rlogind, nnrpd,...) У всех перечисленных выше время жизни процесса - время обслуживания клиента. - apache 1., apache 2. в многопроцессной сборке У apache - процессы форкаются заранее, процесс может жить неограниченное время. Наиболее простой (и поэтому быстрый) вариант такой реализации - на основе стандартного демона Интернет (inetd, xinetd, in.inetd в зависимости от типа Вашей ОС). В простейшем случае Вам надо будет реализовать простую программу реализующую обслуживание одного клиента (соединения TCP), которые считывает запросы клиента из stdin/STDIN_FILENO и выдаёт ответы в stdout/STDOUT_FILENO. Все остальное (приём запроса, разграничение доступа, ограничение нагрузки, протоколирование) обеспечит inetd на основе конфигурационного(ых) файлов /etc/inetd.conf (/etc/xinetd.conf и /etc/xinetd.d/*). Например, реализация "тривиальных" TCP/IP серверов возможна на основе стандартных команд POSIX/SVR4 (эти сервера обычно встроены в inetd в отладочных целях). # RFC 862: Echo Protocol # echo stream tcp nowait nobody /usr/bin/cat cat 2. Сервер может использовать однопроцессную FSM (Finite State Machine) архитектуру (то есть конечный автомат), используя те или иные методы получения данных о состоянии сокетов (select, poll, etc). Плюсы: + Очень эффективный метод с точки зрения CPU. Минусы: - Не масштабируется с ростом числа процессоров. - Серверные FSM, как правило, достаточно сложны и требуют тщательного подхода при проектировании. - в случае если обработка данных пришедших по соединению требует долгой (в частности блокирующей) операции, то на время выполнения этой операции невозможно обрабатывать другие соединения. Соответственно возникают проблемы с задержкой обработки запросов... Проблема в том что: а) Например в случае ввода вывода на диск, неблокирующий ввод-вывод по select/poll не всегда поддерживается... б) даже если мы пользуемся другим механизмом не обладающим данным недостатком, например kqueue, или aio, то нам все равно может быть не доступна напрямую работа с файлом. Ну например есть библиотека для работы с СУБД и нет возможности залезть в ее внутренности чтобы получить файловые дескрипторы соответствующие соединениям с сервером СУБД. в) даже если мы имеем полный контроль над вводом выводом то может возникать потребность в долгих вычислениях (то есть затык в занятости процессора)... Ну можно конечно вручную пытаться квантовать работу но это не всегда удобно... В принципе все три проблемы можно решить используя для выполнения длительных или блокирующих операций вспомогательные (slave) процессы или нити делая их (операции) тем самым не блокирующими. В принципе про данный подход можно посмотреть здесь: http://www.cs.princeton.edu/~vivek/f...>usenix</b>99/ (Dmitri Lenev) + По собственному опыту могу сказать что имея скажем проработанную библиотеку классов писать сервера на FSM достаточно легко... Примеры реализации: - innd - squid (с ufs хранилищем) - named (с поправкой на протокол UDP для большинства передач) Пример реализации со вспомогательными процессами для блокирующих операций: - squid с diskd Пример реализации со вспомогательными нитями (тредами) для блокирующих операций: - squid с aufs 3. Сервер может использовать небольшое число процессов, каждый из которых имплементирует FSM (a la пункт 2). Плюсы: + Если уже имеется система по типу #2 (один процесс с FSM), то перевод ее на рельсы системы #3 как правило, достаточно простой. Это дает возможность сделать систему масштабируемой за счет очень небольших усилий. Минусы: - Все равно придется делать полную FSM. 4. Сервер - процесс, использующий отдельную нить (thread) на каждого клиента (сокет). Плюсы: + Небольшая сложность разработки, похожа на #1 (отдельные процессы). Требуется проработка механизмов защиты общих данных. + В зависимости от OS, модель может быть и масштабируемой, и эффективной (Solaris, НP-UX). Минусы: - В зависимости от OS, модель может быть как неэффективной (Linux, так как нить "весит" почти столько же, сколько и процесс), так и не масштабируемой с ростом числа процессоров (old BSD libc_r с user-space threads; green threads). В основном это уже в прошлом. - (Igor Khasilev) Если планируется обслуживать одновременно большое число подключенных клиентов (от тысячи и выше в зависимости от ОС) эта модель может оказаться нерабочей по причинам: расход адресного пространства на стек для каждой нити, большая нагрузка на планировщик и ограничение на общее число нитей в системе (особенно в случае 1:1 модели). Иногда может спасти экстенсивный путь - переход на 64-битные платформы. - Существенно затрудняется отладка. Примеры: - Oops! 1.* - apache 2.* в MT-варианте сборки - CommuniGatePro (исходный код недоступен, но в Usenet можно найти много деталей устройства с авторским описанием) 5. Сервер - процесс, использующий небольшое количество нитей, каждая из которых обслуживает некоторое количество сокетов одновременно. Плюсы: + На архитектурах с kernel-threads (Linux, Solaris, FreeBSD 5+, NetBSD 2+...) обладает масштабируемостью и очень эффективна. Минусы: - Требуется разработка FSM по типу #2, плюс разработка разграничения доступа к общим данным (#4). - Не приносит масштабируемости на некоторых имплементациях потоков (libc_r старых BSD; green threads), поэтому на них несколько менее эффективна, чем #2. В основном это уже в прошлом, поэтому учёт таких имплементаций для нового ПО обычно не имеет смысла. 6. Несколько процессов, каждый из которых поддерживает нескольких клиентов путем выделения по потоку на клиента или методом #5. Плюсы: + Система защищена от неустранимых сбоев при обработке одного клиента, так как остаются работать остальные процессы. + Система масштабируется с ростом числа процессоров. Минусы: - Очевидно, складывается сложность всех перечисленных выше методов. - Не предоставляет преимуществ перед #3 на одном процессоре. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Некоторые методы получения состояния (активности) сокета (файлового дескриптора): Плюсы select(): + Широкая переносимость (требуется по POSIX). + Очень эффективен при относительно небольшом числе одновременно активных сокетов (передача в ядро и назад по три бита на сокет). Минусы select(): - На многих платформах максимальное ограничение на 1024 (иногда другое) файловых дескрипторах не обходится без перекомпилирования приложения или даже ядра системы (для FreeBSD не нужно перекомпилировать ядро, только приложение). - При большом количестве неактивных клиентов передача в ядро и назад пустого состояния сокета представляет собой сплошные накладные расходы. Плюсы poll(): + Не содержит имманентного ограничения на максимальное количество обслуживаемых сокетов. + Широкая переносимость (требуется по POSIX). Минусы poll(): - Менее эффективен, чем select() (так как передает в ядро и назад по восемь-шестнадцать байт на сокет). (Реализация в Linux использует особенно тормозной алгоритм обхода данных в poll().) Плюсы /dev/poll (Solaris): + Не имеет ограничения на максимальное количество обслуживаемых сокетов. + Из-за модели передачи событий (events) вместо модели передачи состояний, очень эффективен при обслуживании большого количества клиентов, только часть из которых очень активна (типичная ситуация для web- и другого вида серверов). Состояния неактивных сокетов не вызывают расхода ресурсов. Минусы /dev/poll: - Не переносим. - Неадекватно реагирует на закрытие дескриптора, занесённого в список контроля и не вынесенного оттуда перед закрытием. Дескриптор остаётся в списке (числясь своим номером). Поэтому перед закрытием надо обязательно выносить дескриптор из списка. Ещё про /dev/poll см. http://soldc.sun.com/articles/polling_efficient.html Плюсы kqueue/kevent (FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0+, MacOS X, DragonFlyBSD): + Не имеет ограничения на максимальное количество обслуживаемых сокетов. + Имеет "вкусные фичи", которые позволяют использовать его более эффективным образом не только для сокетов, но и для объектов другого типа (файлов, процессов). + Из-за модели передачи событий вместо модели передачи состояний, очень эффективен при обслуживании большого количества клиентов, только часть из которых очень активна (типичная ситуация для web- и другого вида серверов). Состояния неактивных сокетов не вызывают расхода ресурсов. + (Igor Sysoev) kqueue/kevent эффективнее, чем /dev/poll. Минусы: - Не портабелен за пределы BSD мира. - Линус Торвальдс его необоснованно не любит. (См. http://www.uwsg.iu.edu/hypermail/lin...0.3/0013.html; впрочем, epoll повторяет тот же "silly" триплет) Ссылки по kevent (не забывайте про manpage): http://people.freebsd.org/~jlemon/papers/kqueue.pdf http://people.freebsd.org/~jlemon/kq...des/index.html Плюсы realtime signals (sigtimedwait, http://www.kegel.com/c10k.html#nb.sigio, Linux 2.4+): + Не имеет ограничения на максимальное количество обслуживаемых дескрипторов. (Однако, количество сигналов ограничено, и если дескрипторы группировать по сигналам, внутри группы придется опрашивать все дескрипторы.) Минусы realtime signals: - Есть в слишком малом количестве систем. - Очередь сигналов может переполняться. (Linux в этом случае даёт SIGIO, что означает необходимость итерирования всех дескрипторов. Но это лучше, чем замалчивание переполнения очереди.) - Хуже kqueue/kevent - только один сигнал обрабатывается за раз; kevent() может принять и передать несколько событий за один вызов. Плюсы epoll (Linux 2.5.44+): + Не имеет ограничения на максимальное количество обслуживаемых сокетов. + Из-за модели передачи событий вместо модели передачи состояний, очень эффективен при обслуживании большого количества клиентов, только часть из которых очень активна (типичная ситуация для web- и другого вида серверов). Состояния неактивных сокетов не вызывают расхода ресурсов. Вообще, epoll похож на kevent. 4-я версия научилась level triggering в дополнение к edge triggering (что уже умела kqueue/kevent). Минусы epoll: - Не переносим - только Linux. - Слабее по возможностям чем kqueue/kevent (нет наблюдения за процессами, файлами, таймерами, завершениями AIO запросов; только одно событие на входе и выходе системного вызова). (Начиная с 2.6.19, его возможности расширяются за счёт eventfd, timerfd, signalfd и так далее.) Еще по epoll см. http://www.uwsg.iu.edu/hypermail/lin...10.3/1164.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Поднятые здесь вопросы также обсуждаются в документе по адресу: http://www.kegel.com/c10k.html, местами более развёрнуто и с массой интересных ссылок по отдельным подходам и вопросам. Еще стоит посмотреть в сторону D.C. Schmidt'овкого ACE и JAWS, если не в сторону первого так в сторону последнего как теоретически - экспериментального исследования... --- ifmail v.2.15dev5.4 |