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

Отличная цитата и хорошее введение в этот пост в блоге! Мы хотим дать вам представление о внутреннем сетевом интерфейсе (INI) встроенного веб-сервера Mongoose.

Фон

Первоначально Mongoose использовал API BSD-сокетов, множество функций сокетов и неблокирующий режим. Это означало, что он был переносим только на платформы, которые поддерживали этот API. Это несколько сработало для тех платформ, которые поддерживают сокеты. Но мы обнаружили, что большинство платформ поддерживают по-своему. Авторы Hardware SDK «забывают» о функции select. Также им нравится комбинировать функции select и accept. Это усложняло использование нашего оригинального API-интерфейса BSD-сокетов. У нас было два варианта исправить это:

  1. Не переносите на платформы, которые просто не поддерживают полный API сокетов.
  2. Реализуйте любые недостающие функции для каждой платформы

Героические ходы

Первый вариант не совсем героический способ ведения дел, верно? ? Мы любим сложности.

Первым микрочипом, на который был портирован Mongoose, был Arduino (Mega). И это было реализовано вторым способом. Мы разработали слой BSD-сокетов поверх библиотеки сокетов Arduino. Он работал нормально с некоторыми сбоями.

Следующей платформой стала ESP8266. Здесь нам нужно было решить, чего мы действительно хотим достичь. Мы давно начали работать с ESP8266. Не было RTOS SDK с LWIP (и сокетами BSD). Кроме того, мы хотели портировать Mongoose на гораздо большее количество встраиваемых платформ и начинали понимать, что у большинства из них будут проблемы с сокетами BSD.

Пришло время разделить встроенный веб-сервер Mongoose на уровень протокола и уровень сети. Да, Дэвид Уилер, пора добавить еще один уровень косвенности.

Планирование нашего внутреннего сетевого интерфейса

Наш внутренний сетевой интерфейс (INI) должен был

  1. быть ориентированным на соединения (не сокеты!) — Mongoose ориентирован на соединения, поэтому использование интерфейса, ориентированного на соединения, должно быть более простым.
  2. поддерживают как TCP, так и UDP (остальные поддерживаемые протоколы основаны на этих двух).
  3. быть асинхронным, основанным на обратном вызове.
  4. будь простым!

Как вы уже догадались, самым проблемным моментом было: быть простым. Наша загадка:

С одной стороны, легко разработать сложный интерфейс, но кто тогда будет им пользоваться?

С другой стороны, у Mongoose так много функций, включая функции, связанные с сетью, что было важно найти баланс между функциональностью и простотой.

В результате внутренний сетевой интерфейс Mongoose имеет около 20 функций.

Обзор INI

В INI API есть несколько групп функций:

  1. Управление соединениями
  2. Отправка и получение данных
  3. Клиентские функции
  4. Серверные функции
  5. Небольшой набор полезных функций.

Большинство функций (кроме функций управления соединениями) имеют две версии: одну для TCP и одну для UDP.

И последнее, но не менее важное: есть набор обратных вызовов. Наш INI имеет интерфейс на основе обратного вызова. Короче говоря, это означает, что такие функции, как connect, возвращаются промежуточно и вызывают обратный вызов. Затем соединение устанавливается (или возникает ошибка).

Схема взаимодействия уровней довольно традиционна: протоколы прикладного уровня используют API транспортных протоколов, а транспортные протоколы используют реализации INI для связи с конкретным API платформы (или устройства).

На данный момент у нас есть 3 реализации INI:

  1. BSD-сокеты
  2. LWIP
  3. SimpleLink (Texas Instruments CC3100/C3200)

Конкретная реализация INIвыбирается во время компиляции путем определения макросов CS_PLATFORM (эти макросы можно указать вручную, с помощью параметра компилятора или набора #ifdef #elseв коде Mongoose попытается сделать это за вас).

Как это работает

Конкретная реализация INIдолжна реализовать набор функций для создания соединений, отправки данных, их получения и т. д. Ядро вызывает эти функции для создания соединений, отправки данных и т. д. Если INIхочет доставить какую-либо информацию в ядро, вызывает обратный вызов, реализованный в основном в Mongoose Coreа не в INI, т.е. INI-реализатор не должен обеспечивать реализацию таких функций (в большинстве случаи, когда они называются, заканчиваются на _cb). Однако INI также должен реализовать один обратный вызов (mg_if_recved,см. ниже).

Вот небольшая схема последовательности выполнения:

Функции

Теперь давайте сделаем краткий обзор INIфункций (полное описание можно найти в документации Mongoose здесь):

Функции управления соединениями:

Большинство действий, связанных с управлением соединениями, выполняет сам Mongoose, поэтому INI имеет функции для выполнения действий, специфичных для платформы. Например, «Socket INI» создает сокеты в своей реализации mg_if_create_conn.

  • mg_if_create_conn
  • mg_if_destroy_conn
  • mg_close_conn

Функции клиента и сервера:

  • mg_if_connect_tcp
  • mg_if_connect_udp

После установления соединения следует вызвать функцию mg_if_connect_cb.

  • mg_if_listen_tcp
  • mg_if_listen_udp
  • mg_if_accept_new_conn

Затем INI принимает новое соединение и должен вызывать mg_if_accept_tcp_cb.

Функции отправки и получения:

  • mg_if_tcp_send, mg_if_udp_send и соответствующий обратный вызов mg_if_sent_cb

При получении INI должен вызывать обратные вызовы mg_if_recv_tcp_cb и mg_if_recv_udp_cb. Если ядро ​​подтверждает потребление, оно вызывает mg_if_recved.

Миссия выполнена?

Достигли ли мы того, к чему стремились? Вот напоминание о том, что мы хотели:

Наши внутренние сетевые интерфейсы (INI) должны были быть

  1. быть ориентированным на соединения (не сокеты!) — Mongoose ориентирован на соединения, поэтому использование интерфейса, ориентированного на соединения, должно быть более простым.
  2. поддерживают как TCP, так и UDP (остальные поддерживаемые протоколы основаны на этих двух).
  3. быть асинхронным, основанным на обратном вызове.
  4. будь простым!

Мы думаем, что поняли. Теперь есть способ портировать Mongoose на разные платформы. Это может быть не всегда очень просто, но это возможно, и нам не нужно создавать ярлыки и тому подобное, чтобы это произошло.

Заглядывая в будущее, мы планируем портировать Mongoose на еще больше встраиваемых платформ. Таким образом, мы получим 5–6 различных реализаций INI. Оставайтесь с нами!

Первоначально опубликовано на blog.cesanta.com.