|
|
|||||||||||||||||||||||||||||
|
Написание игрового интернет сервераИсточник: delphisources XProger
ВведениеНаверняка, Вы играли в такую игру как "Warcraft 3". И было бы просто прекрасно, если Вы играли по Интернету, ибо в этом случае Вы бы могли созерцать и испытать в действии то, что называется "Battle.net". В любом случае я поясню. Это некий "портал" благодаря которому игроки всего Интернета могут запросто найти работающие игровые сервера не выходя из игры. Что значительно облегчает им жизнь, т.к. отпадает необходимость заранее договариваться с соперниками при помощи чатов и подобных средств. То, о чем я буду говорить в этой статье, поможет Вам создать подобное для своей игрушки. Сам метод достаточно прост и почти не имеет отрицательных моментов. Из-за отсутствия информации по данной теме мне пришлось самому, методом проб и ошибок, писать подобный портал (далее "арена") для своего проекта TFK Описание метода
Итак, имеем в Интернете домен на котором размещен наш скрипт "арены". Есть игра-клиент, которой нужно узнать кол-во доступных серверов, и при необходимости создать свой. Реализация
Всего будет 2 вида запросов: view и ping. PING для оповещения арены о том что сервер жив и удалять его из списка пока нет никакой необходимости. Вы могли заметить то, что нет запроса на регистрацию сервера на арене, т.к. в качестве регистрации выступает постоянный "ping" посылаемый им. Сам же запрос "ping" следует посылать раз в несколько десятков секунд (20-40). Реализация на стороне игры
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" игра должна разослать им запросы о их текущем состоянии (карта, игроки и т.д.) В этот момент отбрасываются "умершие" сервера, ибо ответа от них не придет. Реализация на стороне интернет сервера
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]."\n"); fflush($f); flock($f, LOCK_UN); fclose($f); ?> Файл со списком серверов должен находиться в "db/arena_list.txt" с атрибутами разрешающими его изменение. Удачи! Ссылки по теме
|
|