Чи існує схема написання покрокового сервера, що спілкується з n клієнтами через сокети?


14

Я працюю на загальному ігровому сервері, який керує іграми для довільної кількості клієнтів, розіграних в сокеті TCP, що грають у гру. У мене "дизайн" зламаний разом із клейкою стрічкою, яка працює, але здається і крихкою, і негнучкою. Чи існує чітко встановлена ​​схема, як написати надійну та гнучку комунікацію між клієнтом та сервером? (Якщо ні, як би ви покращили те, що я маю нижче?)

Приблизно у мене таке:

  • Під час налаштування гри на сервері є одна нитка для кожного гнізда гнізда, що обробляє синхронні запити клієнта та відповіді від сервера.
  • Однак, коли гра відбудеться, всі теми, за винятком одного сну, і цей потік проходить по всіх гравців по одному, повідомляючи про їх рух (у зворотному запиті-відповіді).

Ось схема того, що я маю на даний момент; натисніть для збільшення / читабелішої версії або 66 КБ у форматі PDF .

Діаграма послідовності потоку

Проблеми:

  • Він вимагає, щоб гравці відповідали точно по черзі точно правильним повідомленням. (Я думаю, я міг би дозволити кожному гравцеві відповісти випадковим лайном і рухатись лише після того, як вони дадуть мені дійсний хід.)
  • Це не дозволяє гравцям спілкуватися з сервером, якщо не настала їх черга. (Я міг би сервер надсилати їм оновлення про інших гравців, але не обробляти асинхронний запит.)

Заключні вимоги:

  • Продуктивність не є першорядною. Це в основному буде використовуватися для ігор в режимі реального часу, і, головним чином, для того, щоб виконувати AI один проти одного, а не посмикуватися.

  • Гра завжди буде покрокова (навіть при дуже високій роздільній здатності). Кожен гравець завжди обробляє один хід, перш ніж усі інші гравці отримають поворот.

Реалізація сервера буває в Ruby, якщо це має значення.


Якщо ви використовуєте сокет TCP для клієнта, чи не буде це обмеження для (65535-1024) клієнтів?
o0 '.

1
Чому це проблема, Лохорісе? Більшість людей намагаються отримати 6000 одночасних користувачів, не маючи на увазі 60000.
Kylotan

@Kylotan Дійсно. Якщо у мене більше 10 одночасних ШІ, я буду здивований. :)
Фрогз

З цікавості, який інструмент ви використали для створення цієї діаграми?
Маттіас

@matthias На жаль, це було занадто багато на замовлення роботи в Adobe Illustrator.
Фрогз

Відповіді:


10

Я не впевнений, чого саме ви хочете досягти. Але є одна закономірність, яка постійно використовується на ігрових серверах, і може допомогти вам. Використовуйте черги повідомлень.

Якщо бути більш конкретним: коли клієнти надсилають повідомлення на сервер, не обробляйте їх негайно. Швидше розберіть їх і поставте в чергу для цього конкретного клієнта. Потім у деякому головному циклі (можливо, навіть в іншій темі) перейдіть по черзі всіх клієнтів, витягніть повідомлення з черги та обробіть їх. Коли обробка вказує на те, що черга цього клієнта закінчена, переходите до наступного.

Таким чином, від клієнтів не потрібно суворо працювати по черзі; тільки досить швидко, щоб у вас було щось у черзі до моменту обробки клієнта (ви, звичайно, можете або почекати клієнта, або пропустити його чергу, якщо він відстає). Ви можете додати підтримку асинхронних запитів, додавши чергу "асинхронність": коли клієнт надсилає спеціальний запит, він додається до цієї спеціальної черги; ця чергу перевіряється та обробляється частіше, ніж клієнтська.


1

Нитки апаратних засобів не набирають масштабів, щоб зробити «один на гравця» розумною ідеєю для 3-х значного числа гравців, і логіка знати, коли їх розбудити, - це складність, яка зростатиме. Кращою ідеєю є знайти асинхронний пакет вводу-виводу для Ruby, який дозволить вам надсилати та отримувати дані без необхідності призупиняти весь потік програми під час операції читання або запису. Це також вирішує проблему очікування гравців на відповідь, оскільки у вас не буде жодної теми на операції читання. Натомість ваш сервер може просто перевірити, чи не закінчився термін, а потім повідомити про це іншого гравця.

В основному "асинхронний введення / виведення" - це "шаблон", який ви шукаєте, хоча це насправді не шаблон, а більше підхід. Замість того, щоб явно закликати "читати" в сокет і призупиняти програму, поки дані не надійдуть, ви налаштовуєте систему для виклику обробника "onRead" щоразу, коли дані будуть готові, і ви продовжуєте обробку до цього часу.

кожна розетка має виток

У кожного гравця є черга, а у кожного гравця є сокет, який надсилає дані, що дещо відрізняється. Одного разу, можливо, не захочеться одного гнізда на гравця. Ви взагалі не можете використовувати розетки. Зберігайте окремі зони відповідальності. Вибачте, якщо це звучить як неважлива деталь, але коли ви поєднуєте в своєму дизайні поняття, які повинні бути різними, то це ускладнює пошук та обговорення кращих підходів.


1

Звичайно, існує більше ніж один спосіб зробити це, але особисто я б пропустив окремі потоки цілком і просто використав цикл подій. Як ви це зробите, дещо залежатиме від бібліотеки вводу / виводу, яку ви використовуєте, але в основному ваш основний цикл сервера буде виглядати так:

  1. Налаштуйте пул підключень та розетку для прослуховування для нових з'єднань.
  2. Зачекайте, що щось станеться.
  3. Якщо щось є новим з'єднанням, додайте його до пулу.
  4. Якщо щось є запитом від клієнта, перевірте, чи це щось, з чим ви можете негайно звернутися. Якщо так, зробіть так; якщо ні, покладіть його в чергу і (необов'язково) надішліть підтвердження клієнту.
  5. Також перевірте, чи є щось у черзі, з яким ви зараз можете впоратися; якщо так, зробіть це.
  6. Поверніться до кроку 2.

Наприклад, скажімо, у вас є п ять клієнтів, які беруть участь у грі. Коли тоді перший n − 1 з них відправляє свої ходи, ви просто перевіряєте, чи рухається ця дійсність, і відправляєте повідомлення про те, що цей крок отримано, але ви все ще чекаєте, поки інші гравці рухаються. Після того, як усі n гравців перейшли, ви обробляєте всі збережені ходи та надсилаєте результати всім гравцям.

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

Звичайно, ви також можете реалізувати щось подібне з окремими потоками для кожного з'єднання, передаючи ці потоки будь-яким запитам, які вони не можуть обробляти безпосередньо в центральний потік (або один на гру або один на сервер), який виконує цикл, наприклад показано вище, за винятком того, що він спілкується з потоками обробника з'єднання, а не безпосередньо з клієнтами. Незалежно від того, чи вважаєте ви цей простіший або складнішим, ніж підхід з однорядними потоками, саме вам належить.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.