Написання ігрового інтернет сервера, Різне, Програмування, статті

Введення


Напевно, Ви грали в таку гру як “Warcraft 3”. І було б просто чудово, якщо Ви грали по Інтернету, бо в цьому випадку Ви б могли споглядати і випробувати в дії те, що називається “Battle.net”. У будь-якому випадку я поясню. Це такий собі “портал” завдяки якому гравці усього Інтернету можуть запросто знайти працюючі ігрові сервера не виходячи з гри. Що значно полегшує їм життя, тому що відпадає необхідність заздалегідь домовлятися з суперниками за допомогою чатів і подібних засобів.


Те, про що я буду говорити в цій статті, допоможе Вам створити подібне для своєї іграшки. Сам метод досить простий і майже не має негативних моментів. Через відсутність інформації з даної теми мені довелося самому, методом проб і помилок, писати подібний портал (далі “арена”) для свого проекту TFK.


Опис методу



Отже, опишу те що нам знадобиться для реалізації:
– Хостинг з підтримкою php;
– Ваша гра з працездатним мережевим кодом :).
Перший пункт я сподіваюся не викликає великих проблем у початківців “ігропісателей”, тому що існує безліч сайтів, що надають безкоштовний домен з підтримкою php.
А ось з другим пунктом доведеться трохи попаритися, втім, це вже тема для окремої статті …
Так як в даній статті я використовував PHP, то буде потрібно знання його основ. Втім, при бажанні, переклад на іншу мову написання web сторінок не складе великих труднощів.


Отже, маємо в Інтернеті домен на якому розміщений наш скрипт “арени”. Є гра-клієнт, якої потрібно дізнатися кількість доступних серверів, і при необхідності створити свій.
 Що нам потрібно від “арени”? Всього лише отримати список серверів у вигляді “IP: Port IP: Port IP: Port …” і зареєструвати новий.
 Як це буде відбуватися? Та дуже просто! За допомогою HTTP запитів.
 Так як немає ідеальних рішень, які мінуси у даного методу?
– Сервери знаходяться за шлюзом не буде видно іншим клієнтам, тому що навіть сама гра-сервер без поняття на якому external порту вона висить.
– При падінні хостера (сайту) арена шльопнеться разом з ним! Але це відноситься вже до форс-мажорних обставин … ;).
 А які ж плюси?
– Відносна простота реалізації;
– Легко розмістити таку арену в локальній мережі;
– Не вимагає відновлення після різних НП :).

Реалізація



У цьому розділі описані основні процедури необхідні для втілення нашої мрії в реальність. Робота з ареною ділиться на 2 частини:
1) Подача HTTP запитів та обробка відповідей грою;
2) Обробка запиту скриптом на арені.


Всього буде 2 види запитів: view і ping.
   VIEW необхідний для отримання списку серверів. Буде виглядати наступним чином:
Запит: http://host/?action=arena&mode=view.
Відповідь: 212.100.15.45:25666 192.10.38.212:25666.
Тобто у відповіді ми бачимо, що на даний момент на арені знаходяться 2 сервера на портах 25666.

   PING для оповіщення арени про те що сервер живий і видаляти його зі списку поки немає ніякої необхідності. Ви могли помітити те, що немає запиту на реєстрацію сервера на арені, тому що в якості реєстрації виступає постійний “ping” посилається ім. Сам же запит “ping” слід надсилати раз на кілька десятків секунд (20-40).
Запит: http://host/?action=arena&mode=ping&port=25666
Відповідь нам абсолютно не потрібен :).

Реалізація на стороні гри



Відповідно нам тепер необхідно знати як відправити HTTP запит і отримати на нього відповідь. Все простіше ніж може здатися. Наведу лише одну процедуру використовує можливості WinSock:
 

 function Arena(const mode: string; get: boolean): string;
const
host = “host.ru”;
port = 25666;
var
wData : WSADATA;
addr : sockaddr_in;
sock : integer;
error : integer;
buf : array [0..1023] of Char;
str : string;
phe : PHostEnt;
begin / / Ініціалізація сокета
Result := “”;
WSAStartup($0101, wData);
phe := gethostbyname(PChar(string(host)));
if phe = nil then
begin
WSACleanup;
Exit;
end;
sock := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if sock = INVALID_SOCKET then
begin
WSACleanup;
Exit;
end;
addr.sin_family := AF_INET;
addr.sin_port := htons(80);
addr.sin_addr := PInAddr(phe.h_addr_list^)^;
error := connect(sock, addr, sizeof(addr));
if error = SOCKET_ERROR then
begin
closesocket(sock);
WSACleanup;
Exit;
end;
/ / Складаємо рядок запиту
str := “GET http://” + host + “/?action=arena&mode=” + mode;
if mode = “ping”
then str := str + “&port=” + IntToStr(port);
str := str + ” HTTP/1.0″#13#10#13#10;
/ / Відправляємо
send(sock, str[1], Length(str), 0);
/ / Якщо потрібна відповідь то приймаємо
if get then
begin
ZeroMemory(@buf, 1024);
error := recv(sock, buf, 1024, 0);
while error > 0 do
begin
Result := Result + Copy(buf, 0, error);
error := recv(sock, buf, 1024, 0);
end;
end; / / Закриваємо сокет – завершуємо роботу з мережею
closesocket(sock);
WSACleanup;
/ / Вирізуємо з відповіді то що нам потрібно, тобто відрізаємо / / HTTP заголовки
if get and Result <> “”
then Result:=Copy(Result, pos(#13#10#13#10, Result)+4,
Length(Result));
end;

У функцію передається лише 2 параметра mode і get.
Перший є ім’ям запиту, а другий означає потрібен нам результат обробки запиту. Відповідно виклик цієї функції для наших двох запитів буде виглядати наступним чином:
 

 Str: = Arena (“view”, true); / / для отримання списку серверів  Arena (“ping”, false); / / повідомити арені що наш сервер / / Живіший за всіх живих 

При виклику цієї функції гра на деякий час може підвиснути. Для того, щоб уникнути цього неподобства можна скористатися потоками. Функція працює в потоці практично ніяк не впливатиме на діяльність ігри, але виникає ризик некоректного доступу до загальних ресурсів для гри і потоку.
Наведу приклад:
 

 procedure Arena_PingThread;
begin
Arena(“ping”, false);
end;

procedure Arena_Ping;
var
id : DWORD;
begin
CreateThread(nil, 128, @Arena_PingThread, nil, 0, id);
end;


Після отримання списку серверів запитом “view” гра повинна розіслати їм запити про їх поточний стан (карта, гравці і т.д.) У цей момент відкидаються “померлі” сервера, бо відповіді від них не прийде.

Реалізація на стороні інтернет сервера



Отже, з грою розібралися, тепер залишилося написати скрипт!
У запитах ми посилаємо ключове слово “action = arena” завдяки чому крім арени на даному домені може висіти повноцінний сайт.
Для того, щоб визначити адресується даний запит арені, в index.php необхідно (бажано в самому початку) написати наступне:
 

 if ($action == “arena”)
{
include “arena.php”;
die();
}

Це означає, що в разі того, коли захочуть “поспілкуватися” з ареною, буде запущений скрипт арени для обробки запиту і подальше виконання скрипта index.php припиниться.

А ось і сам код arena.php:
 

 <?php / / У цьому файлі буде зберігатися список активних серверів
$list_file = “db/arena_list.txt”; / / Дізнаємося IP адреса відправника запиту
$ip = $_SERVER[“REMOTE_ADDR”]; / / Читаємо номер порту із запиту
$port = intval($_REQUEST[“port”]); / / Це від хитрих кулхацкер 😉
if (!($port >= 1024 && $port <= 65500))
$port = 25666; / / Читаємо файл-список
$lst = file($list_file); / / В змінної $ time тепер зберігається поточний час
$time = time();
$j = -1;
$i = 0; / / Видаляємо “мерців” і попутно шукаємо адреса відправника / / В цьому списку
while ($i < count($lst)) {
$lst[$i] = trim($lst[$i]);
list($l_ip, $l_port, $l_time) = explode(“:”, $lst[$i]); / / Якщо час з попереднього пінгу перевищило 45 секунд / / – Його явно вже немає
if ($l_time < ($time – 45)) {
for ($t = $i; $t < count($lst) – 1; $t++)
$lst[$t] = $lst[$t + 1];
unset($lst[count($lst) – 1]);
continue;
}
if ($l_ip == $ip) $j = $i;
$i++;
}
/ / Обробка запиту
switch ($mode) {
case “view”:
for ($i = 0; $i < Count($lst); $i++) { / / Висновок чергового IP: Port зі списку
list($l_ip, $l_port, $l_time) = explode(“:”, $lst[$i]);
echo $l_ip.”:”.$l_port.” “;
}
break;
case “ping”:
if ($j == -1) / / Якщо пінгуєтся вперше, значить новий сервер – додаємо
array_push($lst, $ip.”:”.$port.”:”.$time);
else { / / Оновлюємо інформацію для сервера / / Зауважте, що при зміні порту на сервері / / На арені він теж зміниться
list($l_ip, $l_port, $l_time) = explode(“:”, $lst[$j]);
$lst[$j] = $l_ip.”:”.$port.”:”.$time;
}
break;
}
/ / Оновлюємо список серверів у файлі-списку
$f = fopen($list_file, “a+”);
flock($f, LOCK_EX);
ftruncate($f, 0);
for ($i = 0; $i < count($lst); $i++)
fwrite($f, $lst[$i].”
“);
fflush($f);
flock($f, LOCK_UN);
fclose($f);
?>

Файл зі списком серверів повинен перебувати в “db / arena_list.txt” з атрибутами вирішуючими його зміну.
Ось власне і все! Далі справа стоїть за Вашою фантазією …
Якщо помітите якісь помилки або недоробки даної реалізації – буду радий Вас вислухати.

Удачи!

Схожі статті:


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

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*