Russian version
English version
ОБ АЛЬЯНСЕ | НАШИ УСЛУГИ | КАТАЛОГ РЕШЕНИЙ | ИНФОРМАЦИОННЫЙ ЦЕНТР | СТАНЬТЕ СПОНСОРАМИ SILICON TAIGA | ISDEF | КНИГИ И CD | ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ | УПРАВЛЕНИЕ КАЧЕСТВОМ | РОССИЙСКИЕ ТЕХНОЛОГИИ | НАНОТЕХНОЛОГИИ | ЮРИДИЧЕСКАЯ ПОДДЕРЖКА | АНАЛИТИКА | КАРТА САЙТА | КОНТАКТЫ
 
Программное обеспечение
 
Для зарегистрированных пользователей
 
РАССЫЛКИ НОВОСТЕЙ
IT-Новости
Новости компаний
Российские технологии
Новости ВПК
Нанотехнологии
 
Поиск по статьям
 
RSS-лента
Подписаться
Средства разработки

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

Введение

Наверняка, Вы играли в такую игру как "Warcraft 3". И было бы просто прекрасно, если Вы играли по Интернету, ибо в этом случае Вы бы могли созерцать и испытать в действии то, что называется "Battle.net". В любом случае я поясню. Это некий "портал" благодаря которому игроки всего Интернета могут запросто найти работающие игровые сервера не выходя из игры. Что значительно облегчает им жизнь, т.к. отпадает необходимость заранее договариваться с соперниками при помощи чатов и подобных средств.
То, о чем я буду говорить в этой статье, поможет Вам создать подобное для своей игрушки. Сам метод достаточно прост и почти не имеет отрицательных моментов. Из-за отсутствия информации по данной теме мне пришлось самому, методом проб и ошибок, писать подобный портал (далее "арена") для своего проекта TFK ( http://timeforkill.mirgames.ru ).

Описание метода

Итак, опишу то что нам понадобится для реализации:
- Хостинг с поддержкой php;
- Ваша игра с работоспособным сетевым кодом :).
Первый пункт я надеюсь не вызывает больших проблем у начинающих "игрописателей", т.к. существует множество сайтов, предоставляющих бесплатный домен с поддержкой php. К примеру, для TFK он был предоставлен сайтом http://mirgames.ru .
А вот со вторым пунктом придется немного попариться, впрочем, это уже тема для отдельной статьи...
Так как в данной статье я использовал 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:

 = 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" с атрибутами разрешающими его изменение.
Вот собственно и все! Дальше дело стоит за Вашей фантазией...
Если заметите какие-либо ошибки или недоработки данной реализации - буду рад Вас выслушать.

Удачи!


  Рекомендовать страницу   Обсудить материал Написать редактору  
  Распечатать страницу
 
  Дата публикации: 15.03.2006  

ОБ АЛЬЯНСЕ | НАШИ УСЛУГИ | КАТАЛОГ РЕШЕНИЙ | ИНФОРМАЦИОННЫЙ ЦЕНТР | СТАНЬТЕ СПОНСОРАМИ SILICON TAIGA | ISDEF | КНИГИ И CD | ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ | УПРАВЛЕНИЕ КАЧЕСТВОМ | РОССИЙСКИЕ ТЕХНОЛОГИИ | НАНОТЕХНОЛОГИИ | ЮРИДИЧЕСКАЯ ПОДДЕРЖКА | АНАЛИТИКА | КАРТА САЙТА | КОНТАКТЫ

Дизайн и поддержка: Silicon Taiga   Обратиться по техническим вопросам  
Rambler's Top100 Rambler's Top100