Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:game-net [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== game-net ======
 +<​sub>​{{game-net.odt|Original file}}</​sub>​
 +
 +====== сетевые игры — идеи и решения ======
 +
 +крис касперски ака мыщъх, no-email
 +
 +**в наш век коммуникационных технологий всякая уважающая игра должна поддерживать мульплейер,​ то есть режим с несколькими игроками,​ связанными локальной сетью, модемом или даже интернетом. на первый взгляд это просто,​ но попытка практической реализации открывает целый ворох проблем,​ зачастую требующих совсем необычных решений**
 +
 +===== введение или когда игровые миры соприкасаются =====
 +
 +Прежде чем погружаться в тонкости межсетевого взаимодействия возьмем мыщъха за хвост и вспомним старые игры, высаживающие на одном компьютере сразу несколько игроков. В пошаговых стратегиях игрой обычно управляли по очереди. В играх реального времени такой подход был уже неприемлем и приходилось делить одну клавиатуру на двоих (хорошо,​ если был джойстик!).
 +
 +Идея — чтобы организовать сетевую игру достаточно загнать весь ввод/​вывод в магистральный кабель,​ реализовав удаленный монитор и клавиатуру (джойстик/​мышь). Получится точно так же, как и раньше,​ только намного круче. С клавиатурой все просто — тут базару нет — а вот осуществить передачу видеоизображения в реальном времени удастся едва ли. Даже если применить компрессию,​ модемного канала все равно не хватит и понадобиться локальная сеть или по меньшей мере DSL. Не слишком ли завышенные требования?​ На самом деле, все проблемы решаемы,​ стоит только покурить!
 +
 +===== игра для двух игроков =====
 +
 +Рассмотрим простейший случай:​ игру для двоих игроков. Один компьютер будет "​ведомым",​ другой "​ведущим"​. Ведущий компьютер обсчитывает игровое пространство,​ управляет движением монстров (если они есть), обрабатывает столкновения и т. д. Для синхронизации игровых миров все события игрового пространства (перемещение объектов,​ взрывы мин, полеты торпед) передаются ведомому компьютеру,​ который отображает их на экране и отмечает на своей карте, при этом неявно предполагается,​ что обе карты (ведомого и ведущего компьютеров) полностью идентичны. Если же в игровом пространстве присутствуют случайные элементы (например,​ аптечки,​ расставленные в разных местах) они должны быть переданы ведомому компьютеру. Это достаточно сделать всего один раз — перед началом игры. При желании можно даже передать всю карту целиком (нужно учитывать,​ что ведущий компьютер может использовать дополнительные или "​самопальные"​ миссии,​ которых нет у ведомого). Ведомый компьютер,​ в свою очередь управляет локальным игроком,​ отслеживая нажатия клавиш,​ и отправляет эту информацию ведущему.
 +
 +{{game-net_Image_0.png?​553}}
 +
 +Рисунок 1 схема взаимодействия ведомого и ведущего компьютера в мире из двух игроков
 +
 +==== из локальной сети в интернет ====
 +
 +На первый взгляд все просто и никаких греблей здесь нет. В локальной сети это, может быть, и так, но вот передача данных через Интернет сопряжена с не периодичными задержками,​ и если не предпринять дополнительных мер, монстры будут двигаться как припадочные. Как же быть? (Тянуть свое оптоволокно не предлагать). А что если **передавать не сами перемещения,​ а их предполагаемый сценарий**?​ Обычно движение монстров подчиняется набору шаблонов и компьютеру достаточно передать что-то типа: "​двигайся от меня и до обеда/​упора",​ "​атакуй по сценарию D" или "​уклоняйся по сценарию A". Количество передаваемой информации резко сокращаются и для обеспечения синхронизации достаточно лишь периодически (скажем раз в секунду) передавать "​квитки",​ сигнализирующие о пересечении объектом некоторой клетки игрового поля. Как легко показать,​ такой протокол передачи устойчив даже к длительным задержкам,​ что не маловажно при работе на сильно загруженных каналах.
 +
 +С игроками все значительно сложнее. Допустим,​ два горячих мудреца (в смысле думераца) стоят супротив друг друга и пускают по торпеде. Если информация о перемещении одного из игроков хоть чуть-чуть запоздает,​ очень может случиться так, что с точки зрения ведомого компьютера торпеда пройдет мимо, а ведущий увидит как противника разорвало в клочья. Первое,​ что приходит на ум — **осуществлять перемещение только после подтверждения**. Выглядит это так: игрок нажимает на стрелку. Компьютер А (не важно ведущий или ведомый) отправляет уведомление компьютеру B (стадия I). Компьютер B обновляет свое игровое пространство и посылает подтверждение компьютеру А (стадия II). Компьютер А принимает его и перемещает игрока (стадия III). Все хорошо?​ Мыщъх так не думает. Игрок давит на клавишу,​ но фигурка на экране остается неподвижной (задержка передачи данных по сети). Что делает игрок? Правильно! Давит на клавишу еще и еще! К тому же, если на стадии III случится задержка,​ в игровых мирах вновь произойдет рассинхронизация и дело закончится лесом. Следовательно,​ возникает необходимость в четвертой стадии,​ обеспечивающий дополнительный уровень подтверждений,​ но… как же тогда все будет тормозить!
 +
 +==== "​быстрая"​ и "​медленная"​ синхронизация ====
 +
 +Мыщъх рекомендует полуэвристический алгоритм:​ когда игроки находятся в разных концах игрового поля они перемещаются без подтверждений,​ но как только их игровые миры соприкоснуться (один игрок увидит другого или в их поле зрения попадет общий монстр),​ автоматически активируется режим "​обмена подтверждениями"​.
 +
 +Зная направление и скорость движения игрока/​монстра/​торпеды удаленный компьютер может с высокой степенью достоверности рассчитать произойдет ли столкновение на данном временном участке или нет. Даже самый прыткий игрок не может менять направление своего движения несколько раз в секунду,​ поэтому достаточно **подтверждать лишь изменение направления**! Конечно,​ при возникновении задержки в сети, фигурка на экране отреагирует на действие игрока не сразу, но во всяком случае она не будет тупо стоять,​ а продолжит движение. То есть, **субъективно поведение компьютера станет более реалистичным**.
 +
 +==== борьба с жульничеством ====
 +
 +А вот другая серьезная проблема — читерство. Ведущий компьютер может как угодно мухлевать,​ ведь код и данные игрового процесса находятся в полном ведении игрока. Что ему стоит, слегка повозившись с отладчиком,​ приобрести божественное здоровье или нескончаемые патроны?​ Некоторые программисты просто отмахиваются от этой проблемы,​ делая вид, что ее не существует и говорят:​ "вы либо доверяете ведущему компьютеру,​ либо нет"​. На самом деле, решение лежит на поверхности! **Пусть ведомый и ведущий компьютеры периодически меняются ролями**. Это достаточно просто реализуется на программном уровне и надежно защищает от взлома. Конечно,​ хакер может исправить свой экземпляр программы так, чтобы пули не кончались,​ но работать это будет только тогда, когда его компьютер будет ведущим! Согласен,​ даже такой "​половинчатый хак"​ дает огромное преимущество в игре, однако,​ оба игрока находятся в равных условиях и хакерствовать можно каждый из них! Нету никакого "​выделенного"​ компьютера,​ владелец которого приравнивается к богу!
 +
 +===== три и более игроков =====
 +
 +Играть вдвоем довольно скучно и в какой-то момент возникает желание взять третьего. А где трое, там и пятеро. Какие проблемы ждут нас на этом пути? Ведущий компьютер может обслуживать множество ведомых игроков,​ количество которых (в теории) ограничивается пропускной способностью канала и мощностью процессора. Найти мощный процессор — не проблема,​ а с учетом того, что мы передаем не сами перемещения,​ а изменения направления с лихвой хватит и хлипкого модемного канала. Камень преткновения в другом:​ задумаемся,​ что произойдет,​ если ведущий компьютер внезапно отвалиться?​ (Разорвется соединения или его владелец просто устанет и отправится спать). В ситуации с двумя игроками никакой проблемы не возникает. Если один из игроков "​исчезает",​ другой автоматически переходит в режим одиночной игры, но вот с тремя игроками такая стратегия уже не срабатывает. Получается,​ что один отдувается за всех! А оно ему надо?
 +
 +Кто-то наверняка предложит установить выделенный сервер,​ только для трех-пяти игроков это будет слишком расточительным решением. Хорошо,​ поступим так: **пусть все игроки устанавливают соединение не только с ведущим компьютером,​ но так же и друг с другом,​ по очереди становясь то ведомыми,​ то ведущими.** Тогда, если ведущий компьютер исчезнет,​ он просто-напросто будет выброшен из очереди и ничего катастрофического не произойдет!
 +
 +Как вариант,​ **можно вообще отказаться от концепции "​ведомого"​ и "​ведущего"​.****Каждый компьютер управляет движением "​своего"​ игрока и "​своих"​ монстров,​ рассылая информацию о перемещении всем остальным.** Такая схема существенно упрощает проектирование и реализацию игры, снимая множество проблем,​ правда не все…
 +
 +{{game-net_Image_1.png?​553}}
 +
 +Рисунок 2 схема взаимодействия компьютеров в игре из трех игроков
 +
 +===== самоорганизующееся системы с большим количеством игроков =====
 +
 +Описанная выше схема страдает двумя серьезными недостатками. При большом количестве игроков объем трафика увеличивается настолько,​ что ни в какие каналы он уже не вмещается и все начинает дико тормозить,​ особенно в тот момент когда игроки сойдутся в смертельном поединке. Правило "​семеро одного не ждут"​ тут не срабатывает и динамика игры определяется самым медленным компьютером в сети — все остальные терпеливо ждут пока придет подтверждение. Приходится усложнять протокол,​ и **запрашивать подтверждения только у того, "​против кого мы дружим"​.** Допустим,​ игрок А пускает в игрока Б торпеду,​ а игрок С за этим внимательно наблюдает — интересно же. Так вот, чтобы не допустить рассинхонизации компьютеры А и Б вынуждены обмениваться подтверждениями,​ а компьютер С может и отдохнуть.
 +
 +Проблема большого количества соединений снимается путем создания своеобразного "​поезда"​. Вместо того, чтобы рассылать данные всем узам, компьютер А посылает их компьютеру B, тот посылает их (вместе со своими перемещениями) компьютеру С и т. д. Естественно,​ чтобы восстановить цепочку в случае "​падения"​ одного из узлов, компьютер А должен знать адреса компьютера С и D, чтобы при необходимости установить с ними соединение. На самом деле, структура сети не обязательно должна быть линейной (это самый худший вариант). Вот типичный случай из жизни: компьютеры А и C находятся в локальной сети, а компьютер B – где-то далеко в Интернете. За каким чертом мы будем гонять трафик через B, когда логичнее соединить компьютеры так: B  A  C. И это действительно можно сделать!
 +
 +==== динамическое балансирование нагрузки ====
 +
 +Выбираем самый быстрый компьютер с мощным процессором (выбираем,​ естественно,​ автоматически — по скорости передачи данных и времени отклика на ping), подключаем к нему чуть-чуть менее быстрые компьютеры,​ которые будет нести на своих плечах еще менее быстрее и т. д. Тогда **тормозные компьютеры окажутся задвинутыми в самый зад, а весь трафик ляжет на плечи узлов, висящих на быстрых каналах.** Эта схема не является заданной раз и навсегда. Она ​ может (и должна!) динамически обновляться. Главное,​ выбрать протокол обмена так, чтобы равномерно распределить трафик на все узлы.
 +
 +Чтобы игроки (а их количество в этой схеме измеряется десятками) не толпились на крошечном пяточке,​ необходимо создать достаточно большое игровое пространство,​ которое никакой отдельно взятый Pentium не сможет обсчитать. Значит,​ **нам потребуется распределенная система,​ нарезающая "​объекты"​ игрового мира на куски и раскидывающая их по наименее загруженным машинам.**
 +
 +Использованная ранее схема "​каждый компьютер обрабатывает своих монстров"​ уже не катит. И вот почему:​ как уже говорилось,​ когда некоторый объект (монстр или игрок) попадет в поле зрения "​чужого"​ объекта (монстра или другого игрока) для синхронизации игровых миров требуется задействовать механизм подтверждений,​ а это тормоза — вполне терпимые в случае трех-пяти игроков,​ но неприменимые в мире, где их десятки! Допустим,​ мы модернизируем протокол обмена и решим: пусть каждый компьютер обрабатывает только тех монстров,​ которые находятся в его игровом пространстве (то есть если монстр уходит из "​своего"​ игрового пространства,​ его обработка передается другому компьютеру). Все будет замечательно работать до тех пор, пока все монстры не соберутся в пространстве одного игрока и тогда его компьютер конкретно ляжет, будучи не в состоянии обрабатывать эту армию в реальном времени. Так что **без распределенной системы с автоматическим балансированием нагрузки — никуда**. К сожалению,​ подобные системы очень сложны реализации и отладке. В общем случае они не оправдывают себя и становится выгоднее установить выделенный сервер.
 +
 +{{game-net_Image_2.png?​525}}
 +
 +Рисунок 3 система обмена в игре со множеством игроков
 +
 +===== выделенный сервер =====
 +
 +Вот мы добрались до выделенного сервера. Фактически,​ это тот же самый ведущий компьютер только без заморочек. В отличии от обыкновенного компьютера,​ сервер внезапно не отваливается,​ чем сразу же снимает целый комплекс проблем. Задача синхронизации в подобной централизованной структуре решается сама собой, поскольку клиент отображает на экране только то, что сказал ему сервер,​ а уж игровой мир сервера всегда поддерживается в синхронном состоянии с сами собой.
 +
 +Это были достоинства. А теперь поговорим о недостатках. Поддержка постоянно работающего сервера — удовольствие не из дешевых. Обычного хостиига за 8$ в год с Perl, PHP и MySQL тут будет явно недостаточно. Нам потребуется прямой доступ к машине с возможностью выполнять двоичные файлы, написанные под XP (или что у нас там) и очень мощный процессор,​ способный поддерживать огромный игровой мир в подвижном состоянии. Такое может позволить себе только крупная компания! Но это ладно. Это вопросы финансов.
 +
 +Самое неприятное,​ что подобная "​принудительная самосинхронизация"​ означает,​ что клиенты лишаются какой бы то ни было самостоятельности и вынуждены передавать все свои передвижения на сервер с ожиданием подтверждения. Даже если игрок решил заняться онанизмом в тихом дальнем углу, он все равно будет двигаться рывками и слегка притормаживать!
 +
 +К тому же игроки (живые люди, а не фигурки) находятся в полной зависимости от сервера,​ компании-разработчика и своего провайдера. Уже нельзя собраться в куче и поиграть по локалке. Надо обязательно выходить в интернет или… устанавливать свой собственный сервер,​ только как его установишь,​ если в открытом доступе его нет?
 +
 +Очень остро встает и проблема читерства. Чем больше игроков,​ тем выше вероятность обмана. И хотя код, управляющий игровым пространством,​ находится на сервере,​ в который непосредственно залезть никак нельзя,​ хакеры могут повесить бота, то есть написать скрипт,​ управляющий движением игрока и с нечеловеческой меткостью и быстротой мочащий все, что попадается ему на глаза. Увы! Защититься от этого никак нельзя! Единственное,​ что остается — распознавать обманщиков по слишком большому количеству трупов,​ оставленных в единицу времени и ставим им бан, однако,​ всегда найдется игрок, который поднимет вселенский визг, возмущаясь за что его так?! Ведь он играл по всем правилам,​ ну а меткость и реакция,​ как известно,​ не порок.
 +
 +В принципе,​ обработку игровых миров можно поручить и клиентам,​ а сервер будет только коммутировать потоки данных и распределять нагрузку по узлам. Короче говоря,​ мы приходим к той же самой самоорганизующийся системе,​ но только с центральным сервером. Это снимает требование к вычислительной мощности сервера и отменяет принудительную синхронизацию,​ однако,​ вызывает много путаницы с "​отрубавшимися"​ клиентами и серверу приходится постоянно перепроверять — был ли обработан данный блок игрового пространства или нет. При огромном количестве клиентов это нереально. Поэтому,​ на практике зачастую используются гибридные схемы. Весь игровой мир храниться на сервере,​ но максимум перемещений обрабатываются локально. То есть, схватка двух игроков неизбежно происходит через сервер,​ а разборки игрока с монстром,​ может обработать и сам клиент,​ доложив серверу конечный результат:​ кто кого порешил. Естественно,​ в этом случае со стороны клиента возможно наглое читерство.
 +
 +Короче говоря,​ от централизованного сервера не так уж и много пользы. Не стоит думать,​ что сервер освободит нас от всех проблем. Проблем будет не намного меньше,​ чем в самоорганизующийся распределенной системе и это при всех присущих серверу недостатках!
 +
 +{{game-net_Image_3.png?​552}}
 +
 +Рисунок 4 игровой мир с выделенным сервером
 +
 +===== >>>​ врезка TCP, UDP или IP? =====
 +
 +Какой протокол выбрать?​ Очень многие разработчики выбирают TCP, потому что он самостоятельно заботиться о поддержании надежного соединения,​ гарантирует доставку данных и т. д. Все это так, мыщъх ничего не оспаривает,​ но все-таки выбор TCP будет фатальной ошибкой и колоссальным техническим просчетом. Все дело в том, TCP ориентирован на постоянное поддержание соединение и непрерывную передачу данных. С ним тесно связано мерзкое понятие "​медленного страта"​ (подробности о котором можно прочитать в любой книжке по TCP/IP). Крохотные пакеты отправлять крайне нерентабельно. По ряду причин они доходят с большими задержками,​ а для синхронизации игровых миров это более чем актуально!
 +
 +Протокол UDP доставляет пакеты намного быстрее,​ однако,​ не гарантирует успешности доставки. Можно (и нужно!) конечно организовать собственную службу подтверждений,​ только это ничего не меняет. Послали мы в пакет, а в ответ вместо подтверждения — тишина. Повторить передачу или подождать еще? А если ждать то сколько?​ А, может, стоит продублировать посылку по TCP? Увы, чудес не бывает и сам TCP действует точно так же, как и мы — подтверждает приемку или повторяет пересылку по тайм-аутуту. По своему личному опыту работы в плохих сетях, мыщъх может сказать,​ что "​шторм"​ UDP пакетов все-таки предпочтительнее,​ однако,​ он осуществим только если пропускная способность канала с лихвой покрывает наши потребности в трафике иначе случится сплошной затор и затык. Так же, можно взвести бит срочности,​ однако,​ судя по всему большинство провайдеров его нагло игнорирует и он не сильно влияет на скорость обмена данных.
 +
 +Тем не менее, TCP по ряду параметров все-таки удобнее UDP, поэтому можно использовать гибридную схему, передавая по TCP несрочные данные,​ а по UDP — критическую информацию о перемещениях объектов (типа торпеды,​ пущенной мыщъху под хвост). Как мы увидим ниже, в некоторых случаях протокол UDP недоступен и тогда приходится работать только по TCP. Это не слишком усложняет программу — основная информация все равно передается по TCP, и если UDP отсутствует,​ это всего лишь замедлит синхронизацию и вызовет некоторые тормоза,​ но игровой мир не рухнет и в программисту не придется ничего переписывать.
 +
 +Еще меньшим оверхидом обладает протокол IP, который есть смысл использовать в игровых мирах, рассчитанных на большое количество игроков,​ в противном случае выигрыш не стоит приложенных усилий,​ тем более, что через IP удается работать далеко не всегда.
 +
 +Вообще говоря,​ существует огромное количество самых разнообразных протоколов,​ но мы не будет рассматривать их здесь, поскольку TCP/UDP для наших задач вполне достаточно.
 +
 +===== >>>​ врезка HTTP рулез воревер =====
 +
 +При разработке сетевой игры следует учитывать,​ что прямой доступ в интернет имеется далеко не у всех пользователь и брандмауэр и proxy-сервер вовсе не пустые слова. Зачем нам отсекать потенциальных клиентов?​ Давайте научим нашу программу работать по HTTP и поддерживать Proxy — сколько людей нам скажут спасибо! При работе с выделенным игровым сервером никаких проблем не возникает (исключая "​медленную синхронизацию",​ в связи со значительной латентностью TCP, да еще и "​проксированного"​ но тут уже ничего не поделаешь!) — просто поддерживаем Proxy-протокол и все. Ради прикола можно предусмотреть связь по ICMP, а что? Очень даже неплохой способ связи!
 +
 +Ситуация осложняется,​ если один из клиентов сидит за брандмауэром или proxy, допускающем только исходящие соединения. Поскольку,​ на этом незавидном месте может оказаться любой, программа должна предусматривать два режима работы:​ пассивный,​ в котором она слушает порт ожидания соединения,​ и активный,​ при котором она сама ломиться на удаленный порт.
 +
 +А если оба игрока находятся за брандмауэром?​ (естественно,​ каждый за своим). Тогда пробить тоннель прямого соединения уже не получится (ну разве только через ICMP или NAT, но это уже тема отдельного разговора) и придется подключаться к кому еще. К тому кто находится в диком Интернете безо всяких там проки и брандмауэров. Устанавливать централизованный сервер совершенно необязательно! Достаточно найти еще одного игрока (третьим будешь,​ да?) и соединиться через него! Чем популярнее будет ваша игра — тем проще это будет сделать,​ так что дерзайте!
 +
 +===== заключение =====
 +
 +Разработка сетевых игр — это захватывающее занятие,​ а сам игровой процесс отрывает хвост с головой,​ ведь это не с тупым компьютером сражаться который только и умеет, что посылать монстров по прямой,​ тут сталкивается живой интеллект реальных игроков от которых можно ожидать любых хитростей и тактических маневров! Естественно,​ в процессе написания игру необходимо тестировать,​ то есть попросту говоря играть. По сети. Ага! Какое там программирование!
 +
 +Вот тут некоторые спорят — обязательно ли закладывать сетевые возможности на самой ранней стадии разработке программы или их можно добавить позднее?​ Однозначного ответа никто не дает. С одной стороны,​ в правильно спроектированной программе мультиплейер можно добавить когда угодно с минимальными переделками кода, однако,​ где вы видели правильно спроектированные программы?​ Поэтому,​ чем раньше вы возьметесь за поддержку сети — тем лучше.
 +
 +