Создание игр » Featured, Network » boost::asio
boost::asio
boost::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, Создание движка
Каждый раз на вашем сайте узнаешь что-то новое, спасибо!
По мере того, как у вас будет время, если вы не против, рассказывайте пожалуйста, нам о разных технологиях, используемых в gamedeve (да и вообще).
Например, что сейчас используют для обработки ввода/вывода в играх на PC? DirertInput уже не используют, почему? (думаю, вы об этом расскажите в будущих статьях?)
Я глупость написал: “что сейчас используют для обработки ввода/вывода в играх на PC? DirertInput уже не используют”, хотел сказать не “ввода/вывода”, а обработка пользовательского нажатия клавиш на клавиатуре и мыши.
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«
Ок, без проблем. Эти все темы мне знакомы, буду писать потихоньку )
А мне бы хотелось увидеть как движок собрать в dll и подключать его уже как чисто dll библиотеку )) Буду очень благодарен
L-ee-X, хорошо, тоже будет.
Спасибо большое
В первую очередь надо тему графики освещать, я считаю. Может быть шейдеры?
ArchiDevil, вполне возможно ) По шейдерам как раз планировал несколько уроков в самое ближайшее время.
>Если Вам будет интересно узнать, как сделать собственный сетевой >движок на базе boost::asio – напишите мне, я покажу и объясню.
Мне очень интересно, с удовольствием бы почитал как граммотно написать сетевую часть игры.
Еще интересно, как правильно сделать класс игрового объекта, GameObject его обычно называют. Как связать в одном классе графику, физику и логику? Как игровые объекты взаимодействуют между собой? Как обеспечить разнообразие игровых объектов и при этом не запутаться в собственном же коде (в портянках свитчей и if-ов, и т.п.)? Слышал про компонентную систему сушностей. Стоит ли ее применять или нужно делать как-нибудь по-другому?
И еще, спасибо за сайт, он шикарен!
“Кроме того, если даже такой скорости, всё же мало – можно просто запустить параллельно несколько потоков-серверов, каждый из которых будет принимать свои подключения и балансировать нагрузку между ними”
Заблуждаетесь.
Азио работает на портах завершения, поэтому увеличение числа потоков не приведет к ускорению обработки данных, скорее, наоборот, т.к. повысит нагрузку на процессор из-за переключений контекста.
Скорее я просто не совсем корректно выразился. Параллелить саму работу с сетью (отправка/поулчение) смысла, имхо, нет вообще никакого. Надо параллелить обработку данных, т.е. примерно по такому принципу:
– один поток принимает подключения, обрабатывает дисконнекты и поулчение/отправку данных
– данные эти передаёт другим потокам
– и они уже обрабатывают команды (проверка корректности, сжатие/декомпрессия, формирование пакетов из потока данных, обработка пакетов и т.д.)
Потому как, всё же, основная нагрузка обычно это не получение/оправка, а именно обработка данных.
Вячеслав, хотелось бы прочитать Вашу статью про создание клиент-сервера для ММО-игр. Надеюсь на ее появление! И если можно, то как в основном лучше формировать пакет отправки на сервер, какая информация будет избыточна, можно ли написать свой алгоритм формирования пакета. Возможна ли компрессия данных при этом и как она отразиться на производительности.
Ок, попробую сделать урок по сети. Но только не в ближайшее время – надо сначала закончить давно обещанные уроки по другим темам
Хорошо Вячеслав! Хотелось бы Вам помочь в этом деле, но пока у меня не хватает опыта))) Учусь на Ваших примерах!
Если вам не трудно свяжитесь со мной!!!!Хочу побольше узнать об этой библиотеке….
Не знаю как связаться.с вами
Очень интересна эта тема!!!
Cправа в меню большими буквами “Форма для связи”
Форма для связи-не работает((( Очень хотелось бы урок с icmp. Особенно как происходит взаимодействие… Ведь сокеты могут взаимодействовать над IP, то есть с TCP, UDP, а мне важно узнать как же с ICMP))))
Прошу прощения, небольшие проблемы на хостинге. Надеюсь, что они разрешатся в первых числах июня.
По ICMP – пока не могу ничего обещать, на очереди уже больше десятка уроков, которые надо сделать. После этого можно будет подумать и о уроках по ICMP.
Какие уроки планируются, и когда они будут (ну так, оптимистично)?
Именно с этого сайта было приятно получить вводную информацию. Цветовая гамма располагающая и подача информации хорошая. Спасибо, Вячеслав.