Четверг, 02 Мая 2024, 14:46

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Форум игроделов » Программирование » Delphi/Pascal/Object Pascal » Создание сервера на Delphi
Создание сервера на Delphi
NanotentacleДата: Среда, 21 Ноября 2012, 10:13 | Сообщение # 1
был не раз
Сейчас нет на сайте
Здравствуйте, уважаемые форумчане.

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

Немного истории: мысль о том, чтобы делать игру, будоражит меня уже порядка трех лет. За это время периодически подчитывал различные статьи, темы о том, как правильно заниматься их разработкой. Первый проект, который я начал, закономерно окончился ничем. Весь игровой код был в одном модуле, лишь изредка разделенный на процедуры для исполнения рутинных действий. Я с трудом понимал, что такое потоки и как с ними работать. Конечно, ничего толкового выйти не могло. После того, как было выполненно несколько сторонних проектов, я сел за более глубокое изучение Делфи и ООП как такового, начал лучше разбираться в классах и смог делать более красивый код. Знаний все равно было недостаточно. Однако ощущение, что до настоящего мастерства и понимания всего мне предстоит большой путь, не отпускает меня и сейчас. Каждый новый день приносит что-то новое, дает новые знания, которые требуют переделывания всего, что уже было сделано. Но всему свое время.

Возможно, этот текст поможет кому-нибудь быстрее пройти тот путь, и не отказаться от своих мечтаний. Итак, приступим.

Работать я начал с Delphi 7, как наиболее знакомой и обеспечивающей, на данном этапе, достаточные возможности. Первым делом я определился, что хочу получить в результате: это была обычная флеш-игра (про клиент я пока ничего говорить не буду, так как не по теме, затрону только разработку серверной части), игроки могут присоединяться в игровые комнаты и вести там сражения. Максимум - 8 игроков в одной комнате. Соответственно, надо было создать классы для игрока, и для комнаты. Чтобы избежать задержек, обусловленных динамическими массивами, в основном модуле создавался массив для игроков на 1000 человек, и массив комнат на 100 комнат. Да, вполне вероятно, что это будет занимать больше оперативной памяти, но для меня на данном этапе это не сильно критично (кстати, если кто-то в курсе, подскажите, насколько это станет критичным при больших нагрузках?). Обслуживать все подключения, по первоначальному плану, должен был единственный ServerSocket, прослушивающий один порт. Само приложение хотел сделать однопоточным. Увы, в таком виде это было нежизнеспособно.

Первая моя проблема, с которой я столкнулся - незнание такой простой вещи, как pointer. Как я понял, это величайшая вещь в плане оптимизации кода и удобства разработки. Ранее, до того как я начал пользоваться поинтерами, чтобы понять, от кого же пришло то или иное сообщение на сервер, мне приходилось перерывать весь массив игроков на соответствие хэндла (тот, что я приписывал игроку при подключении и тот, что был приписан к сокету), и после определения игрока уже вел с ним беседу. Я воздаю хвалу высшим силам, что я все это поправил на первом же этапе. Особенно, учитывая, что уже в самом сокете предусмотрен поинтер, которому можно присвоить адрес соответствующего элемента массива игроков. Код становился элегантнее буквально на глазах.

Написав свой "Хэллоу ворлд" на флеше, я смог его заставить общаться с сервером. Тут столкнулся с интересной ситуацией: флеш при соединении запрашивает файл политик безопасности. Сначала я этого не знал, и не мог понять, почему же происходит дисконнект. Но, введя запрос в поисковик, быстро справился с проблемой. На первом этапе, пока я работал с одним клиентом лишь для проверки "общения" клиента и сервера, все было прекрасно. Однако, при подключении большего количества клиентов начались проблемы. Тут следует немного отклониться от темы и рассказать про "доверие" к клиенту.

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

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

А вот "бутылочное горлышко" дало о себе знать. Во-первых, я попытался работать напрямую с массивом игроков и комнат из потока, а во-вторых, я попытался отправлять сообщения клиентам прямо из потока, используя наш один-единственный сервер на одном единственном порту. Даже при 6ти игроках весь сервер начинало колбасить и передвижения игроков стали больше походить на пошаговую стратегию. Временным (потому как трудно предположить, что будет при большой загрузке), и на данный момент действующим, решением стало:

1) для каждого потока создать свой локальный экземпляр комнаты и игроков, играющих в ней, куда и передавать все необходимые данные из основного потока. После просчета очередной итерации полученные данные переписываются обратно в основной поток. Сейчас я обдумываю то, как же можно сделать так, чтобы избежать переписываний туда-сюда, а всю информацию хранить строго в потоке, и выгружать ее обратно только при выходе игрока из игры или из боя. Без этого, думаю, при определенном количестве комнат и игроков "пошаговость" игры неминуема. Но это теория. В общем-то, тут не вижу ничего сложного, надо только время и немного мозговой деятельности. Если вам будет интересно, потом я смогу рассказать, как и что я предприму для исправления ситуации;

2) разделить процедуры. Т.е. загрузка-выгрузка данных из основного потока происходит в отдельной процедуре и вызывается отдельно методом synchronize(), чтобы не "застолбить" массивы основного потока на время выполнения расчетов. Также отдельно происходит и сам расчет, использующий исключительно локальные переменные потока, чтобы не создавать тех самых "бутылочных горлышек".

3) для каждой комнаты создал свой сервер. Вернее, при создании комнаты сервер создается и запускается, при удалении - удаляется (что, впрочем, логично). Перестали появляться очереди в отправке сообщений клиентам. Номер порта легко определяется, и у клиента не возникает проблем с подключением.

4) Слегка оптимизировал запросы. Ранее клиент каждый обработанный фрейм (а при 50 фпс это 50 сообщений в секунду) отправлял информацию о направлении перемещения, координатах. Теперь же отправка идет исключительно при изменении направления движения. Сервер считает перемещение, и при изменениях выдает координаты игрока, где он должен находиться. В общем-то, удалось "синхронизировать" клиент и сервер по скоростям так, что отправлять клиенту его положение в каждый фрейм нет необходимости.

На самом деле вопрос оптимизации запросов мне еще только предстоит затронуть в своем изучении. Ведь, представьте, переменная типа Boolean занимает всего 1 бит. Для пересылания по сети 1го знака я использую 1 байт информации - а это в 8 раз больше, чем мне требуется! Я понимаю, что методы архивирования придуманы уже давно, и их надо изучать, но, согласитесь, до таких вещей лучше дойти своей головой и понимать, зачем оно надо. Так что еще одной задачей для себя я ставлю максимальное сжатие траффика.

На данный момент сервер без труда держит 8 игроков, загружая процессор не более 1% при заполненной комнате (учитывая, что процессор тут отнюдь не самый мощный). Мне пока очень трудно предсказать, как же игра поведет себя не в лабораторных условиях, но радует, что пока все выглядит оптимистично и мне интересно работать над проектом, узнавать новые вещи и решать сложившиеся трудности.

Те, кто смог прочитать все до конца, и разбирается в сути вопроса, я бы очень хотел выслушать мнение, с какими подводными камнями я могу столкнуться в будущем, и вообще любые советы. Я понимаю, что не предоставил ни строчки кода, ни каких-либо серьезных технических выкладок, но при необходимости я могу дополнять текст и отвечать на вопросы.


Сообщение отредактировал Nanotentacle - Среда, 21 Ноября 2012, 10:24
TimKruzДата: Пятница, 23 Ноября 2012, 19:27 | Сообщение # 2
старожил
Сейчас нет на сайте
Quote (Nanotentacle)
Чтобы избежать задержек, обусловленных динамическими массивами

Лучше использовать динамический список, потому что удаление элементов из середины будет быстрее. Правда, доступ к элементу только перебором всех имеющихся элементов (иногда прямого доступа к ячейке вообще не нужно, все они так и так по кругу обрабатываются), но зато быстро меняется длина, занимается памяти ровно столько, сколько нужно, и не нужен целый блок памяти, как для массивов...
Quote (Nanotentacle)
Да, вполне вероятно, что это будет занимать больше оперативной памяти, но для меня на данном этапе это не сильно критично (кстати, если кто-то в курсе, подскажите, насколько это станет критичным при больших нагрузках?).

Зависит от того, сколько памяти занимается другими данными. Можно использовать простой массив (достоинство - высокая скорость доступа к нужной ячейке), если не нужно менять его размер. Если у сервера мало памяти, то использование динамических структур поможет только при малом числе игроков.
Quote (Nanotentacle)
Тут я видел два решения проблемы: либо дать клиенту волю, но ставить жесткие проверки на сервере, либо проводить параллельные вычисления на стороне сервера, который априори не доверяет информации с клиента.

Можно всё обрабатывать на сервере, а клиент только отправляет запросы на действие и получает результат для вывода на экран. Это повышает нагрузку на сервер, зато разгружает игроков и сводит возможность обмануть сервер практически к нулю.
Можно обрабатывать не каждого игрока отдельно, а игровой мир целиком, так нагрузка на сервер должна уменьшится, если правильно подойти к этому... Правда, в случае игровых комнат по 8 человек этот приём неприменим, всё равно дробление получается.
Quote (Nanotentacle)
4) Слегка оптимизировал запросы.

Вот запросы нужно сильно оптимизировать. Чтобы не слать огромные структуры с кучей мусора каждую миллисекунду. Только самое важное, только в свёрнутом виде.
Просто интересно узнать формат запросов. В идеале это должно представлять собой строго определённый код, где каждый байт имеет своё имя и назначение. А то я где-то тут видел, как кто-то шлёт огромные XML-файлы с информацией, которую можно сжать в пару десятков байт. wacko
Quote (Nanotentacle)
Ведь, представьте, переменная типа Boolean занимает всего 1 бит. Для пересылания по сети 1го знака я использую 1 байт информации - а это в 8 раз больше, чем мне требуется!

Переменная типа Boolean занимает 1 байт (потому что меньше нельзя, адресуются только байты), но значение имеет только один бит.
Используй битовый сдвиг. Если, например, используется 8 булевых переменных, их можно свернуть в один байт битовым сдвигом, а потом снова развернуть (для удобства использования). Но если используешь 7 булевых переменных, по-любому останется 1 "лишний" бит, который никуда не используешь, если все остальные данные занимают целые байты. Однако есть ли смысл задумываться о каком-то лишнем бите, когда скорости передачи данных гораздо быстрее?
Quote (Nanotentacle)
Я понимаю, что методы архивирования придуманы уже давно, и их надо изучать, но, согласитесь, до таких вещей лучше дойти своей головой и понимать, зачем оно надо. Так что еще одной задачей для себя я ставлю максимальное сжатие траффика.

Архивировать что-либо имеет смысл только тогда, когда это что-то имеет большой размер. Так что нужно оценить скорости сжатия/разжатия и коэффициент сжатия (сильнее сжатие - меньше скорость, больше скорость - слабее сжатие), потому что может получиться выигрыш в какую-то там жалкую миллисекунду или даже наоборот, убыток...
Quote (Nanotentacle)
На данный момент сервер без труда держит 8 игроков, загружая процессор не более 1% при заполненной комнате (учитывая, что процессор тут отнюдь не самый мощный). Мне пока очень трудно предсказать, как же игра поведет себя не в лабораторных условиях, но радует, что пока все выглядит оптимистично и мне интересно работать над проектом, узнавать новые вещи и решать сложившиеся трудности.

Можно чисто теоретически подсчитать затраты ресурсов, но лучше напиши программу-бота, которая будет симулировать среднюю или максимальную активность игрока плюс теоретический разброс времени ответа (пинг) клиента и проблем соединения (потеря пакетов, лишние/фальшивые пакеты, резкое падение скорости или выброс игрока из игры), и подключай её к серверу (желательно, чтобы она симулировала сразу несколько игроков). Так можно быстро на практике оценить мощность сервера, оценить возможные проблемы... По-любому такой тест придётся делать перед тестом с настоящими игроками.
Quote (Nanotentacle)
Так что следующим этапом изучения для меня стала многопоточность.

Не понимаю смысла многопоточности в таких ситуациях. sad Всё равно реально процессор параллельно обрабатывает столько инструкций, сколько у него ядер, т.е. для двухъядерного процессора реальный прирост скорости может быть только от двух параллельных потоков вместо одного, который выполняет функции этих двух, но подряд. А потоки ещё нужно синхронизировать с ядром программы и вообще контролировать, так что проще без них... wacko


NanotentacleДата: Понедельник, 26 Ноября 2012, 09:49 | Сообщение # 3
был не раз
Сейчас нет на сайте
Большое спасибо за ответ, был приятно удивлен.

Quote (TimKruz)
Лучше использовать динамический список, потому что удаление элементов из середины будет быстрее. Правда, доступ к элементу только перебором всех имеющихся элементов (иногда прямого доступа к ячейке вообще не нужно, все они так и так по кругу обрабатываются), но зато быстро меняется длина, занимается памяти ровно столько, сколько нужно, и не нужен целый блок памяти, как для массивов...


По динамическим массивам да, я соглашусь. Скорее всего, при переписывании сервера перейду именно на этот вариант. Единственно, что вызывает у меня сомнения - это сложность удаления элемента из центра массива. Насколько я знаю, для этого придется обнулить сам элемент, и индекс всех вышестоящих элементов уменьшить на единицу, после чего "отрезать" из конца массива лишний элемент. По-моему, это достаточно громоздкое решение. Мб есть что-то более элегантное, про которое я не в курсе?

Quote (TimKruz)
Вот запросы нужно сильно оптимизировать. Чтобы не слать огромные структуры с кучей мусора каждую миллисекунду. Только самое важное, только в свёрнутом виде. Просто интересно узнать формат запросов. В идеале это должно представлять собой строго определённый код, где каждый байт имеет своё имя и назначение. А то я где-то тут видел, как кто-то шлёт огромные XML-файлы с информацией, которую можно сжать в пару десятков байт.


Средний запрос у меня стандартизирован. Первые три байта - код команды или запроса, затем "тело" запроса с данными, со строгим размером данных. Например, при перемещении передается только четыре вектора направления в булевых переменных. Для передачи использую один байт, который передается в виде числа в шестнадцатеричной системе. Если же есть изменения в направлении (например, игрок нажал или отпустил кнопку), то передается еще две координаты положения, по 6 байт в каждой координате. Учитывая, что целая часть будет не больше 4х симоволов, точность получается приемлимая. Правда, как там ужать данные пока не сообразил и, честно говоря, не сильно и обдумывал, оставив на потом. Оптимизация - это для меня вопрос серьезный, но который я пока решил оставить до полного написания сервера.

Quote (TimKruz)
Переменная типа Boolean занимает 1 байт (потому что меньше нельзя, адресуются только байты), но значение имеет только один бит. Используй битовый сдвиг. Если, например, используется 8 булевых переменных, их можно свернуть в один байт битовым сдвигом, а потом снова развернуть (для удобства использования). Но если используешь 7 булевых переменных, по-любому останется 1 "лишний" бит, который никуда не используешь, если все остальные данные занимают целые байты. Однако есть ли смысл задумываться о каком-то лишнем бите, когда скорости передачи данных гораздо быстрее?


Да, именно к такому способу я и пришел, проанализировав, как могу уменьшить объем передаваемых данных. Булевые переменные могут оптимизироваться просто великолепно, но вот что делать с float - ума не приложу. Фактически, чтобы закодировать цифру нам избыточно даже 4 бита, но при передаче данных я использую 8. Возможно, стоит перейти также в шестнадцатеричную систему, и это даст свой выигрыш.

Quote (TimKruz)
Можно чисто теоретически подсчитать затраты ресурсов, но лучше напиши программу-бота, которая будет симулировать среднюю или максимальную активность игрока плюс теоретический разброс времени ответа (пинг) клиента и проблем соединения (потеря пакетов, лишние/фальшивые пакеты, резкое падение скорости или выброс игрока из игры), и подключай её к серверу (желательно, чтобы она симулировала сразу несколько игроков). Так можно быстро на практике оценить мощность сервера, оценить возможные проблемы... По-любому такой тест придётся делать перед тестом с настоящими игроками.


На данный момент как раз пишу такую программу-бота. Единственно чего опасаюсь - что мои иллюзии о том, что код написан хорошо, являются лишь иллюзиями :).

Quote (TimKruz)
Не понимаю смысла многопоточности в таких ситуациях. Всё равно реально процессор параллельно обрабатывает столько инструкций, сколько у него ядер, т.е. для двухъядерного процессора реальный прирост скорости может быть только от двух параллельных потоков вместо одного, который выполняет функции этих двух, но подряд. А потоки ещё нужно синхронизировать с ядром программы и вообще контролировать, так что проще без них...


А вот тут вопрос спорный. Если я не ошибаюсь, то вопрос идет даже не в том, что они должны выполняться параллельно, а в процессорном времени. Все дальше написанное - это то, как я понимаю многопоточность. Поток, сам по себе, представляет собой программу в программе. Поэтому процессорное время выделенное на программу в общем, при применении потоков, увеличивается. Плюс, в моем исполнении один поток отрабатывает свой цикл просчетов, и после этого уходить в sleep на 30 мс, тем самым освобождая процессорное время соседнему потоку. С синхронизацией все проще. Я сейчас воплощаю систему, где основной поток и остальные потоки не используют совместных переменных, чтобы полностью избежать подвисаний и проблем. Хотя вплотную я этим вопросом не занимался, и могу ошибаться. В любом случае, попробовать на практике это стоит :).
TimKruzДата: Среда, 28 Ноября 2012, 23:28 | Сообщение # 4
старожил
Сейчас нет на сайте
Quote (Nanotentacle)
По динамическим массивам да, я соглашусь. Скорее всего, при переписывании сервера перейду именно на этот вариант. Единственно, что вызывает у меня сомнения - это сложность удаления элемента из центра массива. Насколько я знаю, для этого придется обнулить сам элемент, и индекс всех вышестоящих элементов уменьшить на единицу, после чего "отрезать" из конца массива лишний элемент. По-моему, это достаточно громоздкое решение. Мб есть что-то более элегантное, про которое я не в курсе?

Лучше использовать динамические списки, а не динамические массивы. В динамических списках структура одной ячейки следующая: блок информации + указатель на следующую (и/или предыдущую) ячейку. Удаляется любая ячейка освобождением её памяти и переопределением указателей соседнего элемента, это во много раз быстрее, чем в динамичных массивах, особенно больших. Однако придётся для доступа к какой-либо ячейке пробегать по всему списку, хотя в случаях, когда по-любому нужен прямой перебор элементов (например, механизм сервера по кругу обрабатывает всех игроков из кольцевого списка), это не имеет значения.
Что-то типа динамических списков можно найти в стандартных классах Delphi (TList), но можно самому сделать обычный динамичный список, это несложно.
Quote (Nanotentacle)
Первые три байта - код команды или запроса

Ммм, получается возможным более 16 миллионов различных команд или запросов... Я думаю, для небольшого игрового сервера хватило бы 1 байта (256 команд или запросов)...
Quote (Nanotentacle)
но вот что делать с float - ума не приложу

Перевести в целочисленный формат. Вряд ли у тебя в игре десятитысячная доля координаты x на что-то влияет. Т.е. можно умножить Position.X на, например, 100 (если сотые доли координаты имеют значение) и перевести в shortint или какой удобнее - лишние циферки отпадут.
Quote (Nanotentacle)
Фактически, чтобы закодировать цифру нам избыточно даже 4 бита, но при передаче данных я использую 8.

Можно в тех местах, где гарантировано только 4-битные данные - сжимать всё в два раза побитовым сдвигом на 4 и сложением...
Quote (Nanotentacle)
Поэтому процессорное время выделенное на программу в общем, при применении потоков, увеличивается.

Можно забить 100% процессорного времени без использования потоков. biggrin
Потоки, скорее, используются для выполнения независимых фоновых задач внутри одной программы (например, в браузере отдельный поток обслуживает загрузку файлов, тогда как основной поток прорисовывает страницу)...
Хотя, наверное, правда, какой-то прирост производительности сервера, выделяющего поток для каждой игровой комнаты, всё-таки будет... wacko Тут ещё от самих механизмов сервера зависит...
Quote (Nanotentacle)
На данный момент как раз пишу такую программу-бота. Единственно чего опасаюсь - что мои иллюзии о том, что код написан хорошо, являются лишь иллюзиями :).

Ну и как успехи?




Сообщение отредактировал TimKruz - Среда, 28 Ноября 2012, 23:30
NanotentacleДата: Четверг, 29 Ноября 2012, 15:53 | Сообщение # 5
был не раз
Сейчас нет на сайте
Успехи пока что меня вдохновляют :). Бота почти дописал, скоро начну тесты. Дополнительно продолжил искать возможные подводные камни, о которых сразу не подумал. Про динамические списки - это великолепно, я подумаю, как их можно применить в моем случае. На самом деле, о таком простом и элегантном способе я и не думал. Дополнительно мне посоветовали почитать про хэш-списки, тоже сейчас вникаю и разбираюсь, как они могут мне помочь.

Поскольку эту тему я создал не только для того, чтобы получать советы (которые, надо сказать, во многом меня сильно радуют и позволяют взглянуть на свой же проект со стороны), но и для того, чтобы если кто-то решит пойти по моим стопам, то он мог ознакомиться с материалом и лучше понимать, что ему предстоить делать.

Итак, хотелось бы отписаться о программе-боте и небольшом изменении сетевого движка. Программа-бот будет представлять собой динамически создаваемые ClientSocket, их кол-во зависит от моих потребностей. Каждый бот-клиент присоединится к серверу, и будет совершать действия, похожие на действия игрока - давать команды на перемещения, на стрельбу, в идеале - умирать и воскрешаться. Зная структуру запросов к серверу, ничего сложного здесь нет. Хватит всего лишь задать обработку пары-тройки случайных событий.

А вот с сетевым движком все интереснее. Пока что я провожу испытания на своей же машине, т.е. клиент подключается к адресу 127.0.0.1. В данной ситуации пинг является если не нулевым, то минимальным. Дальше я буду рассказывать достаточно очевидные вещи, но они могут оказаться для кого-нибудь полезными. Ранее я брал допущение (замечу сразу, это неправильное допущение и оно всегда приведет к неприятностям, если им пользоваться в реальном сервере), что пинг в игре отсуствует. Т.е. сервер получает от игрока информацию о том, что игрок сменил направление движения, внес информацию об этом в свои переменные и отправил игроку сообщение, где он должен находиться на момент этой смены. Поскольку задержки были равны нулю, это приводило к тому, что обсчет движения, столкновения с объектами на карте я мог производить на сервере, не задействуя клиент.

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

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

А вот что делать с выстрелом я пока не придумал. На данный момент обсчет выстрела, столкновения, взрыва и повреждений ведет сервер, сообщая об этих события клиенту. Но система, как я и писал выше, нежизнеспособна. Видимо, придется доверить клиенту обсчет и этих вещей. Хотя обсчет повреждений я намерен оставить серверу, даже если информация будет приходить с задержкой. Я очень надеюсь, что мне удастся сделать так, что столкновения снаряда и игрока и на сервере, и на клиенте будут максимально приближены друг к другу и тогда ситуации, когда клиент показывает игроку, что он попал и повредил соперника, а сервер это не подтверждает, будут крайне редки.
Возможно, есть более элегантные способы?

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

Update:
После полунедельного затишья продолжил работу. Сейчас, как и хотел, переписал сервер и привел его в более удобный вид. Начал оптимизировать с самого начала - например, определился с необходимыми типами данных, чтобы не было лишнего выделения памяти. Как результат - сервер начал есть в два раза меньше оперативной памяти, а код выглядит более строго и "архитектурно". Последовал совету TimKruz и уменьшил код запроса до одного символа. На данный момент хватает за глаза.

Также попробовал перейти к динамическим массивам, но все же отказался от этой идеи. Слишком много нюансов, которые я пока не могу предсказать. Например, насколько быстро будет создание-удаление игроков. Теперь у меня есть константы, которые задают размер массивов, так что изменить из емкость я могу буквально одним росчерком пера.

Также поработал с клиентом. Теперь он не привязан напрямую к серверу, а лишь сверяется с ним. Уже все выглядит достаточно плавным и приятным, однако в этом вопросе есть куда стремиться. Очень здорово удалось перенести модель столкновения с объектами из сервера в клиент, в результате совпадение получилось тютелька в тютельку.

Единственный вопрос, в котором я очень хотел бы помощи - это система логгирования. Пока я приглядываюсь к MemoryStream или FileStream, но вплотную этим вопросом не занимался. Т.е. хочу вести лог на каждого игрока, на первом этапе - полный, в дальнейшем оптимизирую на хранение неопознанных пакетов или явно читерских. Но насколько это себя оправдает в плане скорости и результата с удовольствием бы от вас послушал.


Сообщение отредактировал Nanotentacle - Пятница, 14 Декабря 2012, 14:21
Форум игроделов » Программирование » Delphi/Pascal/Object Pascal » Создание сервера на Delphi
  • Страница 1 из 1
  • 1
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг