Как реализовать веб-сервер

задняя часть
Как реализовать веб-сервер

Недавно восстановил колесо, построенное в прошлом году.Vino. Vino стремится реализовать легкий веб-сервер с гарантированной производительностью, сосредоточив внимание только на основных частях веб-сервера. В процессе рефакторинга Vino заимствовал идеи из многих отличных проектов с открытым исходным кодом, таких как Nginx, Mongoose и Webbench. Таким образом, по сравнению с предыдущей версией Vino, текущий Vino не только имеет улучшенную производительность, но и имеет более элегантный и надежный дизайн :D.

В этой статье будут описаны ключевые функции, которыми на данный момент обладает Vino, и подытожен небольшой опыт в процессе разработки.

Один поток + неблокирующий

Vino в целом использует управляемую событиями однопоточную + неблокирующую модель. Принята однопоточная модель, которая позволяет избежать накладных расходов системы на выделение нескольких потоков и обмен данными между потоками и в то же время снижает потребление памяти. Из-за однопоточной модели, чтобы лучше использовать потоки, Vino устанавливает блокирующий ввод-вывод по умолчанию на неблокирующий ввод-вывод, то есть в процессе чтения/записи данных потока, если буфер пуст. / Когда буфер заполнен, поток не блокируется, а немедленно возвращается с установленным значением errno.

Первоначально Вино был вдохновленComputer Systems: A Programmer's PerspectiveКнига о простой реализации сетевого программированияWeb ServerКаждое прибытие запроса веб-сервер будет вилять процесс для решения. Очевидно, что в очень одновременных сценариях эта модель неразумна. Каждый процесс вилкой принесет значительные накладные расходы, а количество процессов в системе ограничено. Между тем, в процессе привлечения многопроцепных планировщиков накладных расходов, который будет пропущен, ЦП потратит много времени, чтобы определить, какие процессы вызовы. Переключение между началом процесса контекста процесса планирования, также необходимо проводить значительные ресурсы.

Легко представить себе использование многопоточной модели для замены многопроцессорной модели.По сравнению с многопроцессорной моделью системные ресурсы, занимаемые многопоточной моделью, будут значительно уменьшены, но существенно не уменьшат накладные расходы, вызванные планированием потоков. Чтобы уменьшить накладные расходы, вызванные планированием потоков, мы можем использовать модель пула потоков, то есть зафиксировать количество потоков, но проблема все еще существует: поскольку в Linux по умолчанию ввод-вывод блокируется (Blocking), если все потоки в пуле потоков блокируются одновременно. Для обрабатываемых запросов не будет потоков для обработки новых входящих запросов. Следовательно, если мы заменим блокирующий ввод-вывод по умолчанию на неблокирующий ввод-вывод, поток не будет блокироваться при чтении и записи данных, и проблема может быть решена.

HTTP Keep-Alive

Vino поддерживает постоянные соединения HTTP, то есть несколько запросов могут повторно использовать одно и то же соединение TCP, тем самым снижая нагрузку на производительность, вызванную установлением/разрывом соединения TCP. Каждый раз, когда приходит запрос, Vino анализирует запрос, чтобы определить, есть ли заголовок Connection: keep-alive в заголовке запроса. Если он существует, то соединение будет поддерживаться после обработки запроса, а буфер данных (используемый для сохранения содержимого запроса, содержимого ответа) и флаг состояния будут сброшены, в противном случае соединение будет закрыто.

Что касается преимуществ HTTP Keep-Alive,RFC 2616Существует более полное резюме, приведенное ниже.

  • By opening and closing fewer TCP connections, CPU time is saved in routers and hosts (clients, servers, proxies, gateways, tunnels, or caches), and memory used for TCP protocol control blocks can be saved in hosts.
  • HTTP requests and responses can be pipelined on a connection. Pipelining allows a client to make multiple requests without waiting for each response, allowing a single TCP connection to be used much more efficiently, with much lower elapsed time.
  • Network congestion is reduced by reducing the number of packets caused by TCP opens, and by allowing TCP sufficient time to determine the congestion state of the network.
  • Latency on subsequent requests is reduced since there is no time spent in TCP's connection opening handshake.
  • HTTP can evolve more gracefully, since errors can be reported without the penalty of closing the TCP connection. Clients using future versions of HTTP might optimistically try a new feature, but if communicating with an older server, retry with old semantics after an error is reported.

Таймер Таймер

Что мне делать, если запрос не сможет отправлять данные после установления соединения, или если другая сторона внезапно теряет мощность? Нам нужно реализовывать таймеры для обработки запросов на внешность. Реализация таймера VINO относится к конструкции Nginx. Nginx использует красно-черное дерево для хранения каждого события Timing. Каждый раз, когда петли событий, наименьшее (раннее) событие постоянно встречается с красного черного дерева. Если это время OUT, он запускает обработку времени ожидания., Чтобы упростить реализацию, в VINO, я реализовал небольшую верхнюю кучу для хранения временных событий. Если обрабатываемое событие включает в себя длинные соединения одновременно, то таймер, соответствующий запросу, будет обновляться после обработки запроса после обработки запроса. То есть время. Таймер, связанный с кодом см.vn_event_timer.hиvn_event_timer.c.

HTTP Parser

Из-за нестабильности сети мы не можем гарантировать, что все запрошенные данные будут прочитаны сразу. Поэтому для каждого запроса мы будем открывать буфер для сохранения прочитанных данных. В то же время нам нужно одновременно анализировать считанные данные, чтобы убедиться, что считанные данные являются разумными данными.Например, если текущие данные в буфере GET /index.html HTT, то в следующий раз данные читается Символ должен быть P, в противном случае он должен немедленно определить, что текущий запрос является ненормальным, и активно закрыть текущее соединение.

Основываясь на приведенном выше анализе, нам необходимо реализовать конечный автомат HTTP (парсер) для поддержания текущего состояния синтаксического анализа Реализация конечного автомата Vino относится к дизайну Nginx и упрощает реализацию Nginx. См. код, связанный с парсером HTTPvn_http_parse.hиvn_http_parse.c.

Memory Pool

Обычно мы используем malloc/calloc/free для выделения/освобождения памяти, но эти функции имеют некоторые недостатки для некоторых долго работающих программ. Частое использование этих функций для выделения и освобождения памяти приведет к фрагментации памяти, и системе будет непросто напрямую высвобождать память. Типичным примером является то, что большой параллелизм часто выделяет и освобождает память, что приводит к фрагментации памяти процесса и не будет немедленно освобождена системой.

Использование пула памяти для выделения памяти может в определенной степени повысить эффективность выделения памяти, и нет необходимости каждый раз вызывать функцию malloc/calloc. В то же время использование пулов памяти упрощает управление памятью. В Vino для каждого запроса Vino выделяет один или несколько пулов памяти (каждый пул памяти образует односвязный список) и освобождает всю память вместе после обработки запроса.

Реализация пула памяти Vino по-прежнему относится к реализации Nginx, и она упрощена.Для связанного кода пула памяти см.vn_palloc.hиvn_palloc.c.

разное

Есть много других соображений и компромиссов, которые необходимо учитывать при разработке Vino. При ответе на запрос, если пользователь запрашивает большой файл, а буфер записи заполнен, как мы можем лучше спроектировать буфер ответа? Как спроектировать базовые структуры данных (такие как строки, связанные списки, небольшие вершины кучи и т. д.) более эффективно? Как более элегантно анализировать аргументы командной строки? Как обрабатывать определенный сигнал? Как более надежно обрабатывать сообщения об ошибках? Когда количество кодов достигает определенного уровня, как быстрее найти ненормальные коды?

Разработка и рефакторинг Vino временно подошли к концу, а исходный код размещен вGitHubначальство. Конечно, Vino оставляет желать лучшего, как и нереализованные возможности.

  • Поддерживает только метод HTTP GET, не поддерживает другие методы HTTP.
  • Обработка динамических запросов в настоящее время не поддерживается.
  • Поддерживаемые функции HTTP/1.1 ограничены.
  • ...

Написав эту статью, я надеюсь, что она будет полезна для начинающих.

Ссылаться на

[1] Vino, https://github.com/tinylcy/vino .

[2] Computer Systems: A Programmer's Perspective, http://csapp.cs.cmu.edu/ .

[3] Advanced Programming in the UNIX Environment (3rd Edition), https://www.amazon.ca/Advanced-Programming-UNIX-Environment-3rd/dp/0321637739 .

[4] Unix Network Programming, Volume 1, https://www.amazon.ca/Unix-Network-Programming-Sockets-Networking/dp/0131411551 .

[5] Nginx, https://github.com/nginx/nginx .

[6] Mongoose, https://github.com/cesanta/mongoose .

[7] Web Bench, http://home.tiscali.cz/~cz210552/webbench.html .

[8] Zaver, https://github.com/zyearn/zaver .

[9] RFC 2616, https://tools.ietf.org/html/rfc2616 .

[10] How to use epoll? A complete example in C, https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/ .