Будь ласка, використовуйте цей ідентифікатор, щоб цитувати або посилатися на цей матеріал:
https://er.chdtu.edu.ua/handle/ChSTU/7760| Назва: | Синхронізація станів в багатокористувацьких іграх |
| Автори: | Кунченко-Харченко, Валентина Іванівна Яблонський, Віталій Анатолійович |
| Ключові слова: | багатокористувацькі ігри;синхронізація станів;клієнт-серверна архітектура;однорангова мережа (p2p);сокети;передача даних у мережі |
| Дата публікації: | 2021 |
| Короткий огляд (реферат): | Проблема надсилання даних, кодування інформації та проблема підвищення ефективності прийому і обслуговування клієнта на серверній стороні. Дослідження клієнт-серверної і однорангової топології мережі та їх особливості роботи, взаємодії з клієнтами, забезпечення узгодженості між клієнтами та захисту від читерства, метод шифрування RSA, перевірка ведення клієнта на сервері та методи ахисту від злому гри на стороні клієнта |
| URI (Уніфікований ідентифікатор ресурсу): | https://er.chdtu.edu.ua/handle/ChSTU/7760 |
| Розташовується у зібраннях: | 125 Кібербезпека та захист інформації (Безпека інформаційних і комунікаційних систем) |
Файли цього матеріалу:
| Файл | Опис | Розмір | Формат | |
|---|---|---|---|---|
| М_125_Яблонський_Кунченко-Харченко.pdf Restricted Access | 1.26 MB | Adobe PDF | Переглянути/Відкрити Запит копії |
Усі матеріали в архіві електронних ресурсів захищено авторським правом, усі права збережено.
Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
Факультет електронних технологій і робототехніки
Кафедра робототехнічних і телекомунікаційних систем та кібербезпеки
Пояснювальна записка
до кваліфікаційної роботи
магістра
(освітньо-кваліфікаційний рівень)
на тему: «Синхронізація станів в багатокористувацьких іграх»
Виконав: студент 2 курсу, групи БІ-001
Спеціальності 125 - «Кібербезпека»,
освітньої програми «Безпека
інформаційних і комунікаційних систем»
(шифр і назва спеціальності)
Яблонський В.А.
(прізвище та ініціали)
Керівник Кунченко-Харченко В.І.
(прізвище та ініціали)
Рецензент Чепинога. А.В.
(прізвище та ініціали)
Черкаси 2021
Форма № Н-9.01
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
(повне найменування вищого навчального закладу)
Факультет електронних технологій і робототехніки _______________________________________
(повна назва)
Кафедра робототехнічних і телекомунікаційних систем та кібербезпеки
(повна назва)
Освітньо-кваліфікаційний рівень магістр
Спеціальність 125 – «Кібербезпека»
Освітня програма Безпека інформаційних і комунікаційних систем
ЗАТВЕРДЖУЮ
Завідувач кафедри д.т.н., проф.
____________ В.В. Палагін
“___” _____________ 2021 року
З А В Д А Н Н Я
НА КВАЛІФІКАЦІЙНУ РОБОТУ МАГІСТРА СТУДЕНТУ
Яблонському Віталію Анатолійовичу
(прізвище, ім’я, по батькові)
1. Тема роботи «Синхронізація станів в багатокористувацьких іграх» _________________________
керівник роботи Кунченко-Харченко Валентина Іванівна, д.т.н., доцент_____ _________________
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання)
затверджені наказом Черкаського державного технологічного університету від
«21» Вересня 2021 року №289-1/01.
2. Строк подання студентом роботи «20» грудня 2021 р.
3. Вихідні дані до роботи виконати аналіз предметної області; дослідити роботу сокетів та клієнт-
серверної і однорангової топології; дослідити проблеми предметної області; розробити програму
яка буде синхронізувати гравців. ________________________________________________________
4. Зміст розрахунково-пояснювальної записки (перелік питань, що їх належить розробити)
Вступ, Розділ 1 Аналіз предметної області, Розділ 2 Основи передачі даних, Розділ 3
Синхронізація станів, Загальні висновки, Список використаних джерел.
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових креслень, плакатів):
мультимедійна презентація “Синхронізація станів в багатокористувацьких іграх” – 17 слайдів
(додається до роботи).
6. Консультанти розділів роботи
Підпис, дата
Розділ Прізвище, ініціали та посада консультанта
завдання видав завдання прийняв
7. Дата видачі завдання __________________
КАЛЕНДАРНИЙ ПЛАН
№ Строк виконання
Назва етапів кваліфікаційної роботи магістра Примітка
з/п етапів роботи
1. П ошук і аналіз інформації по заданій темі 25.09.2021-
30.09.2021
2. Н аписання І розділу “Аналіз предметної області” 01.10.2021-
20.10.2021
3. Н аписання ІІ розділу роботи “Основи передачі 21.10.2021-
даних” 10.11.2021
4. Н аписання ІІІ розділу “Синхронізація станів” 11.11.2021-
30.11.2021
5. Н аписання вступу і висновків, складання списку 01.12.2021-
літератури 10.12.2021
6. О формлення кваліфікаційної роботи магістра 11.12.2021-
15.12.2021
7. П одання роботи в ЕК 20.12.2021
8. З ахист роботи в ЕК 23.12.2021
Студент ________________________Яблонський В.А.
(підпис) (прізвище та ініціали)
Керівник роботи __________Кунченко-Харченко В.І.
(підпис) (прізвище та ініціали)
ЗМІСТ
СПИСОК СКОРОЧЕНЬ ТА УМОВНИХ ПОЗНАЧЕНЬ ..................................... 4
ВСТУП ...................................................................................................................... 5
РОЗДІЛ 1 ................................................................................................................ 13
АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ ..................................................................... 13
1.1 Локальні багатокористувацькі ігри ..................................................... 13
1.2 Ранні мережеві багатокористувацькі ігри .......................................... 14
1.3 Основні елементи багатокористувацької гри ..................................... 17
1.4 TCP/IP ..................................................................................................... 22
1.5 Sockets .................................................................................................... 29
РОЗДІЛ 2 ................................................................................................................ 40
ОСНОВИ ПЕРЕДАЧІ ДАНИХ ............................................................................ 40
2.1 Надсилання та отримання даних ......................................................... 40
2.2 Кодування інформації ........................................................................... 41
2.3 Складання потоків введення/виведення ............................................. 44
2.4 Фреймінг та парсинг ............................................................................. 45
2.5 Блокування сокету ................................................................................ 47
2.6 Мережеві топології ............................................................................... 54
РОЗДІЛ 3 ................................................................................................................ 62
СИНХРОНІЗАЦІЯ СТАНІВ................................................................................. 62
3.1 Затримка ................................................................................................. 62
3.2 Джіттер ................................................................................................... 66
3.3 TCP/UDP та синхронізація ................................................................... 67
3.4 Безпека .................................................................................................... 80
ЗАГАЛЬНІ ВИСНОВКИ ...................................................................................... 91
Список використаних джерел .............................................................................. 93
Додаток А .............................................................................................................. 95
Додаток Б .............................................................................................................. 127
Додаток В ............................................................................................................. 136
ЧДТУ.21.20106.009 ПЗ
Змн. Арк. № докум. Підпис Дата
Розроб. Яблонський В.А. Літ. Арк. Акрушів
Перевір. Кунченко-Харченко В.І. 3 144
СИНХРОНІЗАЦІЯ СТАНІВ В
Реценз. Чепинога А.В.
БАГАТОКОРИСТУВАЦЬКИХ ІГРАХ
Н. Контр. Кунченко-Харченко В.І. ЧДТУ БІ-001
Кунченко-Харченко В.І.
Затверд. Палагін В.В.
СПИСОК СКОРОЧЕНЬ ТА УМОВНИХ ПОЗНАЧЕНЬ
1) IDE – (англ. Integrated Development Environment) – інтегроване
середовище розробки.
2) MVS – microsoft visual studio.
3) ПЗ – програмне забезпечення.
4) ОС – операційна система.
5) ПК – персональний комп’ютер.
6) Мультіплеєр – багато гравців.
7) Читерство – шахраювати, обманювати, обхід правил заради власної
вигоди.
8) Хост – власник (будь-який пристрій), який приймає гостей.
Арк.
ЧДТУ.21.20106.009 ПЗ 4
Змн. Арк. № докум. Підпис Дата
ВСТУП
У сучасному світі створення відеоігор є одним з найбільш великих
сегментів індустрії розваг. Масштаби ігрової індустрії можна порівняти,
наприклад, з індустрією кіно.
Game Development – це повний цикл створення ігор: дизайну, розробки та
випуску. Сюди ж входить створення концепції і тестування. При створенні гри
важливо думати про ігрову механіку, нагороди, залучення гравців і опрацювання
рівнів. В команду по розробці входять: програмісти, звукорежисери, художники,
дизайнери і багато інших ролі. Розробка ігор може здійснюватися великий
студією або навіть однією людиною.
GameDev або розробку ігор неможливо розглядати відокремлено від
індустрії комп’ютерних ігор в цілому. Безпосередньо створення ігор - це лише
частина комплексної «екосистеми», що забезпечує повний життєвий цикл
виробництва, розповсюдження та споживання таких продуктів, як комп’ютерні
ігри.
У структурі сучасної ігрової індустрії можна виділити такі рівні:
платформи, ігрові движки, розробка відеоігор, видання та оперування,
популяризація та споживання.
Дуже невелика кількість навчальних закладів в світі готують фахівців
безпосередньо для ігрової індустрії. Умовно команду ділять на три великі групи:
розробники, художники і продюсери проекту.
До першої групи входять: програмісти, web-девелопери, розробники під
мобільні пристрої, системні адміністратори, адміністратори баз даних і т.д.. Це
найбільш високооплачувана частина команди, яка відповідає за всю технічну
складову гри.
До другої групи належать фахівці, що створюють візуальну частину гри:
3D моделлери, аніматори, текстувальники, ілюстратори та дизайнери.
І, продюсери проекту, які придумують гру і керують усією розробкою:
гейм-дизайнери і продюсери. Початковий внесок дизайнерів куди більш
Арк.
ЧДТУ.21.20106.009 ПЗ 5
Змн. Арк. № докум. Підпис Дата
важливий, ніж у більшості учасників команди. Саме вони створюють гарну
обкладинку.
Трохи інакше справа йде в інді-проектах (проекти, створені без прямої
фінансової підтримки), де ядро команди складають програміст, художник і гейм-
дизайнер. Решта завдань можуть виконуватися або учасниками команди з
додатковими навиками, або делегуються на аутсорс.
Апаратно-програмні системи, що дозволяють запускати інтерактивні
ігрові програми. Серед основних видів можна виділити:
• Персональні комп'ютери на базі Windows, Mac/OS X або Linux;
• Ігрові консолі (спеціалізовані пристрої для ігор: Xbox, PlayStation,
Nintendo Wii U);
• Мобільні пристрої (iOS, Android, Windows);
• Універсальні web платформи, соціальні мережі (FB, VK);
• Аркадні автомати;
• Платформи віртуальної реальності.
Всіх розробників можна розділити на групи, кожна з яких має свою зону
відповідальності. Загалом виділяють:
Геймдизайн – процес опрацювання основні концепцій майбутньої гри, її
правил, контенту, ігрового світу, механіки і геймплея. Геймдизайнер повинен
володіти одночасно художніми, технічними та письменницькими навичками
(для складання документації).
Провідний дизайнер, ведучий геймдизайнер – керівник і координатор
дизайнерської команди, в його обов'язки входить визначення та опрацювання
ключових елементів майбутньої гри, взаємодію з іншими керівниками інших
відділів (арт-директором, провідним програмістом і т.д.), вирішення інших
дизайнерських завдань, які не можна за будь-яких причин доручити підлеглим
дизайнерам. У загальному вигляді саме провідний дизайнер є особою, яка
визначає кінцевий вигляд гри, її "суть" на рівні геймплея.
Дизайнер ігрової механіки – людина, в чиї обов'язки входить визначення
потреб з боку програмного коду для реалізації ідей і геймплея, розробленого
Арк.
ЧДТУ.21.20106.009 ПЗ 6
Змн. Арк. № докум. Підпис Дата
дизайнерською командою. Дизайнер ігрової механіки повинен мати деякі
технічні навички в області програмування, щоб ставити завдання по реалізації
геймплея через програмний код перед програмістами.
Дизайнер рівнів, левел-дизайнер – людина, в завдання якого входить
створення та збирання ігрових рівнів, карт, локацій і т.д. Левел-дизайнером може
як вузькоспеціалізований людина, яка займається тільки складанням локацій з
готових елементів (моделей), так і поєднувати цю посаду з іншого – наприклад,
левел-дизайнер може бути ще і 3D-художником.
Дизайнер інтерфейсу, дизайнер UI – розробляє призначений для
користувача інтерфейс, всілякі меню, вікна і т.д..
Сценарист – людина, яка відповідає за текстову складову гри. В його
обов'язки входить складання діалогів, опрацювання сюжету, опис всіляких сцен,
подій, можливих варіантів їх розвитку, наповнення внутрішньо літератури, якщо
така є і т.д. – загалом, все, що зробить геймплей органічно вписаним в ігровий
світ. Набагато більш складним завданням може стати складання несуперечливих,
логічних місій і завдань, написання реалістичних реплік для різних персонажів -
все це вимагає високої художньої майстерності, володіння мовою.
Програмування ігор – процес створення програмного коду з метою
візуалізації ігрового світу, взаємодії гравця з цим світом і пересування по ньому.
Програмування відеоігор підрозділяється на велику кількість спеціалізованих
областей, кожна з яких відповідає за певну функціональну частину майбутньої
гри (штучний інтелект, фізика, візуалізація і т.д.).
Провідний програміст – найбільш досвідчений і технічно підкований
фахівець, в завдання якого входить зведення всіх підрозділів ігрового движка в
широкому сенсі цього слова в єдину працюючу систему. Провідний програміст
саме програмуванням може займатися меншу частину свого часу, тому що його
основний обов'язок це все ж саме координація і з’єднання всіх систем в єдине
ціле. Також ведучий програміст може займатися написанням ігрового движка
для всіх підсистем, якими вже будуть займатися інші люди.
Арк.
ЧДТУ.21.20106.009 ПЗ 7
Змн. Арк. № докум. Підпис Дата
Програміст ігрової механіки – людина, яка відповідає за програмну
реалізацію всіх елементів геймплея.
Програміст 3D-движка, програміст графічного движка – фахівець, який
відповідає за відображення ігрового світу на екрані гравця, шейдери, графічні
ефекти.
Програміст AI (штучного інтелекту) – в його завдання входить програмна
реалізація різних моделей поведінки ворогів, системи тригерів, умов та інших
елементів, які керують поведінкою ігрових об’єктів з боку комп’ютера.
Програміст UI – відповідає за взаємодію користувача з ігровим світом
через інтерфейс користувача (UI), меню.
Програміст інструментарію – в його завдання входить створення утиліт,
редакторів та інших інструментів, які повинні допомагати художникам і
дизайнерам виконувати їх завдання швидше, зручніше і якісніше. Зручний
інструментарій здатний в рази скоротити час створення окремих елементів гри.
Програміст мережевого коду – відповідає за взаємодії гри через мережу
інтернет (або локальну мережу) з серверами оновлень, іншими гравцями
(мультиплеер).
Графічний контент, Ассет – сукупність об’єктів ігрового світу, які гравець
бачить на екрані. Сюди входять моделі персонажів, предмети оточення,
ландшафт.
Арт-директор – керівник художнього відділу, зазвичай найдосвідченіший
співробітник арт-відділу, відповідає за координацію роботи підлеглих 2D і 3D
художників, контролює якість, стежить за витримуванням загального стилю.
2D-художник – узагальнена назва ряду більш вузьких спеціальностей.
Завданням 2D-художнка зазвичай є створення графічного контенту в 2D-
редакторах (зазвичай - Photoshop) у вигляді ескізних малюнків, текстур для 3D-
моделей, різного роду заставок, фонових зображень.
Концепт художник – відповідає за створення різного роду начерків, ескізів
персонажів, будівель, декорацій, локацій – іншими словами, у візуальній формі
представляє образи та ідеї майбутньої гри. Робота концепт художника служить
Арк.
ЧДТУ.21.20106.009 ПЗ 8
Змн. Арк. № докум. Підпис Дата
джерелом координації дій інших учасників арт-відділу, служить для
демонстрації ідей керівництву, інвесторам.
Художник по текстурам – в його обов’язки входить створення різних
текстур для 3D-моделей, створених 3D-художниками, відповідно до концепт-
малюнкам.
3D-художник – займається створенням 3D-контента у вигляді моделей
персонажів, техніки, архітектури та інших ігрових об’єктів. Спеціалізаціями 3D-
художників є: моделер персонажів, моделер оточення (environment artist),
аніматор.
Моделер персонажів – займається створенням в 3D-пакеті моделі ігрових
персонажів, їх одягу, озброєння, обладунків. Втім, персонажі можуть бути як
органічними (гуманоїди), так і неорганічними (різного роду техніка).
Моделер оточення – в його завдання входить моделювання предметів
навколишнього світу, ландшафту, будівель і т.д..
Аніматор – аніматорів можливо виділити в окрему категорію
спеціальностей; займаються анімацією (пожвавленням) 3D-моделей, найчастіше
персонажів. До завдань аніматора входить створення рухів ходьби, бігу,
стрибків, атаки і подібних рухів.
Тестування, контроль якості (QA, Quality Assurance) – процес виявлення
різного роду помилок і дефектів контенту гри. Тестування обов’язково повинно
проводитися щоб уникнути часткової або повної непрацездатності гри у
кінцевого споживача.
Тестування може проводитися відділом тестування видавця гри, тестерами
розробника, незалежними тестерами, а також в процесі публічного бета-
тестування звичайними користувачами. Процес тестування зводиться до
проведення і перевірки ряду стандартних сценаріїв (наприклад, не зависає гра
при виході на робочий стіл), тестування геймплея, і т.д..
Звук – відділ звуку забезпечує гру звуковими ефектами, музикою і озвучує
ігрових персонажів.
Композитор – в його завдання входить написання музики до гри.
Арк.
ЧДТУ.21.20106.009 ПЗ 9
Змн. Арк. № докум. Підпис Дата
Звукоінженер – займається пошуком готових звукових ефектів для гри або
створенням нових шляхом синтезу декількох звуків або записом оригінальних.
Актори озвучування – озвучують ігрових персонажів.
За останні кілька десятиліть ігрова індустрія стрімко розвивалася. Завдяки
технологічним досягненням, таким як тривимірна графіка, віртуальна реальність
і штучний інтелект. Починаючи з 2000-х не було ніяких сумнівів у тому, що
мультиплеєр підвищить цінність гри.
Захоплення іграми охопило людей різних вікових груп і різних професій.
У реальному житті зрозуміти, хто грає в ігри, а хто ні, не представляється
можливим, тільки якщо людина не демонструє відкрито своєї пристрасті. Чим
викликана така популярність онлайн ігор?
1) Немає кінцевої мети. В онлайн грі немає кінцевої мети, того фінішу при
якому гра закінчиться. По суті, протягом онлайн ігри нескінченно, в кампаніях
адміністрація може додати нові місії, а до самої гри нові можливості, ігрові
майданчики або рівні персонажів. Поки у гравців є інтерес до гри, вона
триватиме, адже практично будь-яка онлайн гра це, по суті, гонка на виживання.
Чому так? Можна дуже сильно в неї вкластися, але успіх буде тимчасовим, тому
що якщо перестанеш заходити в гру, тебе дуже швидко наздоженуть. При
бажанні в онлайн ігри можна грати роками.
2) Самореалізація. Можливість самореалізації, практично в кожній онлайн
грі є своє мірило успіху. Ігрова валюта, яку можна витрачати на розвиток або
накопичувати, а в деяких іграх торгувати на ігровому ринку з іншими гравцями.
Вона ж зазвичай визначає ваш ігровий статус. Проблема сучасного світу в тому,
що дуже багато людей просто не можуть в ньому реалізувати себе, приречені
бути простими шестернями у величезній структурі. Самореалізація в онлайн грі
часто здатна хоча б частково закрити цю потребу.
3) Свобода. Саме волі не вистачає багатьом в сучасному світі. Свобода
вибору, в більшості онлайн ігор ви можете бути тим, ким захочете, добрим чи
злим. Віртуальний світ не обмежує людини ні в здібностях, ні в правилах, ні в
діях. Користувач може вибирати ту стратегію поведінки, яка ніколи не буде
Арк.
ЧДТУ.21.20106.009 ПЗ 10
Змн. Арк. № докум. Підпис Дата
реалізована їм у реальному житті. Крім того, гравець має можливість
перевтілитися в будь-якого персонажа, поглянути на себе по-іншому. Діяти по
тій стратегії, яка вам найбільш приваблива. Більш того, відразу видно наскільки
ваша стратегія правильна, ви легко можете порівняти свій розвиток з іншими
гравцями. Зрозуміти, що ви робите не так. В одиночних іграх цього моменту
немає.
4) Спілкування. Закрити потребу в спілкуванні (Людина – соціальна істота,
а це значить, що ми не просто потребуємо соціальних контактів, ми потребуємо
відчутті своєї приналежності до групи.). Зрозуміло, це можна зробити і на будь-
якому інтернет-форумі, але загальний інтерес до гри зближує. Особливо, якщо в
онлайн грі передбачена командна робота, в цьому з’являється можливість
реалізувати свої лідерські якості. Або стати незамінним членом команди. У
деяких іграх передбачені форуми для спілкування. Деякі з них вдало реалізовані,
якщо ні, то завжди є можливість піти спілкуватися командою в соціальні мережі.
5) Можливість заробляти на грі. У багатьох сучасних і популярних онлайн
іграх ця можливість є. І здатність ще й заробити, граючи в улюблену гру,
безумовно, додає їй привабливості.
6) Якщо ви не заходите в гру, то вона все одно розвивається. Онлайн ігри
не статичні, вони не будуть вас чекати, якщо ви на якийсь час перестанете
заходити в неї, вона все одно буде розвиватися. Ви можете пропустити якісь
цікаві ігрові події, або навіть розробники гри можуть змінити правила. І
повернувшись, виявите щось нове, що знову поверне вам інтерес до гри.
7) Відпочинок. Зрозуміло, є ігри, в яких необхідна присутність мало не
постійно, але таких ігор все ж меншість. А більшість онлайн ігор орієнтовані на
більш спокійний режим. Ви можете зайти тоді, коли вам зручно, коли у вас є
бажання розвіятися, відволіктися від проблем реального світу.
8) Наявність великої кількості часу. Коли у людини багато вільного часу,
він прагне його чимось зайняти. Часто на допомогу приходять саме комп’ютерні
ігри.
Арк.
ЧДТУ.21.20106.009 ПЗ 11
Змн. Арк. № докум. Підпис Дата
9) В мультиплеєрі завжди є місце невизначеності. Не можливо
передбачити, як поведе себе інший гравець, і це робить гру цікавою. Саме ця
невизначеність дозволяє вам насолоджуватися грою, не дивлячись на
обмеженість контенту. І якщо в одиночній грі розробник змушений витрачати
довгі місяці на виробництво контенту, то в багатокористувацької грі на його
частку припадає лише частина подібної роботи.
Метою випускної роботи: дослідити проблеми синхронізація станів в
багатокористувацьких іграх.
Задачі випускної роботи:
1) Виконати аналіз предметної області;
2) Дослідити проблеми предметної області;
3) Розробити програму яка буде синхронізувати гравців.
Арк.
ЧДТУ.21.20106.009 ПЗ 12
Змн. Арк. № докум. Підпис Дата
РОЗДІЛ 1
АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ
1.1 Локальні багатокористувацькі ігри
Деякі з ранніх відеоігор мали локальний розрахований на багато
користувачів режим, тобто були розроблені для двох або більше гравців на
одному комп’ютері. Це включало деякі дуже ранні ігри, як Pong (Рис. 1.1) для
двох (1972). Здебільшого локальні багатокористувацькі ігри розраховані на
багато користувачів ігри можна програмувати так само, як і однокористувацькі
ігри. Єдиною відмінністю зазвичай являється у кількох точках зору (режимом
Split Screen – розподілом екрану) та/або підтримці кількох пристроїв введення.
Рис. 1.1 Дві ракетки відбивають м’яч. Рахунок відображається числами (0 та 1)
вгорі екрану.
Арк.
ЧДТУ.21.20106.009 ПЗ 13
Змн. Арк. № докум. Підпис Дата
1.2 Ранні мережеві багатокористувацькі ігри
Перші мережеві розраховані на багато користувачів ігри запускалися в
невеликих мережах. Що відрізняє мережну розраховану на багато користувачів
гру від локальної розрахованої на багато користувачів гри, так це те, що в
мережевих іграх два або більше комп’ютера підключені один до одного під час
активного ігрового сеансу. Оскільки персональні комп'ютери почали набувати
деякого поширення у другій половині 1970-х років, розробники з’ясували, як два
комп’ютери можуть зв’язуватися один з одним через послідовні порти.
Послідовний порт дозволяє передавати дані по одному біту за раз, і його типова
мета полягала в обміні даними із зовнішніми пристроями, такими як принтери
або модеми. Однак також можна було з’єднати два комп’ютери один з одним і
змусити їх обмінюватись даними через це з’єднання. Це дозволило створити
ігровий сеанс, який зберігався. Кілька персональних комп’ютерів, що призвело
до появи деяких ранніх мережевих комп’ютерних ігор.
Таким чином, незважаючи на те, що технологія була доступна на початку
1980-х років, більшість ігор, випущених протягом десятиліття, насправді не
використовували переваги локальної мережі таким чином. Тільки в 1990-х роках
ідея локального з’єднання кількох комп’ютерів для гри дійсно набула
поширення.
Локальна мережа або LAN – це термін, який використовується для опису
кількох комп’ютерів, підключених один до одного відносно невеликої території.
Механізм, що використовується для локального з’єднання, може різнитися -
наприклад, з’єднання через послідовний порт. Тим не менш, локальні мережі
дійсно стали популярними з поширенням Ethernet.
Хоча це ні в якому разі не перша гра, що підтримує розрахований на багато
користувачів режим по локальній мережі, Doom (1993) у багатьох відношеннях
став прабатьком сучасної мережевої гри. Початкова версія шутера від першої
особи id Software підтримувала до чотирьох гравців за одну ігрову сесію з
можливістю спільної гри чи «deathmatch – смертельного матчу».
Арк.
ЧДТУ.21.20106.009 ПЗ 14
Змн. Арк. № докум. Підпис Дата
Протягом багатьох років переважна більшість мережевих ігор також
підтримували ігри у локальній мережі. Хоча деякі і розраховані на багато
користувачів ігри все ще випускаються з можливістю гри по локальній мережі, в
останні роки спостерігається тенденція до того, що розробники відмовляються
від гри по локальній мережі на користь виключно мережевої гри.
Один із перших рішень яке потрібно зробити при розробці мережевої гри,
– це вибрати протокол зв’язку або встановлену угоду (an established convention),
відповідно до якої відбувається обмін даними між двома комп’ютерами.
Використання ненадійного протоколу (це означає, що отримання даних,
надісланих через мережу, одержувачем не гарантується) може бути
проблематичним, коли грі необхідно надіслати інформацію, яка важлива для всіх
гравців у грі. Таким чином потрібно розглянути різні типи даних, які потрібно
надіслати, розділивши свої вимоги до даних на наступні чотири категорії:
1. Негарантовані дані. Як і слід було очікувати, це дані, які гра вважає
несуттєвими для гри;
2. Гарантовані дані. Ці дані гарантують отримання та впорядкування
даних. Це використовується для даних, які гра вважає критичними, як подія, що
вказує, коли гравець вистрілив зі зброї;
3. Дані про «останній стан». Цей тип даних призначений для випадків,
коли важлива лише остання версія даних. Один із прикладів – кількість життів
конкретного гравця;
4. Гарантовано найшвидші дані. Цим даним дається найвищий
пріоритет, щоб їх можна було передати якнайшвидше з гарантованою
доставкою. Прикладом цього типу даних є інформація про рух гравця, яка
зазвичай є актуальною протягом дуже короткого періоду часу і, отже, має
передаватися швидко.
У моделі клієнт-сервер (англ. client-server model – Рис. 1.2) всі гравці
підключаються до центрального сервера, тоді як в одноранговій моделі (англ.
peer-to-peer model, P2P – рівний до рівного – Рис. 1.3) кожен гравець
підключається до решти гравців.
Арк.
ЧДТУ.21.20106.009 ПЗ 15
Змн. Арк. № докум. Підпис Дата
Рис. 1.2 Однорангова мережа (P2P)
Рис. 1.3 Мережа, заснована на моделі клієнт-сервер
Арк.
ЧДТУ.21.20106.009 ПЗ 16
Змн. Арк. № докум. Підпис Дата
1.3 Основні елементи багатокористувацької гри
Пакет – це відформатований набір даних, що надсилаються мережею. Цей
рівень є оболонкою для стандартних API-інтерфейсів сокетів, які можуть
створювати та відправляти пакети різних форматів.
Завдання диспетчера з’єднань – абстрагувати з’єднання між двома
комп’ютерами через мережу. Він приймає дані від рівня вище, диспетчер
потоків, і передає дані на рівень нижче.
Основне завдання диспетчера потоків – надсилати дані диспетчеру
з’єднань. Одним із важливих аспектів цього є визначення максимальної
дозволеної швидкості передачі даних. Це буде залежати від якості підключення
до інтернету. Ця швидкість та розмір відправляються на сервер при підключенні
клієнта, щоб гарантувати, що сервер не перевантажує з’єднання клієнта надто
великим обсягом даних.
Оскільки кілька інших систем запитуватимуть у диспетчера потоків дані
про відправку, він також зобов’язаний пріоритизувати ці запити. Менеджери
переміщення та подій отримують найвищий пріоритет у сценарії з обмеженням
смуги пропускання. Як тільки диспетчер потоків вирішує, які дані надсилати,
пакети надсилаються диспетчеру з’єднань. У свою чергу менеджери вищого
рівня будуть проінформовані менеджером потоку про статус доставки.
Через встановлений інтервал і розмір пакета, встановлений диспетчером
потоків, дуже можливо, що пакет буде відправлено з кількома типами даних в
ньому. Наприклад, у пакеті можуть бути деякі дані з диспетчера переміщень,
деякі дані з диспетчера подій.
Менеджер подій підтримує чергу подій, що генеруються симуляцією гри.
Ці події можна розглядати як просту форму віддаленого виклику процедури або
RPC (remote procedure call), функції, які можуть виконуватися на віддаленому
комп’ютері.
Наприклад, коли гравець стріляє зі зброї, це, швидше за все, призведе до
відправки події «гравець вистрелив» до диспетчера подій. Потім ця подія може
Арк.
ЧДТУ.21.20106.009 ПЗ 17
Змн. Арк. № докум. Підпис Дата
бути відправлена на сервер, який фактично підтвердить та виконає вистріл зі
зброї. Також до компетенції диспетчера подій входить пріоритизація подій – він
намагатиметься записати якнайбільше подій з найвищим пріоритетом, доки не
буде виконано одну з наступних умов: пакет заповнений, черга подій порожня.
Менеджер подій також відстежує записи передачі для кожної події,
позначеної як надійна. Таким чином, диспетчер подій дуже просто забезпечити
надійність. Якщо достовірна подія не підтверджена, диспетчер подій може
просто додати подію до черги подій та повторити спробу. Звісно, будуть події,
відзначені як ненадійні. Для цих ненадійних подій немає потреби навіть
відстежувати їх записи про передачу.
На високому рівні завдання менеджера з оптимізації полягає в реплікації
динамічного об’єкта, який вважається таким, що відноситься до конкретного
клієнта. Іншими словами, сервер надсилає клієнтам інформацію про динамічні
об’єкти, але тільки про об’єкти, про які, на думку сервера, клієнт повинен знати.
Рівень симуляції гри відповідає за визначення того, що клієнту необхідно знати
і що клієнт в ідеалі повинен знати. Це додає невід’ємну пріоритизацію ігровим
об'єктам у світі: об’єкти, яким потрібно знати, мають найвищий пріоритет, а
об’єкти, які потрібно знати, мають нижчий пріоритет. Загалом визначення
релевантності об’єкта дуже залежить від гри.
Незалежно від того, як обчислюється набір відповідних об’єктів, завдання
менеджера привидів полягає в тому, щоб передавати стан об’єкта від сервера до
клієнта якомога більшої кількості відповідних об’єктів. Дуже важливо, щоб
менеджер гарантував, що найсвіжіші дані завжди успішно передаються всім
клієнтам. Причина цього в тому, що інформація про ігровий об’єкт часто містить
таку інформацію, як здоров’я, зброя, кількість боєприпасів і т.д..
Коли об’єкт стає релевантним (або «попадає в область дії»), менеджер
присвоює йому деяку інформацію. Цей запис включатиме такі елементи, як
унікальний ідентифікатор, маску стану, пріоритет та зміну статусу (незалежно
від того, об’єкт був відзначений як вхідний або що виходить за область дії). Як
Арк.
ЧДТУ.21.20106.009 ПЗ 18
Змн. Арк. № докум. Підпис Дата
тільки менеджер привидів визначить об’єкти, які слід надіслати, їх дані можна
додати у вихідний пакет.
Відповідальність менеджера руху – якнайшвидше передавати дані про
пересування гравця. В динамічних розрахованих на багато користувачів іграх,
точна інформація про рух надзвичайно важлива. Якщо інформація про
положення гравця надходить повільно, це може призвести до того, що гравці
стрілятимуть у те місце, де він був раніше, а не в тому, де він знаходиться.
Швидкі поновлення руху можуть бути важливим способом зменшити
сприйняття затримки з боку гравця.
Кожен клієнт відповідає за передачу інформації про своє переміщення на
сервер. Потім сервер застосовує цю інформацію про хід у моделюванні гри та
підтверджує отримання інформації про хід клієнту, який її надіслав.
Оскільки кожен ігровий екземпляр виконує незалежну симуляцію, є сенс
використовувати однорангову топологію, одна з переваг моделі полягає в тому,
що дані можуть швидше досягати кожного комп’ютера. Це тому, що сервер не
діє посередником. Однак одним із недоліків є те, що кожен гравець повинен
надсилати свою інформацію кожному іншому гравцеві, а не лише одному
серверу.
Так, наприклад, якщо гравець А видає команду атаки, то кожен ігровий
екземпляр повинен знати про цю команду атаки, інакше їх моделі будуть
розходитися один з одним.
Однак, слід враховувати ще один ключовий фактор. Різні гравці
запускатимуть гру з різною частотою кадрів, і у різних гравців буде різна якість
зв’язку. Повертаючись, наприклад, коли гравець А видає команду атаки, так само
важливо, щоб гравець А не відразу застосував команду атаки. Натомість гравець
A повинен застосовувати команду атаки лише тоді, коли всі гравці B та C готові
одночасно застосувати команда. Але якщо гра гравця А надто довго чекає, щоб
виконати команду атаки, здаватиметься гра має поганий відгук на дії гравця який
ініціював команду атаки.
Арк.
ЧДТУ.21.20106.009 ПЗ 19
Змн. Арк. № докум. Підпис Дата
Є один важливий крайній випадок, який слід враховувати під час
використання підходу. Що станеться, якщо у одного з гравців виникне сплеск
затримки, і він більше не зможе встигати за 200-мілісекундним таймером? Деякі
ігри можуть тимчасово призупиняти симуляцію, щоб подивитися, чи можна
подолати сплеск затримки – зрештою, гра може вирішити відмовитися від
гравця, якщо він продовжить уповільнювати гру для решти. Можна спробувати
компенсувати цей сценарій, динамічно регулюючи частоту кадрів рендерингу
залежно від умов мережі – таким чином, комп’ютер з особливо повільним
підключенням до інтернету може виділяти більше часу для прийому даних через
мережу, при цьому менше часу виділяється на рендеринг графіки.
Існує також додаткова перевага передачі команд, відданих клієнтами. За
такого підходу не потрібно багато додаткової пам’яті або роботи для збереження
команд, виданих протягом усього матчу. Це безпосередньо веде до можливості
реалізації повторів матчів. Повтори дуже популярні в іграх RTS, тому що вони
дозволяють гравцям оцінювати матчі, щоб отримати глибше розуміння
стратегій. Для створення повторів у підході, при якому замість команд
передається інформація про пристрій, знадобиться значно більше пам’яті.
Оскільки кожна машина приймає та обробляє команди незалежно, дуже
важливо, щоб кожна машина давала однаковий результат. Один конкретний
приклад виникає з того факту, що в більшості ігор є певний ступінь випадковості
у діях. Наприклад, що, якщо гра виконує випадкову перевірку, щоб визначити,
чи влучає лучник піхоту? Можна припустити, що екземпляр гравця A вирішує,
що лучник дійсно вражає піхоту, тоді як екземпляр гравця B вирішує, що лучник
не вражає піхоту. Вирішенням цієї проблеми є використання генератора
псевдовипадкових чисел (PRNG – pseudorandom number generator). Оскільки всі
PRNG використовують певний вид заповнення, ви можете гарантувати, що
обидва гравці А і Б отримають однакові випадкові результати. Проте слід
пам’ятати, що seed (генератор ініціалізований одними й тими самими
початковими умовами, видає абсолютно ідентичні випадкові послідовності)
гарантує лише певну послідовність чисел. Тому не тільки важливо, щоб кожен
Арк.
ЧДТУ.21.20106.009 ПЗ 20
Змн. Арк. № докум. Підпис Дата
екземпляр гри використовував одне і те ж початкове число, але і не менш
важливо, щоб кожен екземпляр гри виконував однакову кількість викликів
випадкового числа генерації – інакше числа PRNG не синхронізуватимуться.
Перевірка синхронізації має неявну перевагу – вона знижує можливість
шахрайства для гравців. Наприклад, якщо один гравець виділяє 500 додаткових
ресурсів, інші ігрові екземпляри можуть негайно виявити десинхронізацію в
ігровому стані. Однак, як і з будь-якою системою, тут є компроміси – той факт,
що кожен ігровий стан імітує елементи у грі, означає, що можна створювати чіти,
які розкривають інформацію, яка не повинна бути видимою.
У шутерах реалізована мережева архітектура, як і раніше, актуальна для
сучасних бойовиків. Використовується модель клієнт-сервер, тому кожен
гравець у грі підключений до сервера, який координує гру. На найнижчому рівні
пакетний модуль платформи абстрагує надсилання пакетів по мережі. Потім
диспетчер з’єднань підтримує з’єднання між гравцями та сервером та надає
повідомлення про стан доставки. Менеджер потоків отримує дані від менеджерів
вищого рівня (включаючи менеджерів подій та переміщень) та залежно від
пріоритету додає ці дані до вихідних пакетів. Менеджер подій приймає важливі
події, такі як «гравець вистрелив», та забезпечує отримання цих даних
відповідними сторонами. Менеджер з оптимізації обробляє надсилання оновлень
об’єктів для набору об’єктів, які вважаються релевантними для конкретного
гравця. Менеджер ходів надсилає останню інформацію про рух для кожного
гравця.
Альтернативою буде модель де всі комп’ютери підключаються один до
одного в одноранговій мережі. Замість того, щоб надсилати інформацію про
кожен пристрій мережі, гра надсилає команди кожному партнеру. Далі ці
команди незалежно оцінюються кожним партнером. Щоб гарантувати
синхронізацію машин, таймер ходу використовується для збереження команд
протягом певного періоду часу перед їх відправкою по мережі. Ці команди
виконуються лише через два ходи, що дає кожному партнеру достатньо часу для
відправлення та отримання команд. Крім того, важливо, щоб кожен партнер
Арк.
ЧДТУ.21.20106.009 ПЗ 21
Змн. Арк. № докум. Підпис Дата
виконував детерміноване моделювання, що означає, наприклад, синхронізацію
генераторів псевдовипадкових чисел.
1.4 TCP/IP
Стек протоколів TCP/IP (Transmission Control Protocol/Internet Protocol,
протокол управління передачею/протокол інтернету) – мережна модель, що
описує процес передачі цифрових даних. Пакет TCP/IP складається з вежі
незалежних і добре абстрагованих рівнів (Рис. 1.4), кожен із яких підтримується
безліччю взаємозамінних протоколів, які виконують свої обов’язки щодо
підтримки залежних рівнів та передачі своїх даних.
Рис. 1.4 Стек протоколів TCP/IP
Модель взаємодії систем (OSI) використовує сім рівнів: фізичний рівень,
рівень каналу передачі даних, мережевий рівень, транспортний рівень, рівень
сеансу, рівень подання та додатків. Кожен рівень має обов’язок підтримувати
потреби рівня, що безпосередньо над ним. Зазвичай цей обов’язок включає:
Арк.
ЧДТУ.21.20106.009 ПЗ 22
Змн. Арк. № докум. Підпис Дата
• Прийняття блоку даних для передачі з вищого рівня;
• Упаковка даних за допомогою заголовка рівня;
• Перенаправлення даних на нижній рівень для подальшої передачі;
• Отримання даних з нижнього рівня;
• Розпакування переданих даних шляхом видалення заголовка;
• Перенаправлення переданих даних на більш високий рівень для
подальшої обробки.
Існують різні протоколи, які кожен рівень може використовувати для
виконання своїх завдань. Аналогічно з об’єктно-орієнтованим програмуванням,
може бути корисно розглядати кожен рівень як інтерфейс, а кожен протокол чи
набір протоколів – як реалізацію цього інтерфейсу. В ідеалі деталі реалізації
рівня абстрагуються від вищих.
У самому низу знаходиться підтримуючий рівень – фізичний рівень.
Завдання фізичного рівня – забезпечити фізичне з’єднання між мережевими
комп’ютерами чи хостами. Для передачі необхідний фізичний носій. Вита пара,
телефонні лінії, коаксіальний кабель і оптоволоконний кабель – це приклади
фізичних носіїв, які можуть забезпечити з’єднання, необхідне фізичним рівнем.
Радіохвилі також є відмінним фізичним середовищем для передачі інформації.
Завдання канального рівня – забезпечити спосіб зв’язку між фізично
підключеними хостами. Канальний рівень повинен надавати метод, за
допомогою якого вихідний хост може запакувати інформацію та передавати її
через фізичний рівень, так що передбачуваний цільовий хост має шанс отримати
пакет і отримати бажану інформацію. На канальному рівні одиниця даних
передачі відома як кадр. Доставка кадру на хост тільки можлива, але не
гарантована. Порушення фізичного середовища, якісь електричні перешкоди або
відмова обладнання можуть призвести до того, що кадр буде відкинуто і ніколи
не буде доставлено. Канальний рівень не обіцяє, що будуть зроблені будь-які
зусилля, щоб визначити, чи надійшов кадр, чи повторно відправити його, якщо
він не надійшов. На щастя, завдяки абстракції пакета TCP/IP деталі
використовуваних протоколів канального рівня в основному приховані від гри.
Арк.
ЧДТУ.21.20106.009 ПЗ 23
Змн. Арк. № докум. Підпис Дата
Особливості способу передачі пакетів Ethernet фізично різняться в
залежності від носія і не мають відношення до програміста
багатокористувацьких ігор. Досить сказати, що кожен хост у мережі отримує
кадр, після чого хост читає кадр і визначає, чи є він очікуваним одержувачем.
Якщо це так, він отримує дані корисного навантаження і обробляє їх відповідним
чином на основі значення поля довжини/типу.
Канальний рівень забезпечує чіткий спосіб відправлення даних з хоста.
Виявляється, канальний рівень має кілька недоліків, які вимагають наявності
вищого рівня адресування:
• Записані MAC-адреси обмежують гнучкість обладнання. Наприклад
якщо ви маєте дуже популярний веб-сервер, який тисячі користувачів відвідують
щодня через Ethernet. Якщо ви використовували лише канальний рівень, запити
до сервера потрібно було б адресувати через MAC-адресу його мережевої карти
Ethernet. Але якщо одного дня мережна карта згорає і ви встановлюєте замінну
мережевій карті, вона матиме іншу MAC-адресу, і, таким чином, ваш сервер
більше не отримуватиме запити від користувачів. Ясно, що вам потрібна деяка
адресна система, що легко настроюється, яка живе поверх MAC-адреси;
• Канальний рівень не підтримує сегментування Інтернету на дрібніші
локальні мережі. Якби весь інтернет працював лише на канальному рівні, всі
комп’ютери довелося б поєднати в єдину безперервну мережу в якій кадри
відправляються на кожен хост у мережі який намагається визначити, чи є він
передбачуваним одержувачем. Якби інтернет використовував для зв’язку тільки
Ethernet, то кожен кадр мав би передаватися на кожен хост на планеті.
Максимальний розмір корисного навантаження кадру Ethernet становить
1500 байтів, а максимальний розмір пакету IPv4 складає 65 535 байт. Якщо IP-
пакет необхідно передати, уклавши його в кадр канального рівня, як він може
бути більшим, ніж MTU (maximum transmission unit) канального рівня? Якщо IP-
модуль просять передати пакет, розмір якого перевищує MTU цільового
канального рівня, він може розбити пакет на стільки фрагментів з розміром MTU,
скільки необхідно.
Арк.
ЧДТУ.21.20106.009 ПЗ 24
Змн. Арк. № докум. Підпис Дата
Хоча фрагментація IP-пакетів дозволяє відправляти гігантські пакети, вона
призводить до двох великих недоліків. По-перше, це фактично збільшує обсяг
даних, які мають бути надіслані через мережу. Наприклад 3020-байтовий пакет
фрагментується на два 1500-байтові (Payload – 1480, Header length – 20) пакети і
80-байтовий пакет (Payload – 60, Header length – 20), всього 3080 байтів. Але
якщо при передачі втрачається один фрагмент, хост, що приймає, повинен
відкинути весь пакет. Це збільшує можливість відкидання великих пакетів з
великою кількістю фрагментів. З цієї причини зазвичай рекомендується
повністю уникати фрагментації, переконавшись, що всі IP-пакети менші за MTU
канального рівня. Це не обов’язково просто, тому що між двома хостами може
бути кілька різних протоколів канального рівня, тому розробники ігор
наближаються до того, що мінімальний MTU всього маршруту пакета
становитиме 1500 байтів.
Ці 1500 байтів повинні інкапсулювати 20-байтовий заголовок IP, корисне
навантаження IP і будь-які додаткові дані, необхідні протоколами оболонки,
такими як VPN або IPSec, які можуть використовуватися. Тому розумно
обмежити корисне навантаження IP приблизно 1300 байтами.
Гра, що відправляє пакети довжиною всього 200 байт, витрачає 10% своєї
пропускної спроможності лише на IP-заголовки, що дуже неефективно. З цієї
причини, якщо мінімальний MTU становить 1500 це означає, що лише 1,3%
пропускної спроможності витрачається на заголовки IP, що набагато краще , ніж
20%.
Відмінність від IPv4 полягає в тому, що IPv6 більше не підтримує
фрагментацію пакетів на рівні маршрутизатора. Це дозволяє видалити всі поля,
пов’язані з фрагментацією, із заголовка IP та заощадити частину смуги
пропускання для кожного пакета. Якщо пакет IPv6 досягає маршрутизатора і
занадто великий для рівня вихідного каналу, маршрутизатор просто відкидає
пакет і відповідає відправнику, що пакет був занадто великим. Відправник має
спробувати ще раз із меншим пакетом.
Арк.
ЧДТУ.21.20106.009 ПЗ 25
Змн. Арк. № докум. Підпис Дата
Завдання транспортного рівня – забезпечувати зв’язок між окремими
процесами. Оскільки на одному хості можуть виконуватися кілька процесів, не
завжди достатньо знати, що хост A відправив IP-пакет хосту B. Коли хост B
отримує IP-пакет, йому необхідно знати, в який процес слід передати вміст для
подальшого оброблення. Щоб вирішити цю проблему, транспортний рівень
запроваджує поняття портів. Порт – це 16-бітове невід’ємне число, що
представляє кінцеву точку зв’язку на певному хості.
Порти від 0 до 1023 відомі як системні або зарезервовані порти. Ці порти є
особливими, оскільки більшість операційних систем дозволяють зв’язувати
системні порти лише процесами кореневого рівня, що дозволяє використовувати
їх для цілей, які потребують підвищеного рівня безпеки.
Порти з 49 152 по 65 535 відомі як динамічні порти. Вони ніколи не
призначаються і можуть використовуватись будь-яким процесом. Якщо процес
намагається виконати прив’язку до динамічного порту і виявляє, що він
використовується, він повинен обережно обробити це, намагаючись виконати
прив’язку до інших динамічних портів, доки не буде знайдено доступний.
Після того, як програма визначила порт, що використовується, він повинен
використовувати протокол транспортного рівня для фактичного відправлення
даних.
TCP (transmission control protocol) – транспортний протокол передачі у
мережах TCP/IP, попередньо встановлює з’єднання з мережею.
UDP (user datagram protocol) – транспортний протокол, який передає
повідомлення без необхідності встановлення з’єднання в IP-мережі.
TCP вимагає відгуку від клієнта, якому доставлено пакет даних,
підтвердження доставки, і для цього йому необхідне заздалегідь встановлене
з’єднання. Також протокол TCP вважається надійним, тоді як UDP є ненадійним.
TCP виключає втрати даних, дублювання та перемішування пакетів, затримки.
UDP все це допускає, і з’єднання для роботи не потрібно. Процеси, яким дані
передаються UDP, повинні обходитися отриманим, навіть і з втратами. TCP
Арк.
ЧДТУ.21.20106.009 ПЗ 26
Змн. Арк. № докум. Підпис Дата
контролює завантаженість з’єднання, UDP не контролює нічого, крім цілісності
отриманих датаграм.
У самому верху TCP/IP знаходиться прикладний рівень, і саме тут живе
код нашої розрахованої на багато користувачів гри. Прикладний рівень також є
домом для багатьох фундаментальних протоколів Інтернету, які покладаються
на рівень транспорту.
Призначення унікальних IPv4-адрес кожному хосту в приватній підмережі
може бути адміністративною проблемою, особливо в епоху коли
використовуються ноутбуки та смартфони. Протокол динамічної конфігурації
хоста (DHCP – dynamic host configuration protocol) вирішує цю проблему.
Після підключення до мережі хост створює повідомлення
DHCPDISCOVER, що містить власну MAC-адресу, і передає його за допомогою
UDP на 255.255.255.255:67. Оскільки це йде на кожен хост у підмережі, будь-
який DHCP-сервер отримає повідомлення. DHCP-сервер, якщо він має IP-адресу
для пропозиції клієнту, готує пакет DHCPOFFER. Цей пакет містить як
пропоновану IP-адресу, так і MAC-адресу клієнта, якій буде надіслано
пропозицію. На цьому етапі клієнту не призначено IP-адресу, тому сервер не
може безпосередньо адресувати йому пакет. Замість цього сервер розсилає пакет
по всій підмережі через UDP-порт 68. Всі DHCP-клієнти отримують пакет, і
кожен перевіряє MAC-адресу в повідомленні, щоб визначити, чи є гаданим
одержувачем. Коли правильний клієнт отримує повідомлення, він зчитує
запропоновану IP-адресу і вирішує, чи хоче вона прийняти цю пропозицію. Якщо
це так, він відповідає за допомогою широкомовного розсилання повідомленням
DHCPREQUEST із запитом запропонованої адреси. Якщо пропозиція ще
доступна, сервер відповідає, знову через широкомовну розсилку, повідомленням
DHCPACK. Це повідомлення підтверджує клієнту, що IP-адреса призначена, і
передає будь-яку необхідну додаткову мережну інформацію, таку як маска
підмережі, адресу маршрутизатора та будь-які рекомендовані DNS-сервери імен
для використання.
Арк.
ЧДТУ.21.20106.009 ПЗ 27
Змн. Арк. № докум. Підпис Дата
DNS-сервер (DNS – domain name system) – це сервер, який містить базу
даних публічних IP-адрес і пов’язаних з ними імен хостів. Як правило, сервер
DNS виконує роль перекладача, дозволяючи або переводячи імена хостів в IP-
адреси.
Сервер імен зберігає зіставлення доменних імен із IP-адресами. Для
розуміння принципу дії сервера можна використовувати аналогію з телефонним
довідником. DNS, що обробляє різні доменні імена, функціонує як надшвидка
телефонна книга інтернету. Вона безперервно проводить пошук та порівняння
цифр, аналогічний пошуку імені у довіднику з використанням відомого
телефонного номера.
Якщо користувач вводить URL-адресу в адресний рядок свого браузера,
він відправляється на доменний сервер, який потім перенаправляє користувача
на IP-адресу, прив’язану до URL-адреси. Якщо перша служба не знаходить
відповідного призначення, запит надсилається на наступний DNS-сервер. Запити
та відповіді DNS зазвичай надсилаються через UDP на порт 53.
IP-адреса вважається публічно маршрутизованою, якщо будь-який
правильно налаштований маршрутизатор в інтернеті може встановити пакет на
маршруті так, щоб пакет зрештою досяг хоста з цією IP-адресою. Це вимагає,
щоб будь-яка публічно маршрутизована адреса була однозначно призначена
одному хосту. Якщо два або більше хоста спільно використовують ту саму IP-
адресу, то пакет, адресований одному, може прийти іншому.
Оскільки IPv4 підтримує лише 32-бітовий адресний простір, доступно
лише 4 294 967 296 потенційних загальнодоступних IP-адрес. Часто мережевий
адміністратор або користувач може виявити, що йому виділено менше
загальнодоступних IP-адрес, ніж хостів. Наприклад, у нас є смартфон, ноутбук
та ігрова консоль, але ми платимо лише за одну загальнодоступну IP-адресу від
нашого інтернет-провайдера. Було б неприємно, якби кожному пристрою була
потрібна власна виділена загальнодоступна IP-адреса! Щоразу, коли ми
підключали новий гаджет до інтернету, нам доводилося боротися з іншими
Арк.
ЧДТУ.21.20106.009 ПЗ 28
Змн. Арк. № докум. Підпис Дата
користувачами за нову IP-адресу від нашого інтернет-провайдера, а потім,
ймовірно, також платити за неї.
Можна підключити до інтернету цілу підмережу вузлів через одну
загальну загальнодоступну IP-адресу. Це стало можливим завдяки трансляції
мережевих адрес або NAT (network address translation). Щоб налаштувати мережу
для NAT, кожному вузлу в мережі має бути призначена приватна IP-адреса.
Блоки IP-адрес (10.0.0.0 – 10.255.255.255, 172.16.0.0 – 172.31.255.255, 192.168.0.0
– 192.168.255.255), зарезервовані для приватного використання, що гарантують,
що жодна адреса з цих блоків ніколи не буде призначена як загальнодоступна IP-
адреса. Таким чином, будь-який користувач може створити власну приватну
мережу, використовуючи IP-адреси з приватною маршрутизацією, без перевірки
на унікальність. Унікальність між мережами не потрібна, оскільки адреси не є
загальнодоступними. Тобто жоден загальнодоступний маршрутизатор в
інтернеті не повинен мати маршрутну інформацію про те, як досягти приватної
IP-адреси, тому не має значення, чи використовують кілька приватних мереж
усередині тих самих приватних IP-адрес.
1.5 Sockets
Сокет – це абстракція, через яку програма може відправляти та отримувати
дані, майже так само, як відкритий файл дозволяє додатку читати та записувати
дані у стабільне сховище. Сокет дозволяє програмі підключатися до мережі та
взаємодіяти з іншими програмами, які також підключені до тієї ж мережі.
Інформація, записана в сокет програмою на одній машині, може бути прочитана
програмою на іншій машині, і навпаки.
Різні типи сокетів відповідають різним базовим пакетам протоколів та
різним стекам протоколів у пакеті. Основними типами сокетів у TCP/IP сьогодні
є сокети потоків та сокети дейтаграм. Потокові сокети використовують TCP як
наскрізний (end-to-end – від початку до кінця) протокол (з IP під ним) і, таким
чином, забезпечують надійну службу потоку байтів. Сокети дейтаграм
Арк.
ЧДТУ.21.20106.009 ПЗ 29
Змн. Арк. № докум. Підпис Дата
використовують UDP (знову ж таки, наскрізний протокол з IP внизу) і, таким
чином, надають найбільш ефективну службу дейтаграм, яку програми можуть
використовувати для надсилання окремих повідомлень довжиною до 65 535
байт. Сокет TCP/IP однозначно ідентифікується за інтернет-адресою, наскрізним
протоколом (TCP або UDP) та номером порту. У міру просування ви зіткнетеся
з декількома способами прив’язки до адреси сокета.
IPv4 використовує 32-бітові двійкові адреси для ідентифікації хостів, що
взаємодіють. Клієнт повинен вказати IP-адресу хоста, на якій запущено програму
сервера, коли він ініціює зв’язок; мережна інфраструктура використовує 32-
бітну адресу призначення для маршрутизації інформації клієнта на відповідну
машину. Адреси можуть бути вказані в C# за допомогою їх 32-бітного довгого
цілочисельного значення або за допомогою рядка (наприклад, 169.1.1.1). dotNET
інкапсулює IP-адрес у клас IPAddress, який може приймати довгий цілий
аргумент IP у своєму конструкторі або обробляти рядок з чотириточковим
поданням IP-адреси за допомогою свого методу Parse(). Клас Dns також надає
механізм для пошуку або дозволу імен IP-адрес. Оскільки в сучасному інтернеті
нерідко один сервер дозволяється в кілька IP-адрес або псевдонімів імен,
результати повертаються в класі контейнера IPHostEntry, який містить масив з
одного або декількох рядкових імен хостів і екземплярів IPAddress.
Клас dns має метод GetHostName() не приймає аргументів і повертає рядок,
який містить ім’я локального хоста. Методи GetHostByName() та Resolve() в
основному ідентичні; вони приймають рядковий аргумент, що містить ім’я хоста,
яке потрібно знайти, і повертають інформацію про IP-адресу та ім’я хоста для
наданих вхідних даних у формі екземпляра класу IPHostEntry. Метод
GetHostByAddress() приймає рядковий аргумент, що містить рядкове
представлення IP-адреси з точками та чотирма точками, а також повертає
інформацію про хост в екземплярі IPHostEntry.
Платформа dotNET надає два класи спеціально для TCP: TcpClient та
TcpListener. Екземпляр будь-якого з цих класів є одним кінцем TCP-з’єднання.
TCP-з’єднання - це абстрактний двосторонній канал, кожен кінець якого
Арк.
ЧДТУ.21.20106.009 ПЗ 30
Змн. Арк. № докум. Підпис Дата
ідентифікується IP-адресою та номером порту. Перед використанням зв’язку
TCP-з’єднання має пройти етап налаштування, який починається з того, що TCP
клієнта надсилає запит на з’єднання TCP сервера. Примірник TcpListener
прослуховує запити TCP-з’єднання та створює новий сокет (у формі екземпляра
TcpClient або Socket) для обробки кожного вхідного з’єднання.
Клієнт TCP ініціює обмін даними із сервером, який пасивно чекає на
зв’язок. Типовий TCP-клієнт проходить три етапи:
1. Створюємо екземпляр TcpClient: TCP-з’єднання може бути створене
неявно в конструкторі, вказавши віддалений хост та порт, або явно
використовуючи метод Connect();
2. Зв’язок із використанням потоку: підключений екземпляр TcpClient
містить NetworkStream, який може використовуватися як будь-який інший потік
введення-виводу dotNET;
3. Закриваємо з’єднання: викликавши метод Close().
Коли служба імен недоступна з якоїсь причини – скажімо, програма
запущена на машині, яка не підключена до якоїсь мережі, – спроба
ідентифікувати хост на ім’я може зазнати невдачі. Більше того, це може зайняти
значний час, оскільки система намагається різні способи перетворення імені в IP-
адресу.
Якщо ви можете перевірити зв’язок з хостом, використовуючи одне з його
імен (наприклад, запустіть команду ping some.kind.of.site.com). Якщо ваш ping-
тест не пройшов, спробуйте вказати хост IP-адресою, що дозволить повністю
уникнути перетворення імені на адресу.
TcpClient надає прості методи для підключення, надсилання та отримання
даних через TCP-з’єднання. Метод GetStream() TcpClient надає доступ до
NetworkStream для відправлення та отримання даних.
Завдання сервера – налаштувати кінцеву точку для підключення клієнтів
та пасивного очікування підключень. Типовий TCP-сервер проходить два етапи:
Арк.
ЧДТУ.21.20106.009 ПЗ 31
Змн. Арк. № докум. Підпис Дата
1. Створюємо екземпляр TcpListener, вказавши локальну адресу та
порт, і викличте метод Start(). Цей сокет прослуховує вхідні з’єднання на
вказаному порту;
2. Повторюємо:
• Викликаємо метод AcceptTcpClient() TcpListener, щоб отримати
наступне вхідне клієнтське з’єднання. Після встановлення нового
клієнтського з’єднання створюється екземпляр TcpClient для нового
з’єднання, який повертається викликом AcceptTcpClient();
• Спілкуємся з клієнтом, використовуючи методи Read() та Write()
NetworkStream TcpClient;
• Закриваємо з’єднання та потік клієнтського сокету використовуючи
методи Close() NetworkStream та TcpClient.
В C# клас TcpClient використовується для доступу до TCP-з’єднання як на
клієнті, так і на сервері. Можна використовувати той же клас, тому що протокол
TCP дійсно не робить відмінностей між клієнтом та сервером, особливо після
того, як з’єднання встановлено.
TcpListener прослуховує з’єднання від клієнтів мережі TCP. Конструктор
приймає локальний інтерфейс та, можливо, локальний порт для
прослуховування. Метод Start() починає прослуховувати запити на підключення.
Методи AcceptTcpClient() та AcceptSocket() приймають вхідні з’єднання та
повертають екземпляр TcpClient або Socket, відповідно, який вже підключений
до віддаленого клієнта і готовий до відправлення та отримання даних. Метод
Stop() припиняє прослуховування з’єднань та закриває TcpListener.
Основною парадигмою введення-виведення в dotNET framework є
абстракція потоку. Потік – це просто впорядкована послідовність байтів. Потоки
dotNET підтримують як читання, і запис байтів в потік. У TCP-клієнті та сервері
кожен екземпляр TcpClient або TcpListener містить екземпляр NetworkStream.
Коли ми записуємо в потік TcpClient, байти можуть бути прочитані з потоку
TcpListener на іншому кінці з’єднання. Класи Socket та UdpClient
використовують байтові масиви замість потоків для відправлення та отримання
Арк.
ЧДТУ.21.20106.009 ПЗ 32
Змн. Арк. № докум. Підпис Дата
даних. Якщо є помилка читання або запису, NetworkStream кине виняток
IOException. NetworkStream є підкласом Stream і забезпечує основний потік
даних для введення-виводу.
UDP забезпечує наскрізну (end-to-end – від початку до кінця) службу,
відмінну від TCP. UDP виконує лише дві функції: він додає ще один рівень
адресації (порти) до IP, і він виявляє пошкодження даних, яке може статися під
час передачі, та відкидає будь-які пошкоджені повідомлення. Через цю простоту
сокети UDP мають деякі характеристики, які відрізняються від сокетів TCP.
Наприклад, перед використанням не потрібно підключати сокети UDP. Кожне
повідомлення, зване дейтаграмою, несе свою власну адресну інформацію і
залежить від решти. Відразу після створення сокет UDP можна використовувати
для послідовної відправки/отримання повідомлень на/з будь-якої адреси на/з
багатьох різних адрес.
Ще одна різниця між сокетами UDP та сокетами TCP полягає у способі
роботи з межами повідомлень: сокети UDP їх зберігають. Це певною мірою
спрощує отримання повідомлення програми порівняно з TCP-сокетами. Остання
відмінність полягає в тому, що наскрізна транспортна служба UDP забезпечує
максимальну віддачу: немає гарантії, що повідомлення, надіслане через сокет
UDP, буде доставлене до пункту призначення, повідомлення можуть бути
доставлені в іншому порядку, ніж вони були відправлені. Тому програма, яка
використовує сокети UDP, має бути готова впоратися зі втратою та
переупорядкуванням даних.
З огляду на недоліки постає питання: навіщо використовувати UDP замість
TCP? Одна з причин – ефективність. Якщо програма обмінюється лише
невеликим обсягом даних – скажімо, одне повідомлення запиту від клієнта до
сервера та одне повідомлення відповіді в іншому напрямку – фаза встановлення
з’єднання TCP як мінімум подвоює кількість повідомлень (кількість циклів
прийому-передачі службовими повідомленнями викликають затримки),
необхідних для зв’язку. Інша причина – гнучкість. Коли потрібно щось інше, ніж
Арк.
ЧДТУ.21.20106.009 ПЗ 33
Змн. Арк. № докум. Підпис Дата
надійна служба потоку байтів, UDP надає платформу з мінімальними
накладними витратами, на якій можна реалізувати все, що потрібно.
Платформа dotNET надає функціональні можливості UDP-сокетів з
використанням класу UdpClient або Socket. Клас UdpClient дозволяє як
відправляти, так і отримувати пакети UDP і може використовуватися для
створення клієнта, так і сервера UDP.
Клієнт UDP починає з відправки дейтаграми на сервер, який пасивно чекає
на зв’язок. Типовий клієнт UDP проходить три етапи:
1. Створюємо екземпляр UdpClient, необов’язково вказувати локальну
адресу та порт;
2. Спілкуємося, відправляючи та отримуючи дейтаграми (байтові
масиви) за допомогою методів Send() та Receive() UdpClient;
3. Після завершення звільнюємо сокет за допомогою методу Close()
UdpClient.
На відміну від TcpClient, UdpClient не потрібно створювати (або
підключати) з конкретною адресою призначення. Це ілюструє одну з основних
відмінностей між TCP та UDP. Сокет TCP потрібен для встановлення з’єднання
з іншим сокетом TCP на вказаному хості та порту до того, як можна буде
обмінюватися будь-якими даними, і після цього він зв’язується тільки з цим
сокетом, доки він не буде закритий. З іншого боку, UDP-сокет не потрібно
встановлення з’єднання перед встановленням зв’язку, і кожна дейтаграма може
бути надіслана та отримана з іншого місця призначення. Метод Connect()
UdpClient дозволяє вказати віддалену адресу та порт, але його використання
необов’язково. На відміну від TCP-версії Connect(), версія UDP просто
встановлює місце призначення за умовчанням і фактично не викликає
надсилання повідомлень про налаштування з’єднання по мережі.
Одним із наслідків використання UDP є втрата дейтаграм. У разі нашого
запиту від клієнта, або відповідь від сервера можуть бути втрачені в мережі, і
якщо TCP-клієнт відправляє запит, а потім блокується за допомогою Read(),
чекаючи відповіді, а дейтаграма запиту буде втрачена, наш клієнт назавжди
Арк.
ЧДТУ.21.20106.009 ПЗ 34
Змн. Арк. № докум. Підпис Дата
заблокується на прийомі Receive(). Щоб уникнути цієї проблеми, наш клієнт
може встановити тайм-аут для блокування Receive().
Як і TCP-сервер, UDP-сервер встановлює кінцеву точку зв’язку та пасивно
очікує, доки клієнт ініціює зв’язок; однак, оскільки UDP не вимагає
встановлення з’єднання, зв’язок UDP ініціюється дейтаграмою від клієнта, без
налаштування з’єднання, як у TCP. Це означає, що отримання дейтаграми
сервером насправді нічим не відрізняється від отримання датаграми клієнтом.
В результаті замість окремих класів для клієнта та сервера UDP
використовується клас UdpClient для реалізації сервера і клієнта.
Типовий UDP-сервер проходить чотири етапи:
1. Створюємо екземпляр UdpClient, вказавши локальний порт. Тепер
сервер готовий отримувати дейтаграми від будь-якого клієнта;
2. Отримавши пакет за допомогою методу Receive() UdpClient. Метод
Receive() приймає як аргумент посилання на екземпляр IPEndPoint, а коли виклик
повертає клієнту дані, IPEndPoint містить адресу клієнта і тому програма знає
куди надіслати відповідь;
3. Спілкуємся, надсилаючи та отримуючи пакети дейтаграм за
допомогою методів Send() та Receive() UdpClient;
4. Після завершення звільнюємо сокет використовуючи метод Close()
UdpClient.
Різниця між TCP та UDP полягає в тому, що UDP зберігає повідомлення
надіслані клієнтом. Кожен виклик UdpClient.Receive() повертає дані максимум з
одного виклику UdpClient.Send(). Більше того, різні виклики UdpClient.Receive()
ніколи не повертатимуть дані з одного і того ж виклику UdpClient.Send().
Однак UDP не забезпечує відновлення після мережевих помилок і, отже,
не буферизує дані для можливої повторної передачі. Це означає, що до моменту
повернення виклику Send() повідомлення вже було передано в нижчележачий
канал для передачі і вже/або скоро буде на шляху до точки виходу.
Між моментом надходження повідомлення з мережі та моменту, коли його
дані повертаються через Read() або Receive(), дані зберігаються у черзі
Арк.
ЧДТУ.21.20106.009 ПЗ 35
Змн. Арк. № докум. Підпис Дата
отриманих даних у порядку черги (FIFO). При підключеному TCP-сокеті всі
отримані, але не доставлені байти розглядаються як одна безперервна
послідовність байтів. Однак для сокету UDP отримані дані могли надходити від
різних відправників. Отримані дані сокету UDP зберігаються у черзі
повідомлень, кожне зі зв’язаною інформацією, що ідентифікує його джерело.
Виклик методу Receive() ніколи не поверне більше одного повідомлення.
Максимальний обсяг даних, який може бути переданий UdpClient – 65507 байт
для IPv4 і 65527 байт для IPv6 – це найбільше корисне навантаження, яке може
бути перенесене в дейтаграмі UDP.
Оскільки TcpClient, TcpListener та UdpClient використовують клас Socket
для своїх власних реалізацій, Socket містить усі функції цих класів, а також
багато іншого.
Клас Socket – це оболонка API сокетів WinSock. Використання сокету
включає такі кроки:
1. Створіть екземпляр Socket за допомогою конструктора сокету;
2. Якщо Socket є сервером, викличте Bind(), щоб призначити локальну
кінцеву точку;
3. Якщо Socket є клієнтом, викличте Connect() для підключення до
віддаленої кінцевої точки;
4. Якщо Socket є сервером, викличте Listen(), щоб почати
прослуховування з’єднань, та викличте Accept(), щоб отримати вхідне з’єднання;
5. Використовуйте методи Send() та Receive() для передачі даних по
TCP або SendTo() та ReceiveFrom() для UDP;
6. Викличте Shutdown(), щоб вимкнути сокет;
7. Викличте Close(), щоб закрити сокет.
Розробники протоколу TCP/IP задали поведінку за умовчанням, яка
задовольнить більшість програм. Проте рідко буває так, що стандартна
реалізація дійсно підходить всім. Саме для таких ситуацій сокети дозволяють
змінювати багато зі своїх поведінок за умовчанням, і ці поведінки називаються
параметрами сокетів. У dotNET рівень доступу до параметрів сокету
Арк.
ЧДТУ.21.20106.009 ПЗ 36
Змн. Арк. № докум. Підпис Дата
визначається класом, що використовується. Клас TcpClient має такі параметри
сокету:
• LingerState – отримує або визначає інформацію про час очікування
сокетів;
• NoDelay – повертає або задає значення, яке вимикає затримку, коли
буфери надсилання або отримання не заповнені;
• ReceiveBufferSize – отримує або встановлює розмір приймального
буфера;
• ReceiveTimeout – повертає або задає час, протягом якого TcpClient
буде очікувати на отримання даних після запуску операції читання;
• SendBufferSize – отримує або задає розмір буфера надсилання;
• SendTimeout – повертає або задає час, протягом якого TcpClient буде
очікувати на успішне завершення операції відправки.
Для доступу до всіх доступних параметрів сокетів необхідно
використовувати клас Socket. Методи GetSocketOption() та SetSocketOption()
класу Socket надають можливості отримання та встановлення опції. Ці методи
перевантажені, щоб відповідати типу даних різних параметрів, але у всіх
випадках вони приймають ім’я параметра сокета і рівень параметра сокета. Ім’я
параметра сокета – це ім’я параметра, який потрібно отримати/встановити, та
його допустимі значення вказані у класі SocketOptionX
(SocketOptionLevel.Socket, SocketOptionLevel.Tcp, SocketOptionLevel.Udp,
SocketOptionLevel.IP). Рівень параметра сокета – це обсяг параметра, який
потрібно отримати/встановити, наприклад, рівень сокету, рівень TCP чи рівень
IP.
SocketFlags надає кілька додаткових способів змінити поведінку за
замовчуванням окремих викликів Send()/SendTo() та Receive()/ReceiveFrom().
Щоб використовувати прапори сокетів, відповідне перерахування прапорів
просто передається як аргумент методу відправлення або отримання.
Перелік SocketFlags надає допустимі значення для розширених прапорів
сокетів та є необов'язковим входом для методів передачі даних Socket:
Арк.
ЧДТУ.21.20106.009 ПЗ 37
Змн. Арк. № докум. Підпис Дата
• DontRoute – відправка без використання таблиць маршрутизації;
• MaxIOVectorLength – надає стандартне значення для кількості
структур WSABUF, що використовуються для надсилання та отримання даних;
• None – не використовуйте для цього дзвінка прапори;
• OutOfBand – обробка даних поза каналом;
• Partial – часткове надсилання або отримання повідомлення;
• Peek – Перегляд вхідного повідомлення.
Peek дозволяє переглядати вміст наступного методу
Receive()/ReceiveFrom() без фактичного видалення результатів із буфера
мережної системи. Це означає, що ви можете створити копію вмісту наступного
читання, але наступне читання знову поверне ті ж байти. Фактично, якщо з
перегляду по мережі було отримано більше байтів, наступне читання може
повернути більше даних, ніж перегляд. Справа в тому, що на відміну від читання,
повернені байти не були видалені з буфера і все ще доступні для повторного
читання. Теоретично це можна використовувати для перевірки вмісту
наступного читання та виконання програмою рішення про те, що робити, на
основі цих попередніх знань. Фрагмент коду, щоб проілюструвати використання
SocketFlags:
1) Перегляньте дані, не видаляючи їх з мережевого буфера:
int len = s.Receive(buf, 0, buf.Length, SocketFlags.Peek);.
2) Отримання, але цього разу буде видалено з мережевого буфера:
int len = s.Receive(buf, 0, buf.Length, SocketFlags.None);.
Висновки до розділу 1
У цьому розділі наведено огляд внутрішньої роботи інтернету. Комутація
пакетів дозволяє одночасно передавати кілька передач по одній і тій самій лінії,
що призводить до появи ARPANET і, зрештою, Інтернету. Пакет TCP/IP, на
якому працює Інтернет, складається з п’яти рівнів, кожен з яких забезпечує канал
даних для вищого рівня.
Арк.
ЧДТУ.21.20106.009 ПЗ 38
Змн. Арк. № докум. Підпис Дата
Транспортний рівень та його порти забезпечують наскрізний зв’язок між
процесами на віддалених хостах. TCP та UDP є основними протоколами на
транспортному рівні і принципово відрізняються один від одного: UDP є легким,
не вимагає встановлення з’єднання та ненадійним, тоді як TCP має більш важку
площу, вимагає з’єднань з відстеженням стану та гарантує надійну та
впорядковану доставку всіх даних. TCP реалізує механізми керування потоком
та перевантаженням, щоб зменшити втрату пакетів. Розглянуто теоретичну
основу роботи сокетів на основі TCP і UDP. У процесі обміну, як правило,
використовується два сокети – сокет відправника та сокет одержувача. Кожен
процес може слухати сокет (серверний сокет) і прив’язати його до якого-небудь
порту операційної системи (процеси не можуть використовувати порти менше
1024). Слухаючий процес зазвичай перебуває в циклі очікування, тобто
прокидається з появою нового з’єднання. При цьому зберігається можливість
перевірити наявність з’єднань на даний момент, встановити тайм-аут для
операції тощо.
Арк.
ЧДТУ.21.20106.009 ПЗ 39
Змн. Арк. № докум. Підпис Дата
РОЗДІЛ 2
ОСНОВИ ПЕРЕДАЧІ ДАНИХ
2.1 Надсилання та отримання даних
При написанні програм для зв’язку зазвичай використовують сокети, які
реалізують який-небудь протокол передачі даних, тому що одна програма
повинна надавати інформацію або використовувати інформацію, надану іншою
програмою. Відправник і одержувач повинні домовитися про те, як ця
інформація буде закодована, хто і коли яку інформацію надсилатиме і як буде
припинено спілкування. Оскільки для більшості програм потрібно, щоб
поведінка клієнта та сервера залежала від інформації, якою вони обмінюються,
протоколи програм зазвичай складні.
Протоколи TCP/IP передають байти даних, не досліджуючи і не змінюючи
їх. Це дає програмам більшу гнучкість у тому, як вони кодують свою інформацію
для передачі. З різних причин більшість прикладних протоколів визначено у
виразах дискретних повідомлень, складених із послідовностей полів. Кожне поле
містить певний фрагмент інформації, закодований як послідовності бітів.
Протокол програми точно визначає, як ці послідовності бітів повинні бути
відформатовані відправником та інтерпретовані або проаналізовані
одержувачем, щоб останній міг отримати значення кожного поля. Єдине
обмеження, що накладається TCP/IP, полягає в тому, що інформація повинна
відправлятися та прийматися порціями, довжина яких у бітах кратна восьми
(рахуємо повідомлення послідовностями байтів). Враховуючи це, може бути
корисно думати про повідомлення, що передається, як про послідовність чисел,
кожне від 0 до 255 включно (це діапазон двійкових значень, які можуть бути
закодовані в 8 бітах – 1 байт) [2, 3].
Що, якщо клієнтській програмі необхідно отримати деяку інформацію від
іншої програми? Дві програми повинні домовитися про те, як інформація буде
Арк.
ЧДТУ.21.20106.009 ПЗ 40
Змн. Арк. № докум. Підпис Дата
представлена, як послідовність байтів – відправлених по TCP-з’єднанню або
переданих у дейтаграмі UDP.
Для передачі інформації по мережі в платформі dotNET необхідно, щоб
вона була записана NetworkStream (TcpClient або TcpListener) або записана в
масиві байтів в Socket або UdpClient. Це означає, що єдині типи даних, яких
можуть застосовуватися ці операції – це байти і масиви байтів. Як строго
типізована мова C# вимагає явного перетворення типів – string, int та інших
типів, що передаються. На щастя, у мові C# є механізм який робить такі
перетворення зручнішими [4].
2.2 Кодування інформації
Текст – рядки друкованих (відображуваних) символів – це, мабуть,
найпоширеніша форма подання інформації. Коли інформація, що передається, є
природною мовою, текст є найбільш природним уявленням. Текст зручний для
інших форм інформації, тому що люди можуть легко працювати з ним під час
друку або відображення; числа, наприклад, можуть бути подані як рядки
десяткових цифр.
Для надсилання тексту рядок символів перетворюється на послідовність
байтів відповідно до набору символів. Прикладом системи кодування символів є
американський стандартний код для обміну інформацією (ASCII – American
Standard Code for Information Interchange), який визначає взаємно-однозначну
відповідність між набором друкованих символів, що найчастіше
використовуються, в англійській мові і двійковими значеннями. Однак в еру
глобалізації, додаткам необхідно мати справу з іншими мовами, які
використовують символи, для яких ASCII не має кодування. Кодування для мов
світу визначаються компаніями та органами зі стандартизації. Юнікод –
найбільш широко визнане таке кодування символів; він стандартизований
Міжнародною організацією зі стандартизації (ІSO – International Organization for
Standardization).
Арк.
ЧДТУ.21.20106.009 ПЗ 41
Змн. Арк. № докум. Підпис Дата
dotNET надає класи, які можна використовувати для кодування тексту в
ASCII, Unicode. Стандартний Unicode визначає 16-бітовий (2-байтовий) код
кожного символу і, таким чином, підтримує набагато більший набір символів,
ніж ASCII. Кодування за замовчуванням для C# може відрізнятися залежно від
регіональних налаштувань операційної системи, але це UTF-8, який підтримує
весь набір символів Unicode. (UTF-8, кодує символи в 8 бітах, коли це можливо
для економії місця, використовуючи 16 біт тільки за потреби) На кодування за
умовчанням посилаються через System.Text.Encoding.Default.
Класи кодування System.Text пропонують кілька механізмів для
перетворення між різними наборами символів. Всі класи ASCIIEncoding,
UnicodeEncoding, UTF7Encoding та UTF8Encoding надають методи GetBytes() та
GetString() для перетворення з String у масив байтів або навпаки у зазначеному
кодуванні. Клас Encoding також містить статичні версії деяких класів набору
символів (ASCII і Unicode), які містять такі самі методи. Метод GetBytes()
повертає послідовність байтів, що представляють заданий рядок у кодуванні
використовуваного класу. Так само метод кодування класів GetString() приймає
байтовий масив і повертає екземпляр String, що містить послідовність символів,
представлену байтовою послідовністю відповідно до викликаного класу
кодування. Очевидно, що основною вимогою при роботі з кодуванням символів
є те, що відправник та одержувач повинні узгодити код, який
використовуватиметься [5].
Передача великих чисел як текстових рядків не дуже ефективна. Кожен
символ у рядку цифр має одне із 10 значень, які можна представити з
використанням у середньому менше 4 біт на цифру. Проте стандартні коди
символів незмінно використовують щонайменше 8 біт на символ. Крім того,
незручно виконувати арифметичні обчислення та порівняння з числами,
закодованими у вигляді рядків.
Для більш компактного та зручного для обчислень кодування ми можемо
передавати значення цілих чисел наших даних у вигляді двійкових значень. Щоб
Арк.
ЧДТУ.21.20106.009 ПЗ 42
Змн. Арк. № докум. Підпис Дата
відправляти двійкові цілі числа у вигляді байтових послідовностей, відправник
та одержувач повинні узгодити кілька речей:
Розмір цілих чисел: скільки бітів використовується для представлення
цілого числа? Розміри цілочисленних типів C# фіксуються визначенням мови –
short – 2 байти, int – 4, long – 8, тому відправнику та одержувачу C# потрібно
лише узгодити використовуваний примітивний тип (взаємодія з додатком,
відмінним від C#, може бути складнішим). Розмір цілочисельного типу разом з
кодуванням (знаковий/беззнаковий) визначає максимальні та мінімальні
значення, які можуть бути представлені з використанням цього типу.
Порядок байтів: чи записуються байти двійкового подання в потік (або
поміщаються в масив байтів) зліва направо чи справа наліво? Якщо старший байт
передається першим, а молодший байт передається останнім, це званий порядок
від старшого до молодшого (big-endian order). Порядок від молодшого до
старшого (little-endian order), звичайно, якраз навпаки.
Більшість мережевих протоколів визначають порядок байтів із прямим
порядком байтів. Однак, якщо ви взаємодієте з програмою, яка використовує
іншу апаратну архітектуру або написана іншою мовою, порядок байтів може
стати проблемою. Тому завжди корисно перетворювати вихідні багатобайтові
двійкові числа в прямий порядок байтів, а вхідні багатобайтові двійкові числа з
прямого порядку байтів. Ця можливість перетворення забезпечується як
статичними методами класу IPAddress NetworkToHostOrder() та
HostToNetworkOrder(), так і параметрами конструктора у класі UnicodeEncoding.
Як і рядки, dotNET надає механізми для перетворення примітивних цілих
типів у послідовності байтів і навпаки. Зокрема, клас BinaryWriter має метод
Write(), який перевантажений, щоб приймати аргументи різних типів,
включаючи short, int та long. Ці методи дозволяють записувати ці типи
безпосередньо двома порядками байтів (big-endian order/ little-endian order)
(необхідно вказати явне кодування в конструкторі BinaryWriter або викликати
методи в ручному режимі). Так само клас BinaryReader має методи ReadInt32()
(для int), ReadInt16() (для short) та ReadInt64() (для long).
Арк.
ЧДТУ.21.20106.009 ПЗ 43
Змн. Арк. № докум. Підпис Дата
2.3 Складання потоків введення/виведення
Для роботи з бінарними файлами призначена пара класів BinaryWriter та
BinaryReader. Ці класи дозволяють читати та записувати дані у двійковому
форматі.
Класи потоків платформи dotNET можуть бути складені для забезпечення
потужних засобів кодування та декодування. Наприклад, ми можемо обернути
NetworkStream екземпляра TcpClient в екземпляр BufferedStream, щоб підвищити
продуктивність, тимчасово буферизуючи байти і одночасно скидаючи їх у
базовий канал. Потім ми можемо обернути цей екземпляр у BinaryWriter для
надсилання примітивних типів даних [6, 7].
TcpClient serverPoint = new TcpClient(serverIP, port);
BinaryWriter out = new BinaryWriter(new
BufferedStream(serverPoint.GetStream()));
Ми записуємо примітивні значення (double, int, short) даних одне за одним
у BinaryWriter, який записує двійкові дані в BufferedStream, який буферизує дані
з трьох записів (double, int, short), а потім записує один раз у сокет
NetworkStream, який керує записом у мережу. Потім створюємо ідентичну
композицію з BinaryReader на іншій кінцевій точці для ефективного прийому
примітивних типів даних. I/O Class:
• BufferedStream – виконує буферизацію для оптимізації
введення/виведення;
• BinaryReader/BinaryWriter – обробляє читання/запис для
примітивних типів даних;
• MemoryStream – створює потоки, які мають пам’ять, як резервне
сховище, може бути використана в місці тимчасових буферів та файлів;
• Stream – абстрактний клас всіх потоків;
• StreamReader/StreamWriter – читання та запис введення/виведення
символів у/з потоку у зазначеному кодуванні;
Арк.
ЧДТУ.21.20106.009 ПЗ 44
Змн. Арк. № докум. Підпис Дата
• StringReader/StringWriter – завантажити та записати
введення/виведення символів у/з рядка у зазначеному кодуванні;
• TextReader/TextWriter – Абстрактний базовий клас для читання та
запису введення/виведення символів. Базовий клас StreamReader/Writer та
StringReader/Writer.
2.4 Фреймінг та парсинг
Перетворення даних у провідний формат – це, звісно, лише половина
справи; вихідна інформація має бути відновлена у одержувача із надісланої
послідовності байтів. Протоколи програм зазвичай мають справу з дискретними
повідомленнями, які розглядаються як набори полів.
Фреймінг відноситься до проблеми, що дозволяє одержувачу визначати
початок і кінець повідомлення в потоці та поля всередині повідомлення.
Незалежно від того, чи закодована інформація як текст, як багато-байтові
двійкові числа або як деяка їх комбінація, протокол програми повинен дозволяти
одержувачу повідомлення визначати, коли він отримав усе повідомлення, та
аналізувати його в поля [8, 9].
Якщо всі поля в повідомленні мають фіксований розмір, а повідомлення
складається з фіксованого числа полів, розмір повідомлення відомий заздалегідь,
і одержувач може просто прочитати очікувану кількість байтів в буфер byte[].
Однак, коли якесь поле (і / або всі повідомлення) може відрізнятися за довжиною,
ми не знаємо заздалегідь, скільки байтів потрібно прочитати.
Позначити кінець повідомлення легко в особливому випадку останнього
повідомлення, що надсилається по TCP-з’єднанню: відправник просто закриває
сторону з’єднання (використовуючи Shutdown(SocketShutdown.Send) – метод
Shutdown() доступний лише у dotNET у класі Socket) або Close ()) після
відправлення повідомлення. Після того, як одержувач прочитає останній байт
повідомлення, він отримує індикацію кінця потоку (тобто Read() повертає 0). Той
Арк.
ЧДТУ.21.20106.009 ПЗ 45
Змн. Арк. № докум. Підпис Дата
самий принцип застосовується до останнього поля у повідомленні, надісланому
пакетом дейтаграми UDP.
У всіх інших випадках саме повідомлення має містити додаткову
інформацію про кадри, що дозволяє одержувачу проаналізувати
поле/повідомлення. Ця інформація зазвичай набуває однієї з наступних форм:
1. Розділювач: кінець поля або повідомлення змінної довжини
позначається унікальним маркером, явною послідовністю байтів, яка слідує
безпосередньо за даними, але не зустрічається в них.
2. Явна довжина: поля або повідомлення змінної довжини передує поле
довжини (фіксованого розміру), яке повідомляє, скільки байтів воно містить.
Підхід на основі роздільників часто використовується з текстом змінної
довжини, позначення кінця рядка визначає конкретний символ або послідовність
символів. Якщо все повідомлення складається з тексту, його просто читати в
символах, використовуючи екземпляр TextReader (який обробляє перетворення
від байтів до символів), шукаючи послідовність роздільників і повертаючи рядок
символів, що передує їм. Якщо кінець потоку зустрічається до того, як буде
прочитано будь-які дані, повертається null.
Платформа dotNET включає можливості серіалізації: простору імен
System.Xml.Serializable і System.Runtime.Serialization.Formatters містять класи,
які підтримують запис екземпляра класу C# у файл XML (eXtensible Markup
Language – розширювана мова розмітки), двійковий формат. Потрапивши на
віддалений хост файл може бути десеріалізований в екземпляр цього об’єкта. Так
само простір імен System.Runtime.Remoting дозволяє створювати віддалений
проксі-об’єкт, який клієнт може використовувати для виклику методів об’єкта
сервера. Може здатися, що наявність цих інтерфейсів усуне необхідність у
раніше описаних методах передачі даних, однак це не завжди так із кількох
причин [10, 11].
По-перше, закодовані форми, створені Serializable, можуть бути не дуже
ефективними. Вони можуть включати інформацію, яка не має сенсу поза
контекстом Common Language Runtime (CLR), а також можуть нести накладні
Арк.
ЧДТУ.21.20106.009 ПЗ 46
Змн. Арк. № докум. Підпис Дата
витрати для забезпечення гнучкості, яка може не знадобитися. По-друге,
Serializable та Remoting не можна використовувати, якщо вже заданий інший
формат підключення, наприклад, стандартизований протокол. І, нарешті, класи,
розроблені на індивідуальне замовлення, у будь-якому разі повинні надавати свої
власні реалізації інтерфейсів серіалізації.
Основний принцип хорошого проектування протоколу полягає в тому, що
протокол повинен якнайменше обмежувати розробника і зводити до мінімуму
припущення про платформу, на якій буде реалізовано протокол. Тому варто
уникати використання Serializable та Remoting, а натомість використовувати
більш прямі методи кодування та декодування.
2.5 Блокування сокету
Виклики введення-виведення сокету можуть блокуватися з кількох
причин. Методи введення даних блокуються Read(), Receive() та ReceiveFrom(),
якщо дані недоступні. Методи виведення даних Write(), Send() або SendTo()
можуть блокуватися, якщо недостатньо місця для буферизації переданих даних.
Усі методи Accept(), AcceptSocket() та AcceptTcpClient() класів Socket та
TcpListener блокуються доти, доки з’єднання не буде встановлено. Тривалий час
прийому-передачі, з’єднання з високою частотою помилок і повільне (або
вийшли з ладу) сервери можуть призвести до того, що встановлення з’єднання
займе багато часу. У всіх цих випадках метод повертається лише після того, як
запит був задоволений. Звичайно, виклик методу блокування зупиняє виконання
програми. І це – не розглядаючи можливість наявності глючної чи шкідливої
програми на іншому кінці з’єднання [12]!
Як щодо програми, яка повинна виконувати інші завдання в очікуванні
завершення дзвінка? У цих програм може не бути часу чекати виклику
заблокованого методу. На щастя, є кілька механізмів, що дозволяють уникнути
небажаної поведінки блокування.
Арк.
ЧДТУ.21.20106.009 ПЗ 47
Змн. Арк. № докум. Підпис Дата
Один із способів уникнути блокуючої поведінки – не здійснювати
блокуючі виклики. Як цього досягти? Для деяких викликів введення-виводу, які
можуть бути заблоковані, ми можемо попередньо перевірити стан введення-
виводу, щоб побачити, чи буде він заблокований. Якщо попередня перевірка
показує, що дзвінок не буде заблокований, ми можемо продовжити дзвінок,
знаючи, що операція завершиться негайно. Якщо попередня перевірка вказує на
те, що дзвінок буде заблоковано, тоді може бути виконана інша обробка, і ще
одна перевірка може бути виконана пізніше [13, 14].
При читанні даних за допомогою TcpClient цього можна досягти,
перевіривши властивість DataAvailable пов’язаного NetworkStream, яке повертає
true, якщо є дані для читання, і false, якщо їх немає.
TcpListener може попередньо перевірити наявність очікуваних з’єднань
перед викликом AcceptTcpClient() або AcceptSocket() за допомогою методу
Pending(). Pending() повертає true, якщо є підключення, що очікують, і false, якщо
їх немає.
За допомогою класу Socket доступність даних для читання можна
попередньо перевірити за допомогою властивості Available, яка має тип int.
Available завжди містить кількість байтів, отриманих із мережі, але ще не
прочитаних; таким чином, якщо Available більше за нуль, операція читання не
блокується.
Якщо дейтаграма не отримана до закінчення таймера, ReceiveFrom()
розблокується, щоб дозволити клієнту обробити втрату дейтаграми.
Використовуючи параметри сокету, клас Socket підтримує встановлення
обмеження максимального часу (у мілісекундах) для блокування під час
надсилання або отримання даних за допомогою властивостей
SocketOption.SendTimeout та SocketOption.ReceiveTimeout.
socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.SendTimeout, 5000); // Set a 5 second timeout on Send()/SendTo()
Якщо ви використовуєте клас TcpClient, він містить властивості
SendTimeout та ReceiveTimeout, які можна встановити чи отримати.
Арк.
ЧДТУ.21.20106.009 ПЗ 48
Змн. Арк. № докум. Підпис Дата
tcpClient.ReceiveTimeout = 7000; // Set a 7 second timeout on Read()
В обох випадках, якщо вказаний час закінчується до повернення з методу,
генерується виключення Socket-Exception із властивістю ErrorCode Socket,
встановленим на 10060 (час очікування з’єднання закінчився).
Метод Poll() класу Socket пропонує більше функціональних можливостей.
Poll() приймає два варіанти: ціле число мікросекунд (не мілісекунд) для
очікування відповіді та режим, що вказує, який тип операції ми очікуємо. Час
очікування може бути негативним, що вказує на невизначений час очікування
(переважно блок). Час очікування може бути нульовим, що дозволяє
використовувати Poll() для попередньої перевірки. Для режиму встановлено одне
із значень перерахування SelectMode SelectRead, SelectWrite або SelectError,
залежно від того, що ми перевіряємо. Poll() повертає true, якщо сокет очікує на
операцію для запитаного режиму, або false, якщо її немає [15].
// Block for 3 second waiting for data to read or incoming connections
if (socket.Poll(3000000, SelectMode.SelectRead)) {
// Socket has data to read or an incoming connection
} else {
// No data to read or incoming connections
}
Загалом опитування вважається не самим ефективним, оскільки потребує
повторних викликів для перевірки статусу, тому що воно включає безперервний
цикл для перевірки подій, які, ймовірно, відбуваються нечасто (принаймні,
залежно від кількості виконаних перевірок).
Виклик Write() або Send() блокується доти, поки останній записаний байт
не буде скопійовано в локальний буфер реалізації TCP. Таким чином, кількість
часу блокування для відправлення великих даних, контролюється додатком, що
приймає. Отже, будь-який протокол, який надсилає досить великий обсяг даних
через екземпляр сокету, може блокуватися на необмежений час.
Встановлення з’єднання Socket із зазначеним хостом та портом буде
заблоковано доти, доки з’єднання не буде встановлено, з’єднання не буде
Арк.
ЧДТУ.21.20106.009 ПЗ 49
Змн. Арк. № докум. Підпис Дата
відхилено або не закінчиться встановлений системою тайм-аут. Встановлений
системою тайм-аут довгий (близько хвилин).
Програми досі мали справу із введенням-виводом по одному каналу, до
цього сервера працювали тільки з одним клієнтським підключенням за раз.
Однак часто буває, що додатку потрібна можливість виконувати введення-
виведення на кількох каналах одночасно. Наприклад, надавати дані відразу на
декількох портах. Проблема з цим стає очевидною, що після того, як сервер
створює та пов’язує сокет із кожним портом. Все готово до підключення
Accept(), але який вибрати сокет? Виклик Accept() або Receive() в одному сокеті
може блокуватися, внаслідок чого встановлені з’єднання з іншим сокетом без
потреби чекають. Цю проблему можна вирішити за допомогою неблокуючих
сокетів, але в цьому випадку сервер постійно опитує сокети. Було б добре
дозволити серверу заблокуватися, поки якийсь сокет не буде готовий для
введення-виведення [16].
API сокетів надає спосіб зробити це. За допомогою статичного методу
Socket Select() програма може вказати список сокетів для перевірки тих, хто
очікує введення-виведення; Select() призупиняє виконання програми доти, доки
один або кілька сокетів у списку не будуть готові до виконання вводу-виводу. До
списку внесені лише екземпляри Socket, які готові.
Select() приймає чотири аргументи, перші три з яких є списками сокетів, а
четвертий – час у мікросекундах (не мілісекундах), що вказує скільки часу
чекати. Негативне значення часу очікування вказує на невизначений період
очікування. Списки сокетів можуть бути будь-яким класом, що реалізує
інтерфейс IList. По порядку вони представляють сокети з готовністю до
зчитування, готовністю до запису та наявністю помилок. Списки мають бути
заповнені посиланнями на екземпляри Socket перед викликом. Коли дзвінок
завершиться, списки міститимуть лише посилання на сокети, які відповідають
критеріям цього списку (готовністю до зчитування, можливість запису або
наявність помилок). Якщо ви не хочете перевіряти всі ці умови за один дзвінок
Select(), ви можете передати null максимум для двох списків.
Арк.
ЧДТУ.21.20106.009 ПЗ 50
Змн. Арк. № докум. Підпис Дата
Socket.Select(acceptSocketList, null, null, 300000);
Неблокуючий підхід має дві основні недоліки. По-перше, опитування для
завершення методів сокету досить неефективне. Якщо не провести опитування
досить швидко, час буде втрачено після завершення операції сокету. Якщо
провести опитування зарано, операція не буде готова, і доведеться або
заблокувати, або повторити спробу пізніше.
По-друге, кількість підключень, які можуть опрацьовуватися одночасно,
обмежена. Якщо клієнт підключається, коли інший вже обслуговується, сервер
не відображатиме дані нового клієнта доти, доки він не закінчить роботу з
поточним клієнтом, хоча новий клієнт зможе надсилати дані, як тільки він
підключиться. Цей тип сервера відомий як ітеративний сервер. Ітераційні
сервери обробляють клієнтів послідовно, закінчуючи обслуговування одного
клієнта перед обслуговуванням наступного. Вони найкраще підходять для
програм, де кожному клієнту потрібен невеликий обмежений час з’єднання з
сервером; проте, якщо час обробки клієнта може бути тривалим, очікування, яке
відчувають наступні клієнти, може бути неприйнятним.
Якщо додати 8-секундний сон за допомогою Thread.Sleep(8000) після
виклику TcpClient з’єднання (connect call) та підлючитись кількома клієнтами,
які одночасно звертаються до сервера TCP. Операція сну імітує операцію, яка
займає значний час, наприклад, повільне введення-виведення файлу або мережі.
Варто звернуть увагу на те, що новий клієнт повинен дочекатися завершення
роботи всіх вже підключених клієнтів, перш ніж він отримає обслуговування.
Що нам потрібно, то це якийсь спосіб для кожного з’єднання працювати
незалежно, не заважаючи іншим з’єднанням. Ось тут і з’являється реалізація
потоків. Це дозволяє одному «потоку виконання» блокувати операцію, тоді як
інший потік продовжує виконуватись.
Можливості потокової передачі dotNET (System.Threading) дуже гнучкі та
дозволяють програмі обробляти безліч мережевих підключень одночасно.
Використовуючи потоки, одна програма може працювати над кількома
завданнями одночасно. На сервері ми можемо покласти відповідальність за
Арк.
ЧДТУ.21.20106.009 ПЗ 51
Змн. Арк. № докум. Підпис Дата
кожного клієнта на незалежно потік, що виконується. Існує два підходи до
кодування паралельних серверів, а саме потік для кожного клієнта, де
створюється новий потік для обробки кожного клієнтського з’єднання, і пул
потоків, де фіксований, попередньо створений набір потоків працює разом для
обробки клієнтських підключень.
Щоб створити новий потік у C#, потрібно створити новий екземпляр класу
Thread, який як аргумент приймає метод делегата, який працюватиме у своєму
власному потоці. Цей делегат потоку представлений класом ThreadStart, який
приймає як аргумент метод, що запускається. Після створення екземпляра
потоку викликається метод Start(), щоб розпочати виконання цього потоку.
Наприклад, якщо ви створили метод з ім’ям MyMethod(), код для створення та
запуску коду, що працює як власний потік, буде наступним:
using System.Threading;
…
public static void MyMethod()
{…}
…
// Create a ThreadStart instance using your method as a delegate:
ThreadStart methodDelegate = new ThreadStart(MyMethod);
…
// Create a Thread instance using your delegate method:
Thread thread = new Thread(methodDelegate);
…
// Start the thread
thread.Start();
Новий потік не починає виконання, доки не буде викликаний його метод
Start(). Коли викликається метод Start() екземпляра Thread, середовище CLR
викликає виконання зазначеного методу новому потоці одночасно з іншими. Тим
часом вихідний потік повертається зі свого виклику Start() і продовжує
Арк.
ЧДТУ.21.20106.009 ПЗ 52
Змн. Арк. № докум. Підпис Дата
виконання незалежно. Точне чергування виконання потоку визначається
кількома факторами, включаючи реалізацію CLR, навантаження, базова ОС і
конфігурація хоста. Наприклад, в однопроцесорній системі потоки спільно
використовують процесор послідовно; у багатопроцесорній системі кілька
потоків однієї програми можуть виконуватися одночасно різних процесорах.
Делегат методу не може приймати аргументи або повертати значення. На
щастя, є механізми, що дозволяють обійти обидва ці обмеження (приклад коду –
Рис. 2.1, 2.2) [1].
Рис. 2.1 Приклад обходу обмежень
Арк.
ЧДТУ.21.20106.009 ПЗ 53
Змн. Арк. № докум. Підпис Дата
Рис. 2.2 Приклад обходу обмежень
Наприклад, припустимо, що ви хочете передати екземпляр TcpClient у свій
метод MyThread_fn(). Ви можете створити новий клас (наприклад,
MyThreadClass), що містить метод MyThread_fn(), і передати екземпляр
TcpClient у конструктор класу. Потім, використовуючи потік для запуску методу
MyThread_fn(), можна отримати доступ до екземпляра TcpClient через локальну
змінну.
Продемостровано підхід до передачі даних у потік на простому прикладі,
який запускає метод екземпляра MyThreadClass класу у своєму власному потоці.
Метод багаторазово виводить вітання у вихідний потік системи. Рядкове
привітання передається як параметр конструктору класу, де воно зберігається як
змінна екземпляра класу.
2.6 Мережеві топології
Поява можливості обміну даними між віддаленими комп’ютерами стало
основою для грядущих мережевих програм, в тому числі і комп’ютерних ігор.
Але при взаємодії між гравцями виникають важливі питання. Як гравці
надсилатимуть один одному оновлення гри? Чи буде реплікація об’єктів, чи
реплікуватиметься лише стан введення? (Реплікація (англ. replication) – механізм
синхронізації вмісту кількох копій об'єкта. Реплікація – це процес, під яким
Арк.
ЧДТУ.21.20106.009 ПЗ 54
Змн. Арк. № докум. Підпис Дата
розуміється копіювання даних з одного джерела на інше (або на безліч інших) і
навпаки) Що станеться, якщо комп’ютери не погодяться зі станом гри?
Топологія мережі визначає, як комп’ютери у мережі пов’язані один з
одним. У контексті гри топологія визначає, як комп’ютери, які беруть участь у
грі, будуть організовані, щоб усі гравці могли бачити актуальну версію стану гри.
Як і у разі вибору мережного протоколу, існують компроміси незалежно від
обраної топології. У цьому розділі досліджуються два основних типи топологій,
що використовуються в іграх, клієнт-серверна та однорангова, а також невеликі
варіації, які можуть існувати в цих типах.
У топології клієнт-сервер один екземпляр гри призначається сервером, а
інші екземпляри гри призначаються як клієнти. Кожен клієнт завжди спілкується
лише з сервером, тоді як сервер відповідає за зв’язок із усіма клієнтами.
У топології клієнт-сервер для даних n клієнтів існує лише O(2n) з’єднань.
Однак він асиметричний у тому сенсі, що сервер має O(n) підключень (по одному
до кожного клієнта), у той час як у кожного клієнта буде тільки одне
підключення до сервера. З точки зору пропускної спроможності, якщо є n
клієнтів, і кожен клієнт відправляє b байтів даних за секунду, сервер повинен
мати достатню пропускну здатність, щоб обробляти b*n вхідних байтів за
секунду. Так само, якщо серверу необхідно посилати кожному клієнту c байтів
даних за секунду, сервер повинен підтримувати c*n вихідних байтів за секунду.
Однак кожному клієнту необхідно підтримувати тільки c байтів в секунду у
низхідному напрямку та b байтів в секунду у висхідному напрямку. Це означає,
що зі збільшенням кількості клієнтів пропускна здатність, необхідна для сервера,
буде збільшуватися лінійно. Теоретично вимоги до пропускної спроможності
клієнта не змінюються залежно кількості клієнтів. Однак на практиці підтримка
більшої кількості клієнтів призводить до більшої кількості об’єктів для
реплікації, що може призвести до невеликого збільшення пропускної
спроможності кожного клієнта.
Хоча це в жодному разі не єдиний підхід до клієнт-сервера, більшість ігор,
які реалізують клієнт-сервер, використовують авторитетний сервер. Це означає,
Арк.
ЧДТУ.21.20106.009 ПЗ 55
Змн. Арк. № докум. Підпис Дата
що симуляція гри ігровим сервером вважається правильною. Якщо клієнт будь-
коли виявляє незгоду з сервером, він повинен оновити свій ігровий стан на основі
того, що сервер називає ігровим станом. Наприклад, гравець може вирішити
вистрелити зі зброї, але з авторитетною серверною моделлю клієнту заборонено
визначати, чи потрапляє він в іншого гравця. Натомість клієнт повинен
повідомити сервер, що він хоче вистрелити зі зброї. Потім сервер вирішує (іноді
навіть вирішує чи дозволено клієнту вистрелити) потрапив він в іншого гравця
чи ні.
Розміщення сервера як центр управління означає деяку затримку або
затримку дій, які виконує клієнт. У разі вистрілу зі зброї сервер є єдиним ігровим
екземпляром, якому дозволено приймати рішення про те, що відбувається. Але
для відправки запиту на вистріл зі зброї на сервер, який, у свою чергу, обробить
його перед відправкою результату всім клієнтам, буде потрібно деякий час.
Одним із факторів, що впливають на цю затримку, буде час проходження туди і
назад, або RTT (RTT – round trip time), яка є кількістю часу (зазвичай виражається
в мілісекундах), яка потрібна для передачі пакетів до певного комп’ютера в
мережі та назад. В ідеальному сценарії це RTT становить 100 мс або менше, хоча
навіть при сучасних підключеннях до інтернету існує безліч факторів, які можуть
не допускати такого низького RTT.
Припустимо, що існує гра з сервером та двома клієнтами, клієнтами A та
B. Оскільки сервер відправляє всі дані гри кожному клієнту, це означає, що якщо
клієнт A вистріл зі зброї, пакет, що містить запит на вистріл зі зброї, повинен
спочатку пройти до сервера. Потім сервер обробить подію перед відправкою
результату назад клієнтам A і B. У цьому сценарії найгірша мережна затримка,
що відчувається клієнтом B, дорівнюватиме 1/2 RTT клієнта A плюс час обробки
сервера плюс 1/2 клієнта B. RTT. В умовах швидкої мережі це не може бути
проблемою, але насправді в більшості ігор необхідно використовувати різні
методи, щоб приховати цю затримку.
Також існує підкласифікація типів серверів. Деякі сервери виділені, тобто
вони запускають лише стан гри та спілкуються з усіма клієнтами. Процес
Арк.
ЧДТУ.21.20106.009 ПЗ 56
Змн. Арк. № докум. Підпис Дата
виділеного сервера повністю відокремлений від будь-яких клієнтських процесів.
Це означає, що виділений сервер зазвичай не має центра та фактично не
відображає графіку, що дозволяє розробнику запускати кілька процесів
виділеного сервера на одній потужній машині.
Альтернативою виділеному серверу є слухаючий сервер. У цій
конфігурації сервер також є активним учасником гри. Однією з переваг
конфігурації сервера прослуховування є те, що вона може знизити витрати на
розгортання, оскільки немає необхідності орендувати сервери в центрі обробки
даних – натомість один із гравців може використовувати свою машину як сервер,
так і клієнт. Однак недоліком слухаючого сервера є те, що машина, що працює
як слухаючий сервер, повинна бути достатньо потужною і мати досить швидке
з’єднання, щоб впоратися з цим збільшеним навантаженням. Підхід сервера
прослуховування іноді помилково називають одноранговим з’єднанням (peer-to-
peer connection), але точніший термін – одноранговий хостинг (peer hosted).
Сервер все ще існує, так вийшло, що сервер розміщений у гравця в грі.
Одне застереження щодо сервера прослуховування полягає в тому, що
якщо він є авторитетним, він матиме повну картину стану гри. Це означає, що
гравець, на якому запущено сервер прослуховування, може потенційно
використовувати цю інформацію для обману. Більше того, у моделі клієнт-
сервер зазвичай лише сервер знає мережеву адресу всіх активних клієнтів. Це
може стати серйозною проблемою у разі відключення сервера – чи то через
проблеми з мережею, чи, якщо гравець вирішить вийти з гри. Деякі ігри, в яких
використовується сервер, що прослуховує, реалізують концепцію міграції хоста,
що означає, що якщо прослуховуючий сервер відключається, один з клієнтів стає
новим сервером. Однак, щоб це було можливо, між клієнтами має бути певний
обсяг зв’язку. Це означає, що для міграції хоста потрібна гібридна модель, в якій
є клієнт-сервер, так і однорангова топологія.
В одноранговій топології кожен окремий учасник підключений до решти
учасників і між клієнтами передається великий обсяг даних. Кількість
підключень є квадратичною функцією, або, іншими словами, для однорангових
Арк.
ЧДТУ.21.20106.009 ПЗ 57
Змн. Арк. № докум. Підпис Дата
вузлів n кожен одноранговий вузол повинен мати O(n-1) з’єднань. Це також
означає, що вимоги до пропускної здатності для кожного однорангового вузла
зростають у міру того, як все більше і більше однорангових вузлів підключається
до гри. Однак, на відміну від клієнт-сервер, вимоги до смуги пропускання
симетричні, тому кожному одноранговому вузлу буде потрібна однакова
доступна пропускна здатність у висхідному та низхідному напрямках.
У однорангових іграх поняття авторитету набагато розпливчате. Один із
можливих підходів полягає в тому, що певні партнери мають владу над певними
частинами гри, але на практиці таку систему може бути складно реалізувати.
Найпоширеніший підхід у однорангових іграх у тому, щоб розділити всі дії між
усіма учасниками і змусити їх моделювати ці дії. Цю модель іноді називають
моделлю спільного використання вхідних даних.
Одним із аспектів однорангової топології, який робить спільне
використання введення більш життєздатним, є менша затримка. На відміну від
моделі клієнт-сервер, яка має посередника між клієнтами, в одноранговій грі всі
однорангові вузли спілкуються безпосередньо. Це означає, що у найгіршому
випадку затримка між одноранговими вузлами становить 1/2 RTT. Однак, як і
раніше, існує деяка затримка, яка може призвести до найбільшої технічної
проблеми в одноранговій грі: забезпеченню того, щоб всі однорангові вузли
залишалися синхронізованими один з одним.
У детермінованої моделі локального кроку в якій небудь RTS (RTS – real-
time strategy) представлено один із таких підходів, що гра була розбита на ходи
по 200 мс. Всі вхідні команди протягом цих 200 мс поміщаються в чергу, і коли
ці 200 мс закінчуються, команди відправляються всім однорангові вузли. Крім
того, існує затримка на один хід, так що коли кожен одноранговий вузол
відображає результати ходу 1 команди ставляться в чергу для виконання на ходу
3. Хоча цей тип синхронізації концептуально простий, фактичні деталі реалізації
може бути набагато складнішими.
Крім того, важливо переконатися, що стан гри узгоджений між усіма
одноранговими вузлами. Це означає, що реалізація гри має бути повністю
Арк.
ЧДТУ.21.20106.009 ПЗ 58
Змн. Арк. № докум. Підпис Дата
детермінованою. Іншими словами, даний набір входів завжди повинен
призводити до тих самих виходів. Деякі важливі аспекти цього включають
використання контрольних сум для перевірки узгодженості стану гри між
одноранговими вузлами та синхронізацію генерації випадкових чисел на всіх
вузлах.
Ще одна проблема, що виникає в одноранговій мережі – це підключення
нових гравців. Оскільки кожен одноранговий вузол повинен знати адресу
кожного іншого однорангового вузла, теоретично новий гравець може
підключитися до будь-якого однорангового вузла. Однак служби підбору
гравців, які перераховують доступні ігри, зазвичай приймають лише одну адресу
– у цьому випадку один партнер може бути обраний як так званий головний
партнер, який є єдиним партнером, який вітає нових гравців.
Зрештою, проблема відключення сервера, яка турбує сервер-клієнт,
насправді не існує в одноранговій мережі. Зазвичай при втраті зв’язку з
одноранговим вузлом гра може призупинитись на кілька секунд перед
видаленням однорангового вузла з гри. Після відключення вузла однорангові
вузли, що залишилися, можуть продовжити моделювання гри.
Основою моделі клієнт-сервер з авторитетним сервером є те, що код, який
виконується на сервері, відрізняється від коду, що виконується на кожному
клієнті. Візьмемо, наприклад, аватар гравця. Однією з властивостей аватару є
змінна Health, яка відстежує здоров’я, що залишився у нього. Серверу необхідно
знати про здоров’я, тому що, якщо здоров’я досягає нуля, аватар повинен
перейти в стан смерті або відродження. Точно так само клієнту потрібно знати,
скільки здоров’я у аватара, тому що здоров’я, що залишилося, буде
відображатися в правому верхньому кутку. Навіть якщо серверний екземпляр
Health є офіційною версією змінної, клієнту необхідно буде кешувати змінну
локально, щоб відобразити її в інтерфейсі користувача.
Те саме можна сказати і про функції. Деякі функції-члени можуть бути
необхідні тільки для сервера, деякі – тільки для клієнта, а деякі – для обох. Щоб
врахувати це, можна використовати успадкування та віртуальні функції. Таким
Арк.
ЧДТУ.21.20106.009 ПЗ 59
Змн. Арк. № докум. Підпис Дата
чином, існує базовий клас Health і два похідні класи: HealthServer і HealthClient,
які при необхідності перевизначають та реалізують нові функції-члени. З
погляду продуктивності використання віртуальних функцій в такий спосіб може
дати максимально можливу продуктивність, але з погляду простоти
використання ієрархія успадкування, мабуть, найпростіша.
Також код можна розділити на три окремі цілі. Бібліотека, що містить
загальний код, який використовується як сервером, так і клієнтом (Sockets) і код
що виконуються тільки сервером і тільки клієнтом.
Висновки до розділу 2
В цьому розділі розглянуто проблему надсилання даних, кодування
інформації та проблему підвищення ефективності прийому і обслуговування
клієнта на серверній стороні. Вибір топології мережі – одне з найважливіших
рішень, які приймаються під час створення мережевої гри. У топології клієнт-
сервер один екземпляр гри позначається як сервер і є органом влади всієї гри. Всі
інші ігрові екземпляри є клієнтами та обмінюються даними лише з сервером. Це
означає, що дані реплікації об’єкта відправляються з сервера на клієнт. В
одноранговій топології кожен ігровий екземпляр перебуває більш-менш у рівних
умовах. Один із підходів в одноранговій грі полягає в тому, щоб кожен учасник
моделював гру незалежно.
Код було поділено на три окремі об’єкти: загальна бібліотека, сервер та
клієнт. Процес добавлення нових клієнтів сервером включає передачу вітального
пакета серверу і вітального пакета назад клієнту. У системах введення клієнт
відправляє вхідні пакети, які містять рухи, що виконуються клієнтом,
включаючи переміщення і вистріли зі зброї. Сервер підтримує клієнтський
проксі для кожного клієнта, як для того, щоб відстежувати, які дані реплікації
потрібно надсилати кожному клієнту, так і для того, щоб мати об’єкт, в якому
можуть зберігатися переміщення, що очікують.
Арк.
ЧДТУ.21.20106.009 ПЗ 60
Змн. Арк. № докум. Підпис Дата
Обговорено багато основних проблем при розробці однорангової гри з
незалежним моделюванням. Використання головного вузла дозволяє пов’язати
відому IP-адресу з конкретною грою. Кожен одноранговий вузол підтримує
список адрес всіх інших однорангових вузлів у грі. Вітати нових гравців трохи
складніше, ніж у грі клієнт-сервер, тому що новий одноранговий вузол повинен
інформувати всіх інших однорангових вузлів про своє існування. Однорангові
вузли підтримують синхронізацію, передаючи пакети наприкінці кожного x-
мілісекундного кроку. Команди у цих пакетах заплановані для виконання 1-3
ходами пізніше.
Арк.
ЧДТУ.21.20106.009 ПЗ 61
Змн. Арк. № докум. Підпис Дата
РОЗДІЛ 3
СИНХРОНІЗАЦІЯ СТАНІВ
3.1 Затримка
Гра випущена на ринок, повинна боротися з декількома негативними
факторами, яких немає в жорстко контрольованому тестовому середовищі
локальної мережі. Одним із факторів є затримка. Затримка це період між
натисканням клавіш та реакцією ігрового світу.
Певна затримка неминуча, і різні жанри ігор мають різні пороги
прийнятності затримки. Шутери від першої особи та інші швидкі ігри – найбільш
чутливі до затримки. Затримка у цих іграх може становити від 16 до 150 мс, перш
ніж користувач почне відчувати, незалежно від частоти кадрів, що гра млява і
«не відповідає». RTS-ігри мають найвищий допуск до затримки. Затримка в цих
іграх може збільшуватися до 500 мс без шкоди для досвіду користувача.
Зменшення затримки – це один із способів покращити враження користувачів від
гри.
Поширена хибна думка, що мережева затримка є основним джерелом
затримки в ігровому процесі. Хоча обмін пакетами через мережу є значним
джерелом затримки, він безумовно не єдиний. Є щонайменше п’ять інших
джерел затримки, деякі з яких не перебувають під вашим контролем:
• Затримка пристрою управління. Час між натисканням кнопки
користувачем та виявленням цієї кнопки грою іноді може бути досить значним.
Якщо пристрій управління зафіксовуватиме введення на початку кожного кадру,
а потім відповідним чином оновлює всі об’єкти перед остаточною візуалізацією
ігрового світу, то займе майже цілий кадр, перш ніж гра оновить щось на основі
натискання цієї кнопки. Це означає, що в середньому існує половина кадру
затримки між натисканням кнопки та реакцією гри на це натискання;
Арк.
ЧДТУ.21.20106.009 ПЗ 62
Змн. Арк. № докум. Підпис Дата
• Затримка конвеєра рендерингу. Графічний процесор не виконує
команди відтворення у той час, коли ЦП виконує пакетну обробку інших команд.
Якщо необхідно виконати багато рендерингу, графічний процесор може
відставати від процесора на цілий кадр, перш ніж відобразить візуалізоване
зображення користувачеві;
• VSync. Щоб уникнути розривів екрана, прийнято змінювати
зображення, яке відображається відеокартою, лише під час вертикальної
розгортки монітора. Таким чином, монітор ніколи не відображатиме частину
одного кадру і частину наступного кадру одночасно. Це означає, що поточний
виклик графічного процесора повинен дочекатися вертикального інтервалу
розгортки монітора користувача, який зазвичай становить кожні 1/60 секунди.
Якщо кадри вашої гри займають лише 16 мс, це не проблема. Однак, якщо для
рендерингу кадру потрібно навіть на 1 мс більше, рендеринг не буде завершено
на той час, коли монітор буде готовий оновити відображення. У цьому випадку
команда для представлення зупиниться, чекаючи на додаткові 15 мс до
наступного інтервалу вертикального розгортування. Коли це відбувається, ваш
користувач має додаткову затримку в кадрі. Розрив екрана – це те, що
відбувається, коли графічний процесор представляє задній буфер, коли монітор
перебуває у процесі оновлення зображення на екрані. Монітори зазвичай
оновлюють зображення на екрані по одному горизонтальному рядку зверху
донизу. Якщо зображення, яке відображається на екрані, змінюється всередині
оновлення, користувач спостерігає, як нижня половина екрана показує нове
зображення, а верхня половина все ще показує попереднє зображення;
• Відставання дисплея. Більшість телевізорів високої чіткості та РК-
моніторів певною мірою обробляють своє введення перед фактичним
відображенням зображення. Ця обробка може включати de-interlacing, HDCP та
ефекти зображення, такі як шумозаглушення, адаптивну яскравість;
• Час відгуку пікселя. Додаткова проблема РК-дисплеїв полягає в
тому, що пікселям потрібен час, щоб змінити яскравість. Зазвичай ця тривалість
Арк.
ЧДТУ.21.20106.009 ПЗ 63
Змн. Арк. № докум. Підпис Дата
виражається в одиницях мілісекунд. Перехід починається відразу, але не
завершується протягом декількох мілісекунд.
Хоча існує безліч джерел затримки, затримка, що випробовується пакетом
при його переміщенні від вихідного хоста до місця призначення, зазвичай є
найбільш значною причиною затримки в іграх багато користувачів. Існує чотири
основні типи затримки, з якими стикається пакет протягом свого терміну
служби:
• Затримка обробки. Мережевий маршрутизатор працює, зчитуючи
пакети з мережного інтерфейсу, перевіряючи IP-адресу призначення,
визначаючи наступну машину, яка має отримати пакет, а потім перенаправляючи
його з відповідного інтерфейсу. У час, що витрачається на вивчення вихідної
адреси та визначення відповідного маршруту, відомо як затримка обробки.
Затримка обробки також може включати будь-які додаткові функції
маршрутизатора, такі як NAT або шифрування;
• Затримка передачі. Щоб маршрутизатор пересилав пакет, він
повинен мати інтерфейс канального рівня, який дозволяє йому пересилати пакет
за деяким фізичним середовищем. Протокол канального рівня контролює
середню швидкість, з якою біти можуть бути записані в середовище. Цей час,
витрачений на запис бітів в фізичне середовище, відомий як затримка передачі;
• Затримка у черзі. Маршрутизатор може обробляти лише обмежену
кількість пакетів за один раз. Якщо пакети надходять зі швидкістю, що
перевищує швидкість їхньої обробки маршрутизатором, вони потрапляють у
чергу прийому, очікуючи обробки. Так само мережний інтерфейс може виводити
лише один пакет за раз, тому після обробки пакета, якщо відповідний мережний
інтерфейс зайнятий, він переходить у чергу передачі. Час, проведений у цих
чергах, називається затримкою у черзі;
• Затримка розповсюдження. Час, витрачений на подорож, відомий як
затримка поширення.
Затримка передачі зазвичай залежить від типу з’єднання канального рівня,
який використовує кінцевий користувач. Оскільки пропускна спроможність
Арк.
ЧДТУ.21.20106.009 ПЗ 64
Змн. Арк. № докум. Підпис Дата
зазвичай збільшується при наближенні пакета до магістралі інтернету,
найбільша затримка передачі знаходиться на краях інтернету. Дуже важливо
переконатися, що сервери використовують з’єднання з високою пропускною
здатністю. Надсилання пакетів максимально великого розміру також допоможе,
оскільки ви зменшите кількість байтів, що витрачаються на заголовки. Якщо ці
заголовки складають значну частину розміру пакета, вони перетворюються на
значну частину затримки передачі.
Затримка постановки в чергу – це результат резервного копіювання
пакетів, що очікують на передачу або обробку. Мінімізація затримки обробки та
передачі допоможе мінімізувати затримку у черзі. Варто зазначити, що оскільки
типова маршрутизація вимагає перевірки лише заголовка пакета, ви можете
зменшити затримку в черзі для всіх ваших пакетів, відправивши кілька великих
пакетів замість багатьох маленьких. Наприклад, пакет з корисним
навантаженням розміром 1400 байт зазвичай відчуває таку затримку обробки, як
і пакет з корисним навантаженням 200 байт. Якщо ви відправите сім 200-
байтових пакетів, останній пакет повинен перебувати в черзі під час обробки
шести попередніх пакетів і, таким чином, зазнаватиме більшої сукупної
мережної затримки, ніж один великий пакет.
Затримка поширення часто є дуже гарною ціллю для оптимізації. Оскільки
він заснований на довжині дроту між хостами, що обмінюються даними,
найкращий спосіб його оптимізації – це перемістити хости ближче один до
одного. У однорангових іграх це пріоритет географічного розташування під час
пошуку партнерів. У клієнт-серверних іграх це означає, що ігрові сервери мають
бути доступні поруч із вашими гравцями. У деяких випадках сервери розділяють
по географічним регіонам.
Оскільки затримка – це надто перевантажений термін, розробники ігор
частіше обговорюють час проходження туди та назад, або RTT. Сам трафік не
обов’язково рухається з однаковою швидкістю в кожному напрямку. RTT рідко
вдвічі перевищує час, необхідний передачі пакета від одного хоста до іншого.
Арк.
ЧДТУ.21.20106.009 ПЗ 65
Змн. Арк. № докум. Підпис Дата
Тим не менш, в іграх час у дорозі в один кінець часто наближається, скорочуючи
RTT вдвічі.
3.2 Джіттер
При написанні мережного коду варто пам’ятати, що RTT не обов’язково
матиме постійне значення. Для будь-яких двох хостів RTT між ними зазвичай
коливається біля певного значення. Однак будь-яка з цих затримок може згодом
змінитись, що призведе до відхилення RTT від очікуваного значення. Це
відхилення називається джиттером.
Будь-яка з мережних затримок може робити внесок у джиттер, хоча деякі з
них можуть відрізнятися з більшою ймовірністю, ніж інші:
• Затримка обробки. Як найменш значущий компонент мережної
затримки, затримка обробки також є найменш значущим фактором джиттера.
Затримки обробки можуть змінюватись, оскільки маршрутизатори динамічно
коригують шляхи пакетів;
• Затримка передачі та затримка поширення. Ці дві затримки є
функцією маршруту, яким йде пакет: протоколи канального рівня визначають
затримку передачі, а довжина маршруту визначає затримку поширення. Таким
чином, ці затримки змінюються, коли маршрутизатори динамічно балансують
навантаження та змінюють маршрути, щоб уникнути сильно перевантажених
областей, а зміна маршруту може значно змінити час поїздки туди та назад;
• Затримка у черзі. Затримка постановки у чергу – це функція кількості
пакетів, які має обробити маршрутизатор. Таким чином, оскільки кількість
пакетів, що надходять на маршрутизатор, змінюється, затримка постановки у
чергу також змінюватиметься. Інтенсивні сплески трафіку можуть викликати
значні затримки у черзі та змінити час прийому-передачі.
Відкинуті пакети – іще одна причина затримки. Незалежно від запобігання
втраті пакетів, ігровий процес буде кращим із меншою кількістю відкинутих
пакетів. Варто розміщувати сервера якомога ближче до гравців, тому що менша
Арк.
ЧДТУ.21.20106.009 ПЗ 66
Змн. Арк. № докум. Підпис Дата
кількість маршрутизаторів та проводів означає меншу ймовірність того, що один
з них втратить дані. Крім того, бажано відправляти якнайменше пакетів: багато
маршрутизаторів засновують ємність черги на кількості пакетів, а не на
загальному обсязі даних. У цих випадках гра має вищий шанс переповнення
маршрутизаторів і черг, якщо вона відправляє багато маленьких пакетів, ніж
менших. Для надсилання семи 200-байтових пакетів через забитий
маршрутизатор необхідно, щоб у черзі було сім вільних місць, щоб уникнути
втрати пакетів. Однак для відправки тих самих 1400 байтів в одному пакеті
потрібно лише одне вільне місце черги.
Коли його черги заповнені, маршрутизатор не обов’язково відкидає кожен
пакет. Натомість він може відкинути раніше поставлений у чергу пакет. Це
відбувається, коли маршрутизатор визначає, що вхідний пакет має вищий
пріоритет або важливіший, ніж пакет у черзі. Маршрутизатори приймають
рішення про пріоритет на основі даних QoS в заголовку мережного рівня, а також
іноді на основі глибшої інформації, отриманої під час перевірки корисного
навантаження пакета.
3.3 TCP/UDP та синхронізація
Основна перевага TCP полягає в тому, що він забезпечує надійну передачу
даних. Крім того, він надає складні функції керування навантаженням, які
обмежують втрату пакетів, відправляючи дані зі швидкістю, яка не перевантажує
проміжні маршрутизатори.
Хоча контроль навантаження допомагає запобігти втраті пакетів, іноді гра
може відправляти пакети з повільнішою швидкістю, ніж хотілося б. Алгоритм
Негла тут є поганим порушником, оскільки він може затримувати пакети до
півсекунди перед їх відправкою. Фактично, ігри, що використовують TCP як
транспортний протокол, зазвичай відключають алгоритм Негла, щоб уникнути
саме цієї проблеми, але в той же час відмовляються від переваги зменшеної
кількості пакетів, яку він забезпечує.
Арк.
ЧДТУ.21.20106.009 ПЗ 67
Змн. Арк. № докум. Підпис Дата
UDP, з іншого боку, не пропонує вбудованої надійності та керування
потоком, які забезпечує TCP. Однак він являє собою чисте полотно, на якому
можна створити будь-яку систему надійності, що настроюється. Можна створити
систему, яка надсилає лише нову інформацію при заміні відкинутих пакетів,
замість повторної передачі точних даних, які були втрачені, керувати пам’яттю
та точно контролювати те, як дані групуються в пакети мережного рівня. Все це
потребує витрат часу на розробку та тестування. Але UDP пов’язаний з
підвищеним ризиком втрати пакетів, оскільки маршрутизатори можуть бути
налаштовані на зниження пріоритету пакетів UDP. Таблиця 3.1 підсумовує
різницю між протоколами.
Таблиця 3.1
Різниця між TCP і UDP
Критерій TCP UDP
Надійність Все доставляється та Потрібна індивідуальна
обробляється у тому реалізація, але
порядку, в якому було забезпечує високу
відправлено. надійність.
Управління потоком Автоматично знижує Немає (але вимагає
швидкість передачі, управління потоком і
якщо пакети губляться. перевантаженням).
Вимоги до пам’яті ОС повинна зберігати Реалізація користувача
копії всіх надісланих повинна вирішувати, які
даних до підтвердження. дані зберігати, а які
відразу ж відкинути.
Пам’ять керується лише
на рівні програми.
Пріоритезація Може мати пріоритет Може бути відкинуто
маршрутизатора над пакетами UDP. перед пакетами TCP.
У більшості випадків вибір транспортного протоколу, що
використовується, зводиться до наступного питання: чи потрібно отримувати всі
дані, які відправляє гра, і чи потрібно їх обробляти повністю впорядкованим
чином? Якщо відповідь позитивна, вам слід розглянути можливість
використання TCP. Це часто вірно для покрокових ігор. Кожна частина введення
Арк.
ЧДТУ.21.20106.009 ПЗ 68
Змн. Арк. № докум. Підпис Дата
повинна бути отримана кожним хостом і оброблена в тому самому порядку, тому
TCP ідеально підходить.
Якщо TCP не ідеально підходить для вашої гри, а для більшості ігор це не
так, вам слід використовувати UDP із системою забезпечення надійності на рівні
додатків поверх нього. Це означає використання стороннього програмного
забезпечення або створення власної системи.
Традиційна вимога будь-якої стійкої до шахрайства клієнт-серверної
установки: сервер – єдиний хост, на якому виконується симуляція, яка має
значення. Це означає, що завжди є деяка затримка між моментом, коли гравець
здійснює дію, і моментом, коли гравець може спостерігати справжній стан гри,
що виникає внаслідок цієї дії.
Розглянемо приклад де час приймання-передачі (RTT) між клієнтом A та
сервером становить 100 мс. На момент часу 0 аватар гравця A на клієнті A
перебуває у стані спокою, а позиція Z дорівнює 0. Потім гравець A натискає
кнопку руху. Припускаючи приблизно симетричну затримку, пакету, що несе
вхід гравця А, потрібно близько 50 мс, або 1/2 RTT до сервера. Коли він отримує
введення, сервер встановлює Z-позицію його аватара на 1. Він надсилає новий
стан, який досягає Клієнта A ще через 50 мс, або 1/2 RTT. Клієнт A оновлює
положення Z аватара гравця A на основі стану, надісланого з сервера, та
відображає результати на екрані. Отже, нарешті через повні 100 мс після
натискання кнопки переходу, гравець А може побачити ефект дії руху.
У ранніх клієнт-серверних іграх того часу клієнти відправляли введення на
сервер, а потім сервер запускав моделювання та надсилав результати назад
клієнту для відображення. Клієнтам не потрібно було нічого розуміти у
симуляції; їхня єдина мета полягала в тому, щоб передати введення, отримати
результуючий стан і відобразити його користувачеві. Оскільки вони показували
лише стан, продиктований сервером, вони ніколи не показували користувачеві
неправильний стан. Хоча це могло бути віддалено в часі, хоч би який стан
показував користувачеві його термінал, безумовно було правильним станом у
якийсь недавній момент часу.
Арк.
ЧДТУ.21.20106.009 ПЗ 69
Змн. Арк. № докум. Підпис Дата
Розробка гри – саме по собі непросте заняття, але мультіплеер гри
створюють абсолютно нові проблеми, що потребують вирішення. Якщо ви
розробляєте одиночну гру, вам все одно якщо користувач вирішить взламати гру
– його дії впливають тільки на нього. Так, можливо гравець не отримає той
досвід, який ви хотіли йому дати, але в кінці кінців він купив гру і може
користуватися нею як захоче.
А ось в мультіплеєрних іграх все зовсім інакше. У будь-якій грі змагання
обманюючий гравець не тільки покращує ігровий процес для себе, але й
погіршує його для інших гравців. Вам, як розробнику, варто перешкоджати
цьому, так як чітери відганяють гравців від вашої гри.
Є багато речей, які можна зробити, щоб запобігти чітерству, але
найважливіший (і, ймовірно, єдиний дійсно значущий) простий підхід: не
довіряйте гравцю. Завжди чекайте гіршого – що гравець буде намагатися вас
обдурити.
Цей принцип веде нас до простого, на перший погляд, рішення – вся ігрова
логіка крутиться на головному сервері, під вашим контролем, а клієнт лише
демонструє поточний стан сервера і відправляє йому команди (натискання
клавіш і т.д.). Зазвичай це називають авторитарним сервером, тому що він
єдиний, хто вміє моделювати світ.
Тим не менше, використання авторитарного сервера запобігає широкий
спектр проблем. Наприклад, ви не можете довіряти клієнтові рівень HP (health
point) гравця. Зламаний клієнт може змінити локальну інформацію і повідомити
що у гравця 3000 HP, але сервер знає що життів всього 10 і якщо гравця атакують,
він помре незалежно від того, що про це думає клієнт.
Так само не можна вірити гравцеві, коли він повідомляє про його позиції в
світі. Якщо ви довіритеся, зламаний клієнт може повідомити сервер:
– Я на (10, 10)
А за мить:
– Я на (20, 10)
Арк.
ЧДТУ.21.20106.009 ПЗ 70
Змн. Арк. № докум. Підпис Дата
При цьому можливо він «пройшов» через стіну або рухається швидше ніж
йому належить.
Наївна реалізація цієї концепції призводить до суттєвої затримки між
командою та реакцією. Наприклад, якщо гравець натискає стрілку вліво,
персонаж почне рухатися через деякий час після натискання. Це тому, що
команда повинна дійти до сервера, а результат команди після цього повинен
дійти до клієнта.
Різкість, викликана нечастими оновленнями стану з сервера, може змусити
гравців відчувати, що гра працює повільніше, ніж є насправді. Один із способів
пом’якшити це – інтерполяція на стороні клієнта. При використанні інтерполяції
на стороні клієнта клієнтська гра не телепортує об’єкти автоматично на нові
позиції, відправлені сервером. Натомість кожного разу, коли клієнт отримує
новий стан об’єкта, він плавно інтерполює цей стан з часом.
Якщо ігровий світ досить детермінований (тобто, враховуючи стан гри та
набір вхідних даних, результат цілком передбачуваний). Припустимо, у нас є
затримка 100 мс, а анімація переходу персонажа від одного квадрата до іншого
займає 100 мс. Використовуючи наївну реалізацію, вся дія займе 200 мс.
Оскільки світ є детермінованим, ми можемо припустити, що вхідні дані,
які ми надсилаємо на сервер, будуть успішно виконані. При такому припущенні
клієнт може передбачити стан ігрового світу після обробки вхідних даних, і вони
будуть правильними.
Замість того, щоб відправляти вхідні дані та чекати, поки новий стан гри
почне його рендеринг, ми можемо відправити вхідні дані та почати рендеринг
результатів цих входів, якби вони були успішними, поки ми чекаємо, поки сервер
відправить «справжній» стан гри – що найчастіше відповідає стану,
розрахованому локально.
Тепер немає абсолютно ніякої затримки між діями гравця та результатами
на екрані, в той час як сервер, як і раніше, є авторитетним (якщо зламаний клієнт
відправить неприпустимі вхідні дані, він може відобразити на екрані все, що
захоче, але це не вплине на стан сервера, що бачать інші гравці).
Арк.
ЧДТУ.21.20106.009 ПЗ 71
Змн. Арк. № докум. Підпис Дата
Однак розглянемо трохи змінений сценарій: припустимо, у нас затримка
до сервера 250 мс, а перехід від квадрата до наступного займає 100 мс.
Припустимо, що гравець натискає праву клавішу 2 рази поспіль, намагаючись
переміститися на 2 квадрати вправо. Використовуючи методи досі, ось що могло
б статися (Рис 3.2).
Рис. 3.2 Невідповідність прогнозованого стану та авторитетного стану
Ми стикаємося з цікавою проблемою при t = 250 мс, коли приходить новий
стан гри. Прогнозований стан клієнта – x = 12 , але сервер повідомляє, що новий
ігровий стан – x = 11 . Оскільки сервер є авторитетним, клієнт повинен
перемістити символ назад x = 11 . Але потім новий стан сервера досягає t = 350 ,
де вказано x = 12 , тому персонаж знову стрибає цього разу вперед.
З погляду гравця, він двічі натискав клавішу зі стрілкою праворуч;
персонаж перемістився на два квадрати праворуч, зупинився там 50 мс, стрибнув
Арк.
ЧДТУ.21.20106.009 ПЗ 72
Змн. Арк. № докум. Підпис Дата
на один квадрат вліво, зупинився там 100 мс і стрибнув на один квадрат
праворуч. Це, звісно, неприпустимо.
Ключем до вирішення цієї проблеми є усвідомлення того, що клієнт бачить
ігровий світ в даний час, але через затримку оновлення, які він отримує від
сервера, насправді є станом гри в минулому. На той час, коли сервер відправив
оновлений стан гри, він не обробив усі команди, надіслані клієнтом.
Однак обійти це не так вже й складно. Спочатку клієнт додає порядковий
номер кожного запиту; у прикладі перше натискання клавіші – це запит №1, а
друге натискання клавіші – це запит №2. Потім, коли сервер відповідає, він
включає порядковий номер останнього обробленого введення (Рис 3.3).
Рис. 3.3 Прогнозування на стороні клієнта + узгодження сервера
Оскільки сервер є авторитетним, він встановлює позицію символу x = 11.
Тепер припустимо, що клієнт зберігає копію запитів, які він надсилає на сервер.
Арк.
ЧДТУ.21.20106.009 ПЗ 73
Змн. Арк. № докум. Підпис Дата
Ґрунтуючись на новому стані гри, він знає, що сервер вже обробив запит №1,
тому він може відкинути цю копію. Але він також знає, що сервер досі має
відправити назад результат обробки запиту №2. Таким чином, знову
застосовуючи прогнозування на стороні клієнта, клієнт може обчислити стан гри
на основі останнього авторитетного стану, відправленого сервером, плюс вхідні
дані, які сервер ще не обробив.
Отже, при t = 250 клієнт отримує «x = 11, останній оброблений запит = #1».
Він відкидає свої копії надісланих вхідних даних до №1, але зберігає копію №2,
яка не була підтверджена сервером. Він оновлює свій внутрішній ігровий стан,
використовуючи те, що відправив сервер, x = 11, а потім застосовує всі вхідні
дані, які сервер ще не бачить – в даному випадку вхід №2 «рухатися вправо».
Кінцевий результат – x = 12, що вірно.
Продовжуючи наш приклад, при t = 350 новий ігровий стан надходить із
сервера; цього разу він каже: «x = 12, останній оброблений запит = #2». На цьому
етапі клієнт відкидає всі вхідні дані до #2 і оновлює стан x = 12. Немає
необробленого введення для відтворення, тому обробка закінчується з
правильним результатом.
Розглянутий вище приклад має на увазі рух, але той самий принцип можна
застосувати практично до всього іншого. Наприклад, у покроковій бойовій грі,
коли гравець атакує іншого персонажа, ви можете показати кров і число, що
представляє завдану шкоду, але ви не повинні фактично оновлювати здоров’я
персонажа, поки сервер не скаже про це.
Через складність ігрового стану, який не завжди легко оборотний, ви
можете захотіти уникнути вбивства персонажа, поки сервер не скаже про це,
навіть якщо його здоров’я впало нижче за нуль в ігровому стані клієнта (що, якби
інший персонаж використовував аптечку безпосередньо перед смертельною
атакою, але сервер ще вас не повідомив про це).
Це підводить нас до цікавого моменту – навіть якщо світ повністю
детермінований і клієнти взагалі не обманюють, все ж таки можливо, що стан,
передбачений клієнтом, і стан, відправлений сервером, не збігаються після
Арк.
ЧДТУ.21.20106.009 ПЗ 74
Змн. Арк. № докум. Підпис Дата
погодження. Сценарій неможливий, як описано вище, з одним гравцем, але його
легко запустити, коли до сервера підключено кілька гравців одночасно.
Кілька клієнтів можуть надсилати дані одночасно і в швидкому темпі
(настільки швидко, наскільки гравець може давати команди, будь то натискання
клавіш зі стрілками, переміщення миші або клацання по екрану). Оновлення
ігрового світу щоразу при отриманні вхідних даних від кожного клієнта, а потім
трансляція стану гри вимагатиме надто багато ресурсів ЦП та смуги
пропускання.
Найкращим підходом є постановка клієнтських вхідних даних у чергу у
міру їх надходження без будь-якої обробки. Натомість ігровий світ періодично
оновлюється з низькою частотою, наприклад 10 разів на секунду. Затримка між
кожним оновленням, у разі 100 мс, називається часовий кроком (time step). На
кожній ітерації циклу оновлення застосовується все необроблене введення
клієнта (можливо, з меншими часовими інтервалами, ніж часовий крок, щоб
зробити фізику більш передбачуваною), і новий стан гри транслюється клієнтам.
Таким чином, ігровий світ оновлюється незалежно від присутності та кількості
дій клієнта із передбачуваною швидкістю.
З погляду клієнта, цей підхід працює так само гладко, як і раніше –
прогнозування на стороні клієнта працює незалежно від затримки оновлення,
тому очевидно, що він також працює при передбачуваних, хоч і щодо нечастих,
оновлення стану. Однак, оскільки стан гри транслюється з низькою частотою (у
нашому прикладі кожні 100 мс), клієнт має дуже мізерну інформацію про інші
об’єкти, які можуть переміщатися світом.
Перша реалізація оновлюватиме положення інших символів при отриманні
оновлення стану; це негайно призводить до дуже уривчастого руху (Рис 3.4),
тобто дискретних стрибків кожні 100 мс замість плавного руху.
Залежно від типу гри, яку ви розробляєте, є багато способів упоратися з
цим; загалом, що більш передбачувані ваші ігрові об’єкти, то легше зробити це
правильно.
Арк.
ЧДТУ.21.20106.009 ПЗ 75
Змн. Арк. № докум. Підпис Дата
Рис. 3.4 Уривчастий рух
У 3D-шутері гравці зазвичай бігають, зупиняються та повертають
повороти на дуже високих швидкостях, що робить марний розрахунок по суті,
оскільки положення та швидкість більше не можуть бути передбачені на основі
попередніх даних.
Ви не можете просто оновлювати позиції гравців, коли сервер надсилає
достовірні дані; ви отримаєте гравців, які телепортуються на короткі відстані
кожні 100 мс, що зробить ігровий процес неприємним.
Що у вас є, то це достовірні дані про місце розташування кожні 100 мс;
фокус у тому, як показати гравцеві, що відбувається між ними. Ключ до рішення
– показати інших гравців у минулому щодо гравця користувача.
Допустимо, ви отримуєте дані про місцезнаходження в t = 1000. Ви вже
отримали дані за t = 900 , тому ви знаєте, де був гравець при t = 900 і t = 1000 .
Отже, починаючи з t = 1000 і t = 1100 ви показуєте, що зробив інший гравець з t
= 900 до t = 1000 . Таким чином, ви завжди показуєте фактичні дані про рух
Арк.
ЧДТУ.21.20106.009 ПЗ 76
Змн. Арк. № докум. Підпис Дата
користувача, за винятком того, що показуєте їх на 100 мс із запізненням
(Рис. 3.5).
Рис. 3.5 Клієнт 2 відображає клієнта 1 «у минулому», інтерполюючи останні
відомі позиції
Дані про місце розташування, які ви використовуєте для інтерполяції від
t=900 до t=1000, залежать від гри. Інтерполяція зазвичай працює досить добре.
Якщо це не так, ви можете попросити сервер відправляти більш докладні дані
про рух з кожним оновленням – наприклад, послідовність прямих сегментів, за
якими слідує гравець, або позиції, що вибираються кожні 10 мс, які виглядають
краще при інтерполяції (вам не потрібно для відправки в 10 разів більше даних –
Арк.
ЧДТУ.21.20106.009 ПЗ 77
Змн. Арк. № докум. Підпис Дата
оскільки ви відправляєте дельти для невеликих переміщень, формат може бути
оптимізований для цього конкретного випадку).
Зауважте, що, використовуючи цю техніку, кожен гравець бачить трохи
різну візуалізацію ігрового світу, тому що кожен гравець бачить себе в
теперішньому, але бачить інші сутності в минулому. Однак навіть для гри, що
швидко розвивається, побачити інші сутності зі швидкістю 100 мс зазвичай не
помітно.
Є винятки – коли вам потрібна велика просторова та тимчасова точність,
наприклад, коли гравець стріляє в іншого гравця. Оскільки інші гравці видно в
минулому, ви прицілюєтеся із затримкою в 100 мс, тобто ви стріляєте туди, де
була ваша ціль 100 мс тому!
Архітектура гри клієнт-сервер, яку можна резюмувати так:
1) Сервер отримує вхідні дані від усіх клієнтів із відмітками часу;
2) Сервер обробляє вхідні дані та оновлює світовий статус;
3) Сервер з деякою частотою надсилає свій стан усім клієнтам;
4) Клієнт відправляє команди та локально відтворює їх результат;
5) Клієнт отримує оновлення світу та:
– Застосовує стан від сервера;
– Наново застосовує всі свої команди, які сервер не встиг застосувати;
– Інтерполює попередні стани інших гравців.
6) З погляду гравця, це має два важливі наслідки:
– Гравець бачить себе у справжньому;
– Гравець бачить інші об'єкти у минулому.
Ця ситуація загалом хороша, але досить проблематична для подій, дуже
чутливих до часу та простору (наприклад вистрілів в об’єкти малого розміру –
слабкі місця).
Чому це відбувається? Через раніше описану клієнт-серверну архітектуру
ви цілилися в те слабке місце ворога (наприклад голова), де воно (слабке місце)
знаходилося за 100 мс до пострілу, а не під час пострілу!
Арк.
ЧДТУ.21.20106.009 ПЗ 78
Змн. Арк. № докум. Підпис Дата
В якомусь сенсі це схоже на гру у всесвіті, де швидкість світла справді
дуже мала; ви цілитеся в минулу позицію вашого ворога, але на той час, як ви
натискаєте на спусковий гачок, він уже давно змінив свою позицію.
На щастя, для цього є відносно просте рішення, яке також подобається
більшості гравців більшу частину часу (за одним винятком, яке обговорюється
нижче).
Ось як це працює: коли ви стріляєте, клієнт відправляє цю подію на сервер
із повною інформацією: точною позначкою часу вашого пострілу та точним
напрямком зброї.
Ось вирішальний крок. Оскільки сервер отримує всі вхідні дані з
відмітками часу, він може авторитетно реконструювати світ будь-коли в
минулому. Зокрема, він може реконструювати світ таким, яким він виглядав для
будь-якого клієнта в будь-який момент часу.
Це означає, що сервер може точно знати, що було в прицілі зброї в момент
пострілу. Це було минуле становище вашого ворога, але сервер знає, що це було
становище ворога у вашому теперішньому. Сервер обробляє знімок у цей
момент часу та оновлює клієнтів.
І всі задоволені! Сервер щасливий, тому що він є сервером. Він завжди
щасливий. Ви щасливі, тому що ви прицілилися в голову ворогові, вистрілили та
отримали корисний постріл у голову! Ворог може бути єдиним, хто не зовсім
щасливий. Якщо він стояв нерухомо, коли в нього стріляли, то його вина, правда?
Якщо він рухався... вау, ти справді класний снайпер.
Але що, якби він був у відкритій позиції, заліз за стіну, а потім через частку
секунди отримав кулю, коли він думав, що перебуває в безпеці? Що ж, таке
буває. Це ваш компроміс. Оскільки ви стріляли в нього в минулому, він все ще
може бути застрелений протягом кількох мілісекунд після того, як сховався.
Це дещо несправедливо, але це найприйнятніше рішення для всіх
учасників. Це звучить нечесно, але пам’ятатимемо про те, що з точки зору
стрільця цей гравець перебігав через відкритий простір стільки ж часу, скільки і
з точки зору гравця. Було б набагато гірше, якби точний постріл не спрацював!
Арк.
ЧДТУ.21.20106.009 ПЗ 79
Змн. Арк. № докум. Підпис Дата
Важливо зауважити, що цей метод працює навіть за гігантського пінгу.
Команди з давністю у дві секунди не треба виконувати на сервері.
Але було би незручно грати при частоті оновлення сервера 10 разів на
секунду. Так як на клієнті ми використовуємо інтерполяцію для відображення
позицій ворогів, було б не цілком коректно застосовувати конкретний стан
сервера, який нібито малював клієнт, адже насправді гравець стріляв у ворога,
який перебував між двома станами. Якщо ми застосовуємо механізм компенсації
лага, з його допомогою можна знайти два стани світу, між якими стався постріл
і проінтерполювати їх. Таким чином можна стріляти при 2-4 оновленнях сервера
в секунду.
Хоча гра за такої частоти сервера, звичайно, виглядає жахливо. І взагалі
варто тримати FPS якомога вище. Чим більша частота дискретизації ігрового
світу, тим якісніший ігровий досвід буде у гравця, але частота яка вища за 60 –
надлишкова і також призводить до великого навантаження в мережі.
3.4 Безпека
У разі нормальної роботи мережі пакети маршрутизуються через кілька
різних комп’ютерів на своєму шляху від джерела до IP-адреси призначення.
Принаймні маршрутизаторам необхідно читати інформацію заголовків у
пакетах, щоб визначити, куди надіслати пакет, іноді адреси заголовків можуть
бути переписані для трансляції мережевих адрес. Однак, враховуючи відкритий
характер даних, що передаються, ніщо не заважає будь-якій з машин на маршруті
перевіряти всі дані в конкретному пакеті.
Іноді перевірка корисного навантаження, що міститься в пакеті, може
виконуватися заради нормальної роботи мережі. Наприклад, деякі споживчі
маршрутизатори використовують глибоку перевірку пакетів (deep packet
inspection) для забезпечення якості обслуговування (quality of service) – систему,
яка надає перевагу одним пакетам над іншими.
Арк.
ЧДТУ.21.20106.009 ПЗ 80
Змн. Арк. № докум. Підпис Дата
Але є також форма перевірки цих пакетів, яка завжди безпечна.
Відстеження пакетів (packet sniffing) – це термін, який зазвичай
використовується для читання пакетних даних для цілей, відмінних від
нормальної роботи мережі. Це може бути зроблено для багатьох цілей,
включаючи спробу крадіжки даних для входу в систему або шахрайство в
мережевих іграх.
При атаці «людина посередині» (англ. Man in the middle (MITM))
комп’ютер десь на шляху від джерела до місця призначення перехоплює пакети
без відома вихідного та цільового комп’ютерів. Насправді це може статися
кількома різними способами. Будь-який комп’ютер, який використовує
незахищену або загальнодоступну мережу Wi-Fi, може отримати всю
інформацію про свої пакети, прочитану іншим комп’ютером у цій мережі. (Ось
чому зазвичай рекомендується використовувати зашифрований VPN при
підключенні до мережі Wi-Fi у місцевому кафе). Якщо у провідній мережі,
можливо, шлюз перехоплює пакети – або через якесь шкідливе програмне
забезпечення, або через системного адміністратора. І якщо з якоїсь причини
урядові агенти націлені на вашу гру, можливо, що програмне забезпечення,
встановлене в інтернет-провайдера, намагається отримати доступ до даних.
Технічно гравець може спеціально встановити посередника з метою
відстеження пакетів. Ви повинні припускати, що у будь-якому випадку гравець
завжди має доступ до всіх даних, що передаються по мережі. Отже, до кінця
цього обговорення «людина посередині» ми припускатимемо, що «людина» – це
третя сторона, невідома як вихідному, так і цільовому комп’ютеру. Загальний
підхід до боротьби з посередником полягає в шифруванні всіх даних, що
передаються.
У разі гри, перш ніж реалізовувати якусь систему шифрування, слід
розглянути, чи містить розглянута гра будь-які конфіденційні дані, які необхідно
зашифрувати. Якщо ваша гра містить будь-які мікротранзакції, в яких гравець
може купувати внутрішньоігрові предмети, необхідно зашифрувати будь-які
дані, пов’язані з покупками. Якщо ви зберігаєте або навіть просто обробляєте
Арк.
ЧДТУ.21.20106.009 ПЗ 81
Змн. Арк. № докум. Підпис Дата
інформацію про кредитну картку, стандарт безпеки даних індустрії платіжних
карток (PCI DSS) може бути юридичною вимогою. Однак, навіть якщо всередині
ігрових покупок немає, будь-яка гра, в якій гравець входить до облікового
запису, що зберігає прогрес, має шифрувати дані, пов’язані з процесом входу до
системи. В обох цих випадках є грошовий стимул для третьої сторони крадіжки
інформації – чи то кредитна картка, чи логін. Тому дуже важливо, щоб ваша гра
захищала цінні дані гравця від зловмисника.
Якщо ви дійдете висновку, що ваша гра дійсно надсилає конфіденційні
дані, які необхідно захистити від сторонніх осіб, то рекомендується
використовувати перевірену систему шифрування таку як RSA.
У разі мережної гри, де є сервер входу в систему, клієнт матиме доступ до
відкритого ключа сервера. Коли клієнт бажає увійти на сервер, його логін та
пароль шифруються з використанням відкритого ключа сервера. Потім цей пакет
входу до системи може бути розшифрований лише закритим ключем сервера,
який, сподіваюся, знає лише сервер!
Мабуть, найпопулярнішою системою шифрування з відкритим ключем,
яку сьогодні використовують, є система RSA, розроблена в 1977. RSA-ключі
генеруються в такий спосіб:
Етап 1: Генерація ключів
Обрати два простих різних числа:
p = 13. q = 19.
Обчислити добуток:
n = p*q = 13*19 = 247.
Обчислити функцію Ейлера:
f(n) = (p - 1)*(q - 1) = 12*18 = 216.
Обрати відкриту експоненту (1<e<f(n) і є простим числом):
e = 7.
Обчислити секретну експоненту (e*d mod(f(n)) = 1):
Арк.
ЧДТУ.21.20106.009 ПЗ 82
Змн. Арк. № докум. Підпис Дата
7*d mod(216) = 1;
7*31 mod(216) = 1;
217 mod(216) = 1.
Опублікувати відкритий ключ:
{e, n} = {7, 247}.
Зберегти секретний ключ:
{d, n} = {31, 247}.
Етап 2: Шифрування
m = 34;
c = E(m);
c = me mod(n);
c1 = 37 mod(247);
c1 = 2187 mod(247);
c1 = 211;
c2 = 47 mod(247);
c2 = 16384 mod(247);
c2 = 82;
c = 211 82.
Етап 3: Розшифрування
c = 211 82;
m = D(c);
m = cd mod(n);
Арк.
ЧДТУ.21.20106.009 ПЗ 83
Змн. Арк. № докум. Підпис Дата
m1 = 21131 mod(247);
m1 = 3;
m2 = 8231 mod(247);
m2 = 4;
m = 34.
Коли ігри, які передають конфіденційні дані, повинні турбуватися про
атаку «зловмисник у середині», кожна мережева гра сприйнятлива до того, що
хост-машина навмисно перехоплює пакети. У цьому випадку шифрування даних
є фактором стримування, але не є надійним заходом. Причина цього в тому, що
файл гри, що виконується, на будь-якій платформі завжди можна зламати, тому
шифрування ігрових даних не завадить комусь навчитися їх розшифровувати.
Десь у файлі, що виконується, повинен бути код, який знає, як розшифрувати
дані, які виконуваний файл повинен отримати. Після визначення схеми
дешифрування пакетні дані можуть бути прочитані так, якби вони не були
зашифровані.
При цьому зворотне проектування коду дешифрування і пошук закритого
ключа, що зберігається в клієнті, займає деякий час. Таким чином, один із
способів ускладнити завдання для потенційних шахраїв – це, як і раніше,
шифрувати дані, але регулярно змінювати ключі шифрування та зміщення
пам’яті на ці ключі. Це вимагатиме від когось повторення процесу зворотного
проектування щоразу, коли ваша гра оновлюється. Так само, якщо ваша гра
регулярно змінює формат і порядок пакетів, це робить чити, які покладаються на
формат певного пакета застарілими. Ще раз, це змушує гравців витратити час на
вивчення нового формату та змусити чити знову працювати. Таким чином,
регулярна зміна шифрування або формату пакетів зробить розробку читів для
гри більш дратівливою. Це означає, що більшість гравців відмовляються від
розробки читів. Але в будь-якому випадку вам все одно доведеться прийняти той
Арк.
ЧДТУ.21.20106.009 ПЗ 84
Змн. Арк. № докум. Підпис Дата
факт, що ви ніколи не зможете перешкодити особам перехопити всі пакети на
хост-машині.
Варто замислитися над тим, чого саме намагається досягти аналізатор
пакетів на хост-машині. Гравець на головній машині зазвичай намагається
використати інформаційний обман, тобто він чи вона намагається зібрати
інформацію, яку він чи вона не повинна знати. Поширеним утриманням від
шахрайства у разі є обмеження кількості інформації, переданої кожному хосту.
У грі клієнт-сервер сервер може обмежити дані, які він відправляє кожному
клієнту.
Загалом ви повинні припускати, що будь-які дані, відправлені на кожен
хост, можуть бути досліджені гравцем, який намагається обдурити. Таким
чином, якщо гра гарантує, що передається лише критична інформація, що
стосується кожного хоста, це мінімізує можливість шахрайства. Це буде набагато
простіше забезпечити в топології клієнт-сервер, ніж в одноранговій топології,
оскільки однорангова мережа може працювати тільки в тому випадку, якщо всі
дані, які стосуються гри, надсилаються кожному одноранговому вузлу. Таким
чином, однорангова гра має використовувати інші підходи боротьби з
читерством.
На відміну від розглянутих методів аналізу пакетів, перевірка введення
прагне гарантувати, що жоден гравець не виконає неприпустиме дію. Цей метод
запобігання читерству може однаково добре працювати як ігри клієнт-сервер, так
і в іграх з одноранговими мережами. Реалізація перевірки введення зводиться до
простої передумови, що гра ніколи не повинна виконувати виконання дії з
пакета, відправленого по мережі. Натомість дія має бути спочатку підтверджена,
щоб переконатися, що вона дійсна в даний момент часу.
Наприклад, припустимо, що по мережі відправляється пакет із проханням
до гравця А вистрілити. Машина, що приймає, ніколи не повинна припускати,
що гравець А може стріляти. Спочатку необхідно переконатися, що гравець А
має зброю, в ній є кулі і що зброя не охолоджується. Якщо будь-яка з цих умов
не виконується, запит на постріл має бути відхилено.
Арк.
ЧДТУ.21.20106.009 ПЗ 85
Змн. Арк. № докум. Підпис Дата
Далі слід підтвердити, що при отриманні дії для гравця A воно
відправляється від клієнта, який відповідає за гравця A. У разі гри клієнт-сервер
кожна адреса хоста була пов’язана з клієнтськими повноваження. В
одноранговій грі кожна команда видається конкретним гравцем. Коли пакети
команд приймаються через мережу, вони зв’язуються з цим конкретним
одноранговым вузлом. Коли настав час виконувати команди, однорангові вузли
відхилять будь-які команди для модулів, що не належать до вузла, що видає
команди.
Якщо виявлені неприпустимі дії, може виникнути бажання викинути із
сесії гравця, що порушив. Однак слід враховувати можливість того, що
неправильне введення було випадковим, можливо, через затримку або втрату
пакетів. Загалом, консервативніший підхід, що полягає у простому відхиленні
неприпустимого введення, буде правильним курсом дій.
Хоча перевірка введення добре працює для сервера, який перевіряє клієнта,
і однорангового вузла, що перевіряє іншого однорангового вузла, для клієнта не
дуже просто перевіряти команди з сервера.
У моделі авторитетного сервера лише сервер має повну картину стану гри.
Таким чином, якщо сервер повідомляє клієнту, що клієнт повинен отримати
пошкодження, клієнту буде складно перевірити, чи ця шкода є законною. Це
подвійно правильно, оскільки у типової конфігурації клієнта немає можливості
безпосередньо спілкуватися коїться з іншими клієнтами. Таким чином, у клієнта
A немає способу перевірити, чи команда прийшла від клієнта B – він повинен
довіряти тому, що сервер відправляє йому дійсну інформацію.
При виявленні читерства програмне забезпечення, яке запускається як
частина ігрового процесу або поза ним, активно контролює цілісність гри.
Більшість методів обману включають запуск читерського програмного
забезпечення тому ж комп’ютері, як і гра. Деякі чіти підключаються до ігрового
процесу, інші чити перезаписують пам’ять в ігровому процесі, треті – це сторонні
програми, що використовуються для автоматизації, а деякі чити навіть
модифікують файли даних, які використовуються грою. Всі ці різні типи
Арк.
ЧДТУ.21.20106.009 ПЗ 86
Змн. Арк. № докум. Підпис Дата
шахрайства можуть бути знайдені за допомогою програмного забезпечення
виявлення читів, що робить його дуже потужним методом боротьби з читерами.
Інший популярний чіт – це бот, який або грає в гру замість гравця, або
якимось чином допомагає гравцю. Наприклад, боти протягом багатьох років
використовувалися в MMO гравцями, які хотіли підвищити рівень або заробити
гроші, навіть коли вони сплять або інакше знаходяться далеко від свого
комп’ютера. В іграх FPS деякі гравці використовують прицільні боти, щоб
забезпечити ідеальну точність при кожному пострілі. Обидва ці типи ботів
можуть серйозно порушити цілісність гри, і вони можуть бути виявлені лише з
допомогою виявлення програмних читів.
Зрештою, будь-яка розрахована на багато користувачів гра, яка хоче
створити сильне співтовариство, повинна буде розглянути можливість
використання програмного забезпечення для виявлення читерства. Сьогодні
використовується кілька різних програмних читів. Якщо ви вирішите реалізувати
власне виявлення програмних читів, майте на увазі, що це вимагає глибокого
розуміння низькорівневого програмного забезпечення та зворотного
проектування. Також варто відзначити, що навіть найкращі платформи
виявлення програмних читів можна оминути. Тому вкрай важливо постійно
оновлювати виявлення читів, щоб випереджати тих, хто пише чит-програми.
Одним із рішень може стати чистий сервер перевіряє вміст користувачів
під час підключення. Сервер чекає на контрольні суми всіх файлів, які повинні
існувати на клієнті. Після приєднання до гри клієнт повинен надіслати на сервер
контрольні суми файлів, і у разі невідповідності клієнт викидається з гри. Цей
процес також відбувається, коли відбувається перехід карти, коли змінюється
рівень. Щоб врахувати той факт, що деякі ігри дозволяють налаштовувати,
наприклад, змінювати зовнішній вигляд персонажів, також можна занести до
білого списку деякі файли та шляхи, щоб вони не перевірялися на узгодженість.
Ще один важливий аспект безпеки мережних ігор – це захист сервера від
атак. Це особливо важливо для ігор із загальним світом із центральними
серверами, але будь-який ігровий сервер може бути вразливим для атаки.
Арк.
ЧДТУ.21.20106.009 ПЗ 87
Змн. Арк. № докум. Підпис Дата
Метою розподіленої атаки типу «відмова в обслуговуванні» (DDoS –
distributed denial-of-service attack) є перевантаження сервера запитами, які він
може успішно виконати, що зрештою робить сервер недоступним чи інакше
непридатним використання законними користувачами. Причина, через яку це
працює, полягає в тому, що занадто багато вхідних даних або перевантажать
мережне з’єднання сервера, або витратить стільки обчислювальної потужності,
що сервер не зможе впоратися з фактичними запитами. Практично кожна велика
мережна гра або онлайн-сервіс для геймерів у той чи інший момент піддавалися
DDoS-атакам.
Якщо ви використовуєте власне обладнання для ігрових серверів, захист
від DDoS-атак може бути складним та стомлюючим. Це включає тісну співпрацю
з вашим інтернет-провайдером, а також можливе оновлення обладнання і
розподіл трафіку між різними серверами. З іншого боку, якщо ви використовуєте
рішення хмарного хостингу для своїх серверів, частина роботи із запобігання
DDoS-атакам виконується постачальником хмари. Усі основні платформи
хмарного хостингу мають певний рівень захисту від DDoS-атак. При цьому
ніколи не слід припускати, що провайдер хмарного хостингу повністю запобігає
можливості DDoS-атак – розсудливо все ж таки витратити час на планування та
тестування різних стратегій пом’якшення наслідків.
Ви також повинні враховувати, що зловмисник може спробувати надіслати
неправильні або неправильні пакети на сервер. Це може бути зроблено з ряду
причин, але найпростіша причина – користувач намагається вивести сервер з
ладу. Однак більш підступний користувач може спробувати змусити сервер
виконати шкідливий код шляхом переповнення буфера пакетів або аналогічної
атаки.
Один з найкращих способів захистити вашу гру від невірних даних – це
використовувати автоматичне тестування, яке називається нечітким
тестуванням. Як правило, нечітке тестування використовується для виявлення
помилок коду, які при звичайному модульному тестуванні або тестуванні якості
не виявляються. Для мережної гри ви повинні використовувати нечітке
Арк.
ЧДТУ.21.20106.009 ПЗ 88
Змн. Арк. № докум. Підпис Дата
тестування для надсилання великих обсягів неструктурованих даних на сервер.
Мета полягає в тому, щоб побачити, чи спричинить відправлення цих даних на
сервер до його збою, і виправити будь-які помилки, виявлені процесом.
Щоб знайти найбільшу кількість помилок, рекомендується
використовувати як повністю рандомізовані, так і більш структуровані дані,
наприклад, пакети, що містять очікувані сигнатури, навіть якщо інше корисне
навантаження є випадковим і неструктурованим. За допомогою безлічі ітерацій
нечіткого тестування та виправлення помилок, виявлених за допомогою
нечіткого тестування, ви можете спробувати звести до мінімуму ймовірність
того, що ваша гра буде вразлива для невірних даних.
Висновки до розділу 3
Висновок: Більшості розробників розрахованих на багато користувачів
ігор потрібно піклуватися про безпеку на якомусь рівні. Насамперед слід
враховувати безпеку передачі даних. Оскільки пакети можуть бути перехоплені,
важливо, щоб конфіденційна інформація, така як паролі та платіжна інформація,
була зашифрована. Рекомендований підхід полягає у використанні деякої форми
криптографії з відкритим ключем, такий як RSA для шифрування даних. Для
даних, які стосуються лише стану гри, корисно мінімізувати обсяг даних, що
відправляються. Це особливо корисно для зменшення читерства в іграх клієнт-
сервер, оскільки дає клієнтам менше інформації для роботи.
Перевірка введення також є важливою, щоб гарантувати, що жоден
користувач не виконує заборонену дію. Неправильне введення не завжди може
бути пов’язане з читерством – у грі клієнт-сервер можливо, що клієнт просто не
отримав останні оновлення при відправці команди. При цьому важливо, щоб усі
команди, що надсилаються по мережі, були перевірені. Це може працювати як
для сервера, який перевіряє введення клієнта, так і для однорангового вузла, що
перевіряє введення іншого однорангового вузла. У разі перевірки даних із
сервера надійний вибір – заборонити гравцям розміщувати свої власні сервери.
Арк.
ЧДТУ.21.20106.009 ПЗ 89
Змн. Арк. № докум. Підпис Дата
Типове програмне забезпечення для виявлення читів активно скануватиме
пам’ять комп’ютера, на якому запущена гра, щоб визначити, чи працюють також
якісь відомі чит-програми.
Арк.
ЧДТУ.21.20106.009 ПЗ 90
Змн. Арк. № докум. Підпис Дата
ЗАГАЛЬНІ ВИСНОВКИ
Реальний світ – жахливе місце для розрахованих на багато користувачів
ігор. Гравці хочуть отримати негайний зворотний зв’язок від своїх дій, і сили
природи діють, щоб запобігти цьому. Навіть без мережного компонента
відеоігор доводиться мати справу з багатьма джерелами затримки, включаючи
затримку вибірки вхідних даних, затримку рендерингу та затримку на основі
відображення. З додаванням фізичної мережі, розраховані на багато
користувачів ігри також повинні мати справу з затримкою, пов’язаною із
затримкою поширення, затримкою передачі, затримкою обробки і затримкою в
черзі.
Нестійкі мережеві умови призводять до того, що пакети прибувають пізно,
виходять з ладу або взагалі не приходять. Щоб отримати задоволення від гри,
вам знадобиться надійна передача даних певного рівня для пом’якшення цих
проблем. Один із способів гарантувати надійну передачу – використовувати
транспортний протокол TCP. Він працює для ігор, яким потрібна надійна
передача всіх своїх даних, але не підходить для типових ігор, які більше
піклуються про актуальні дані, ніж про абсолютно надійні дані. Для цих ігор UDP
– найкращий вибір через гнучкість, яку він пропонує.
При використанні топології клієнт-сервер з авторитетним сервером,
нечастими оновленнями та затримкою в мережі все одно потрібно давати
гравцям ілюзію безперервності та плавності руху. Я дослідив спосіб показати рух
гравця, керованого користувачем, у режимі реального часу, використовуючи
прогнозування на стороні клієнта та узгодження на сервері, це гарантує, що
введення користувача негайно впливає на локального гравця, усуваючи
затримку, яка може зіпсувати враження від гри.
Інтерполяція сутностей взагалі не передбачає майбутніх позицій – він
використовує тільки реальні дані сутностей, надані сервером, таким чином
показуючи інші сутності з невеликою затримкою в часі.
Арк.
ЧДТУ.21.20106.009 ПЗ 91
Змн. Арк. № докум. Підпис Дата
Типові завдання сервера:
а) Сполучна точка для гравців. У розрахованій на багато користувачів грі
гравцям потрібна загальна кінцева точка для зв’язку один з одним. Це одна із
ролей серверної програми. Навіть у моделі зв’язку P2P є сполучна точка для
обміну мережевою інформацією для встановлення з’єднання P2P.
б) Обробка інформації. У багатьох випадках сервер виконує код симуляції
гри, обробляє всі дані, що вводяться гравцями, і оновлює стан гри, але саме
сервер несе відповідальність за обробку гри, тобто, наприклад, за створення
тактів гри.
в) Єдине джерело істинного стану гри. У багатьох розрахованих на багато
користувачів іграх серверна програма також має владу над станом гри. Основна
причина цього – захист від читерства. Крім того, набагато легше орієнтуватись,
коли є єдина точка для отримання правильного стану гри.
Проведено огляд внутрішньої роботи інтернету, пакету TCP/IP, розглянуто
п’ять рівнів мережевої моделі OSI, кожен з яких забезпечує канал даних для
вищого рівня, транспортний рівень, його порти та протоколи передачі даних
(TCP та UDP) які забезпечують наскрізний зв’язок між процесами на віддалених
хостах. Для обміну, як правило, використовується два сокети – сокет відправника
та сокет одержувача. Кожен процес може слухати сокет і прив’язати його до
якого-небудь порту операційної системи (процеси не можуть використовувати
порти менше 1024).
Розглянуто проблему надсилання даних, кодування інформації та
проблему підвищення ефективності прийому і обслуговування клієнта на
серверній стороні. Досліджено клієнт-серверну і однорангову топології мережі
та їх особливості роботи, взаємодії з клієнтами, забезпечення узгодженості між
клієнтами та захисту від читерства, метод шифрування RSA, перевірка ведення
клієнта на сервері та методи захисту від злому гри на стороні клієнта.
Усі вимоги технічного завдання виконано, мета випускної роботи –
досягнута.
Арк.
ЧДТУ.21.20106.009 ПЗ 92
Змн. Арк. № докум. Підпис Дата
Список використаних джерел
1. Э. Хант, Д. Томас. Программист-прагматик. Путь от подмастерья к
мастеру (англ. The Pragmatic Programmer: From Journeyman to Master) : [Текст], :
Москва, издательство: “Лори”, 2009, 270 с.;
2. Джон Шарп. Microsoft Visual C#. Подробное руководство. 8-е
издание (англ. Microsoft Visual C#: Step by Step) : [Текст], : Санкт-Петербург,
издательство: “Питер”, 2017, 848 с.;
3. Албахари Бен, Албахари Джозеф. C# 7.0. Справочник. Полное
описание языка (англ. C# 7.0 in a Nutshell) : [Текст], : Москва, издательство:
“Вильямс”, 2018, 1024 с.
4. Эндрю Троелсен, Филипп Джепикс. Язык программирования C# 7 и
платформы .NET и .NET Core. 8-е издание (англ. Pro C# 7: With .NET and .NET
Core, 8th edition) : [Текст], : Киев, издательство “Диалектика”, 2019, 1000 с.;
5. Джон Скит. C# для профессионалов: тонкости программирования, 3-
е издание (англ. C# in Depth, 3rd Edition) : [Текст], : Москва, издательство
“Вильямс”, 2019, 608 с.;
6. Адам Фримен. ASP.NET Core MVC с примерами на C# для
профессионалов (англ. Pro ASP.NET Core MVC) : [Текст], : Москва, издательство
“Вильямс”, 2017, 992 с.;
7. Эрик Фримен, Элизабет Робсон, Кэти Сьерра, Берт Бейтс. Head First.
Паттерны проектирования (англ. Head First Design Patterns: A Brain-Friendly
Guide) : [Текст], : Санкт-Петербург, издательство “Питер”, 2018, 656 с.;
8. Рой Ошероув. Искусство автономного тестирования с примерами на
С# (англ. The Art Of Unit Testing Second Edition With Examples In C#) : [Текст], :
Москва, издательство “ ДМК Пресс”, 2016, 360 с.;
9. Клири Стивен. Конкурентность в C# (англ. Concurrency in C#
Cookbook) : [Текст], : Санкт-Петербург, издательство “Питер”, 2016, 760 с.;
Арк.
ЧДТУ.21.20106.009 ПЗ 93
Змн. Арк. № докум. Підпис Дата
10. Рихтер Джеффри. CLR via C#. Программирование на платформе
Microsoft .NET Framework 4.5 на языке C# (англ. CLR via C#) : [Текст], : Санкт-
Петербург, издательство “Питер”, 2019, 896 с.;
11. Прайс Марк. C# 9 и .NET 5. Разработка и оптимизация (англ. C# 9
and .NET 5 – Modern Cross-Platform Developmentю 5th Edition) : [Текст], : Санкт-
Петербург, издательство “Прогресс книга”, 2018, 832 с.;
12. Шилдт Герберт. C# 4.0: полное руководство (англ. C# 4.0: The
Complete Reference) : [Текст], : Москва, издательство “Вильямс”, 2016, 1056 с.;
13. Джон Пол Мюллер, Чак Сфер. Для чайников C# (англ. C# 2008 For
Dummies) : [Текст], : Киев, издательство “Диалектика”, 2016, 608 с.;
14. Санджай Мадхав, Джошуа Глейзер. Многопользовательские игры.
Разработка сетевых приложений (англ. Multiplayer Game Programming:
Architecting Networked Games (Game Design)) : [Текст], : Санкт-Петербург,
издательство “Питер”, 2017, 368 с.;
15. Michael J. Donahoo, Kenneth L. Calvert. TCP/IP Sockets in C: Practical
Guide for Programmers (англ. the same title) : [Текст], : -, издательство “-”, 2002,
160 с.;
16. Kenneth L. Calvert (Author), Michael J. Donahoo. TCP/IP Sockets in
Java: Practical Guide for Programmers (англ. the same title) : [Текст], : -,
издательство “-”, 2001, 72 с..
Арк.
ЧДТУ.21.20106.009 ПЗ 94
Змн. Арк. № докум. Підпис Дата