Поиск по сайту Перейти на главную страницу
Second Life Inside. Все о второй жизни.

Написание игрового сервера

1. Введение

Наверняка, Вы играли в такую игру как “Warcraft 3”. И было бы просто прекрасно, если Вы играли по интернету, ибо в этом случае Вы бы могли созерцать и испытать в действии то, что называется “Battle.net”. В любом случае я поясню. Это некий “портал” благодаря которому игроки всего интернета могут запросто найти работающие игровые сервера не выходя из игры. Что значительно облегчает им жизнь, т.к. отпадает необходимость заранее договариваться с соперниками при помощи чатов и подобных средств...

То о чём я буду говорить в этой статье, поможет Вам создать подобное для своей игрушки. Сам метод достаточно прост и почти не имеет отрицательных моментов. Из-за отсутствия информации по данной теме мне пришлось самому, методом проб и ошибок, писать подобный портал (далее “арена”) для своего проекта TFK (http://timeforkill.mirgames.ru)
2. Описание метода.

Итак, опишу то что нам понадобится для реализации.

Хостинг с поддержкой php
Ваша игра с работоспособным сетевым кодом

Первый пункт я надеюсь не вызывает больших проблем у начинающих “игрописателей”, т.к. существует множество сайтов, предоставляющих бесплатный домен с поддержкой php. К примеру, для TFK он был предоставлен сайтом http://mirgames.ru

А вот со вторым пунктом придётся немного попариться, впрочем, это уже тема для отдельной статьи...

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

Итак, имеем в интернете домен на котором размещён наш скрипт “арены”. Есть игра-клиент, которой нужно узнать кол-во доступных серверов, и при необходимости создать свой.

Что нам нужно от “арены”? Всего-навсего получить список серверов в виде “IP:Port IP:Port IP:Port...” и зарегистрировать новый.

Как это будет происходить? Да очень просто! Посредством HTTP запросов.

Так как нет идеальных решений, какие минусы у данного метода?

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

А какие же плюсы?

Относительная простота реализации
Легко разместить такую арену в локальной сети
Не требует восстановления после различных ЧП :)

3. Реализация

В этом разделе описаны основные процедуры необходимые для воплощения нашей мечты в реальность. Работа с ареной делится на 2 части:

Подача HTTP запросов и обработка ответов игрой
<Обработка запроса скриптом на арене

Всего будет 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
Ответ нам абсолютно не нужен :)
4. Реализация на стороне игры

Соответственно нам теперь необходимо знать как отправить HTTP запрос и получить на него ответ. Всё проще чем может показаться. Приведу всего одну процедуру использующую возможности WinSock:
Код на языке Delphi
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.

Первый является именем запроса, а второй означает нужен ли нам результат обработки запроса. Соответственно вызов этой функции для наших двух запросов будет выглядеть следующим образом:
Код на языке Delphi
Str := Arena('view', true); // для получения списка серверов
Arena('ping', false); // сообщить арене что наш сервер живее живых

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

Приведу пример:
Код на языке Delphi
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” игра должна разослать им запросы о их текущем состоянии (карта, игроки и т.д.) В этот момент отбрасываются “умершие” сервера, ибо ответа от них не прийдёт.
5. Реализация на стороне интернет сервера

Итак, с игрой разобрались, теперь осталось написать скрипт!

В запросах мы посылаем ключевое слово “action=arena” благодаря чему помимо арены на данном домене может висеть полноценный сайт.

Для того, чтобы определить адресуется ли данный запрос арене, в index.php необходимо (желательно в самом начале) написать следующее:
Код на языке PHP
if ($action == 'arena') {
include 'arena.php';
die();
}

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

А вот и сам код arena.php:
Код на языке 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]."n");
fflush($f);
flock($f, LOCK_UN);
fclose($f);
?>;

Файл со списком серверов должен находиться в “db/arena_list.txt” с атрибутами разрешающими его изменение.

Вот собственно и всё! Дальше дело стоит за Вашей фантазией...

Если заметите какие-либо ошибки или недоработки данной реализации – буду рад Вас выслушать.

Удачи!

Источник: lj wacko

Тэги:


Вход в систему

2007 - 2014 © slinside.ru. Все права защищены.
При использовании материалов сайта, ссылка на slinside.ru обязательна.