Уроки Коммент.

Создание игр » Featured, Network » boost::asio

boost::asio

boost asioboost::asio (Asynchronous Input/Output System) это, как и следует из названия, одна из библиотек boost, предназначенная для асхинхронного ввода/вывода (asynchronous input/output). Как пишут сами авторы библиотеки “Boost.Asio is a cross-platform C++ library for network and low-level I/O programming that provides developers with a consistent asynchronous model using a modern C++ approach.” – т.е. это кроссплатформенная (как и почти весь boost) библиотека для программирования взаимодействия по сети и низкоуровневых операций ввода/вывода, асинхронных при этом. boost::asio на самом деле используется в просто огромном количестве проектов (опять же, как и вообще многие компоненты boost) – отчасти благодаря её удобству, отчасти благодаря тому, что boost давно уже стал промышленным стандартом не только в среде разработчиков игр, но и вообще разработчиков программного обеспечения.

У меня не было планов по написанию урока про boost::asio в ближайшее время, но я решил написать что-то, так сказать, для души ;-) Думаю, лишним эти знания для начинающих создателей игр и вообще программистов, не будут – всегда полезно понимать как работает тот или иной компонент вашей игры и/или программы и boost::asio окажет нам в этом хорошую помощь

boost::asio основы

boost::asio на самом деле, хоть и называется библиотекой асинхронной, но может выполнять и синхронные, и асинхронные операции. Вся библиотека при этом завязана на объекты класса boost::asio::io_service – именно asio::io_service предоставляет программе связь с нативными объектами ввода/вывода. Соответственно, что бы ваша программа могла работать с boost::asio, нужно создать хотя бы один объект этого класса и уже после этого “аттачить” все другие объекты к нему, например вот так:

boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);

Подключить только что созданный сокет к серверу тоже не представляет из себя проблемы:

boost::system::error_code ec; // здесь будет записана ошибка, если она была
socket.connect(server_endpoint, ec); // server_endpoint задаёт куда подключаемся

После того как мы подключили объекты ввода-вывода (в данном случае socket) к asio::io_service – этот сервис будет осуществлять взаимодействие с операционной системой и получать/отправлять данные.

boost::asio асинхронный

Тот вариант, что мы рассмотрели выше – это синхронное взаимодействие, т.е., в данном случае это значит, что пока происходит подключение – наша программа “зависнет” до того момента, пока подключение не выполнится, либо не произойдёт ошибка. Если же мы хотим что бы работа boost::asio была асинхронной, надо идти немного другим путём, который тоже предусмотрен в библиотеке. Для работы с асинхронными операциями мы должны передать в функцию так называемый completion handler, или, говоря по русски, функцию, которая будет вызвана, когда операция завершится (т.е. либо подключится, либо возникнет ошибка):

void your_completion_handler(const boost::system::error_code& ec)
{
// тут обрабатываем подключение либо ошибку
}
 
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
socket.async_connect(server_endpoint, your_completion_handler);

Все асинхронные функции в boost::asio работают именно по такому принципу. С одной стороны, это довольно удобно для программирования – логика работы каждой из функций отделена от других, с другой стороны это может может показаться Вам немного непривычным. Но, я уверен, Вы освоитесь :-)

boost::asio поддерживает работу, как с TCP/UDP, так и с ICMP. Я сейчас рассказываю о TCP, если Вам так же хочется узнать, как работать с UDP и ICMP – напишите мне, сделаю отдельный урок по ним.

Использование boost::asio

Приведу короткий и простой пример использования boost::asio , это код примера из самого boost, я лишь перевёл для Вас комментарии:

#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
 
int main(int argc, char* argv[])
{
  try
  {
    if (argc != 3) // если аргументы не заданы или заданы не все
    {
      std::cout << "Usage: sync_client <server> <path>\n";
      std::cout << "Example:\n";
      std::cout << "  sync_client www.boost.org /LICENSE_1_0.txt\n";
      return 1;
    }
 
    boost::asio::io_service io_service; // основной класс asio
 
    // Получаем список конечных точек для указанного сервера
    tcp::resolver resolver(io_service);
    tcp::resolver::query query(argv[1], "http");
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;
 
    // Перебираем эти конечные точки и пробуем подключиться
    tcp::socket socket(io_service);
    boost::system::error_code error = boost::asio::error::host_not_found;
    while (error && endpoint_iterator != end)
    {
      socket.close();
      socket.connect(*endpoint_iterator++, error);
    }
    if (error) // подключиться не удалось
      throw boost::system::system_error(error);
 
    // Формируем запрос к веб-серверу. Указываем "Connection: close" что бы
    // сервер закрыл соединение как только отправит нам данные. Это
    // позволит нам узнать что отправка завершенна как только возникнет EOF
    boost::asio::streambuf request;
    std::ostream request_stream(&request);
    request_stream << "GET " << argv[2] << " HTTP/1.0\r\n";
    request_stream << "Host: " << argv[1] << "\r\n";
    request_stream << "Accept: */*\r\n";
    request_stream << "Connection: close\r\n\r\n";
 
    // Отправляем сформированный запрос веб-серверу.
    boost::asio::write(socket, request);
 
    // Читаем ответ сервер. streambuf response примет все данные
    // он сам будет увеличиваться по мере поступления данных от сервера.
    boost::asio::streambuf response;
    boost::asio::read_until(socket, response, "\r\n");
 
    // Проверяем что бы не было ошибок.
    std::istream response_stream(&response);
    std::string http_version;
    response_stream >> http_version;
    unsigned int status_code;
    response_stream >> status_code;
    std::string status_message;
    std::getline(response_stream, status_message);
    if (!response_stream || http_version.substr(0, 5) != "HTTP/") // ошибка
    {
      std::cout << "Invalid response\n";
      return 1;
    }
    if (status_code != 200) // если код ответа не 200, то это тоже ошибка
    {
      std::cout << "Response returned with status code " << status_code << "\n";
      return 1;
    }
 
    // Читаем ответ. Он закончится пустой строкой.
    boost::asio::read_until(socket, response, "\r\n\r\n");
 
    // Парсим заголовки
    std::string header;
    while (std::getline(response_stream, header) && header != "\r")
      std::cout << header << "\n";
    std::cout << "\n";
 
    // Выводим в лог
    if (response.size() > 0)
      std::cout << &response;
 
    // Теперь читаем до конца
    while (boost::asio::read(socket, response, 
            boost::asio::transfer_at_least(1), error))
      std::cout << &response;
 
    if (error != boost::asio::error::eof) // ошибка
      throw boost::system::system_error(error);
  }
  catch (std::exception& e) // возникло исключение
  {
    std::cout << "Exception: " << e.what() << "\n";
  }
 
  return 0;
}

Вот эти 100 строк кода работают как HTTP-клиент и позволяют нам читать страницы с веб-серверов. Мне кажется, что код не сложный, особенно, когда он с комментариями? Если что-то не понятно – пишите, будут объяснять подробнее.

Сетевой движок на базе boost::asio

На базе boost::asio можно достаточно легко и просто сделать сетевой движок. Когда я занимался этим в первый раз, это заняло всего 1 вечер и я получил результат, который меня вполне устроил. Если Вам будет интересно узнать, как сделать собственный сетевой движок на базе boost::asio – напишите мне, я покажу и объясню. Думаю, он мог бы пригодиться Вам, например, для создания сетевых или ммо-игр. В этой статье уже про него рассказывать не буду, т.к. и так много кода, а раздувать статью до гигантских размеров мне не хочется. В принципе, там нет ничего сложного – просто пишется два класса: клиент и сервер. Сервер принимает подключения, клиент имеет лишь одно подключение. Класс клиента имеет небольшой буфер данных, который позволяет отправлять и получать данные пакетами – для большей скорости работы. Работает это всё асинхронно и достаточно быстро – когда я делал тесты, у меня получалось отправлять/принимать до нескольких десятков тысяч пакетов в секунду, что более чем достаточно для абсолютного большинства игр. Кроме того, если даже такой скорости, всё же мало – можно просто запустить параллельно несколько потоков-серверов, каждый из которых будет принимать свои подключения и балансировать нагрузку между ними – этот способ на мощном железе может позволить обрабатывать в разы больше пактов данных, то позволит использовать его даже для средних ММО-игр.

В общем, закругляюсь, всем спасибо за вниманием!

Ещё по этой теме:




Раздел: Featured, Network · Теги: Boost, Создание движка

22 комментариев на "boost::asio"
  1. Antony пишет:

    Каждый раз на вашем сайте узнаешь что-то новое, спасибо! :)
    По мере того, как у вас будет время, если вы не против, рассказывайте пожалуйста, нам о разных технологиях, используемых в gamedeve (да и вообще).
    Например, что сейчас используют для обработки ввода/вывода в играх на PC? DirertInput уже не используют, почему? (думаю, вы об этом расскажите в будущих статьях?)

    1. Antony пишет:

      Я глупость написал: “что сейчас используют для обработки ввода/вывода в играх на PC? DirertInput уже не используют”, хотел сказать не “ввода/вывода”, а обработка пользовательского нажатия клавиш на клавиатуре и мыши.

      1. Вячеслав пишет:

        Antony, спасибо и Вам за комментарии!

        По поводу пользовательского ввода/вывода – расскажу, не проблема.

        Вообще, было бы хорошо, если бы постоянные читатели блога сами озвучили бы темы, которые им интересны – я бы мог написать статьи именно по этим темам, думаю, так было бы удобно “и нам, и вам” ;-)

  2. Antony пишет:

    С удовольствием назову темы, которые очень интересны.

    – обработка пользовательского ввода, как уже отметил)

    – звук в играх, что сейчас используется, а что уже устарело и тратить на это время не нужно (в голове мелькают такие слова, как DirectSound, XACT, XAudio2, OpenAL).

    – Из мира 2d игр хочется узнать, как оптимизировать вывод спрайтов, чтобы игра запросто справлялась с выводом 1000 спрайтов на экран, как делать разные смешивания, альфаблендинг и т.д. Скелетная 2d анимация (как в игре Plants vs. Zombies), ragdoll

    – Из мира 3d море вопросов, анимация, ragdoll,
    камера (вид от 1го и 3го лица), шейдеры и т.д. Одним словом всё :) (постепенно, конечно)

    – Форматы 3d моделей. Почему профессиональные разработчики создают свой формат моделей? Почему их не устраивает тот же .x? В будущем, если можно, говоря о импорте из редактора 3d моделей, не хотелось бы ограничиваться 3d max, редакторов много, было бы лучше, если бы работа велась с “международными” форматами, такими как obj и COLLADA, в которые могут импортировать все редакторы. Насколько мне известно, многие использую Maya, Blender и т.д.

    – редакторы уровней и другие инстументы.

    – пару слов о кроссплатформенной разработки, как это организовывается в общих чертах, как разрабатываются игры для PC и Mac, iPhone/iPad, Android?

    Завалил вопросами вот:)

    »crosslinked«

    1. Вячеслав пишет:

      Ок, без проблем. Эти все темы мне знакомы, буду писать потихоньку )

  3. L-ee-X пишет:

    А мне бы хотелось увидеть как движок собрать в dll и подключать его уже как чисто dll библиотеку )) Буду очень благодарен :)

    1. Вячеслав пишет:

      L-ee-X, хорошо, тоже будет.

  4. L-ee-X пишет:

    Спасибо большое :)

  5. ArchiDevil пишет:

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

    1. Вячеслав пишет:

      ArchiDevil, вполне возможно ) По шейдерам как раз планировал несколько уроков в самое ближайшее время.

  6. mmoshka пишет:

    >Если Вам будет интересно узнать, как сделать собственный сетевой >движок на базе boost::asio – напишите мне, я покажу и объясню.

    Мне очень интересно, с удовольствием бы почитал как граммотно написать сетевую часть игры.

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

    И еще, спасибо за сайт, он шикарен!

  7. Aspire пишет:

    “Кроме того, если даже такой скорости, всё же мало – можно просто запустить параллельно несколько потоков-серверов, каждый из которых будет принимать свои подключения и балансировать нагрузку между ними”
    Заблуждаетесь.
    Азио работает на портах завершения, поэтому увеличение числа потоков не приведет к ускорению обработки данных, скорее, наоборот, т.к. повысит нагрузку на процессор из-за переключений контекста.

    1. Вячеслав пишет:

      Скорее я просто не совсем корректно выразился. Параллелить саму работу с сетью (отправка/поулчение) смысла, имхо, нет вообще никакого. Надо параллелить обработку данных, т.е. примерно по такому принципу:
      – один поток принимает подключения, обрабатывает дисконнекты и поулчение/отправку данных
      – данные эти передаёт другим потокам
      – и они уже обрабатывают команды (проверка корректности, сжатие/декомпрессия, формирование пакетов из потока данных, обработка пакетов и т.д.)

      Потому как, всё же, основная нагрузка обычно это не получение/оправка, а именно обработка данных.

  8. Сергей пишет:

    Вячеслав, хотелось бы прочитать Вашу статью про создание клиент-сервера для ММО-игр. Надеюсь на ее появление! И если можно, то как в основном лучше формировать пакет отправки на сервер, какая информация будет избыточна, можно ли написать свой алгоритм формирования пакета. Возможна ли компрессия данных при этом и как она отразиться на производительности.

    1. Вячеслав пишет:

      Ок, попробую сделать урок по сети. Но только не в ближайшее время – надо сначала закончить давно обещанные уроки по другим темам 8-)

      1. Сергей пишет:

        Хорошо Вячеслав! Хотелось бы Вам помочь в этом деле, но пока у меня не хватает опыта))) Учусь на Ваших примерах!

  9. DimaDDM пишет:

    Если вам не трудно свяжитесь со мной!!!!Хочу побольше узнать об этой библиотеке….
    Не знаю как связаться.с вами :-(
    Очень интересна эта тема!!!

    1. Вячеслав пишет:

      Cправа в меню большими буквами “Форма для связи” ;-)

      1. Леонид пишет:

        Форма для связи-не работает((( Очень хотелось бы урок с icmp. Особенно как происходит взаимодействие… Ведь сокеты могут взаимодействовать над IP, то есть с TCP, UDP, а мне важно узнать как же с ICMP))))

        1. Вячеслав пишет:

          Прошу прощения, небольшие проблемы на хостинге. Надеюсь, что они разрешатся в первых числах июня.

          По ICMP – пока не могу ничего обещать, на очереди уже больше десятка уроков, которые надо сделать. После этого можно будет подумать и о уроках по ICMP.

          1. ArchiDevil пишет:

            Какие уроки планируются, и когда они будут (ну так, оптимистично)?

  10. Илья пишет:

    Именно с этого сайта было приятно получить вводную информацию. Цветовая гамма располагающая :) и подача информации хорошая. Спасибо, Вячеслав.

Оставить комментарий

*

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>