Michał Środek

Po prostu devBlog

Witaj na srodek.info

Jest to blog poświęcony nowoczelnym technologiom ułatwiającym tworzenie aplikacji internetowych. Znajdziesz tutaj porady na temat CSS3, JavaScript, designu, web-usability, standardów W3C.

Cześć! Nazywam się Michał Środek. Z zawodu programista php, z zamiłowania gitarzysta oraz fanatyk GNU/Linuksa(openSUSE® w laptopie). W branży aplikacji internetowych od 9 lat. Prywatnie bez dzieci i kota.

Pracuję wciąż nad własnym elastycznym i wydajnym frameworkiem MVC, kilkoma portalami internetowymi oraz mniejszymi bibliotekami php. Czekam na wasze opinie, zgłoszenia błędów oraz pomysły na dalszy rozwój.

Ta część strony jest w trakcie budowy a moje prace tymczasowo niedostępne.

W przypadku pytań, ofert pracy oraz ciekawych pomysłów proszę się ze mną kontaktować. Możesz mnie znaleźć i wysłać PW na php.pl(SHiP), jamendo.com(michalsrodek), goldenLine.pl, facebook.com lub nk.pl

14 Maj 2010

Serwer gry. Część druga

Filed under: Gry,Moje projekty,PHP — Michał Środek @ 21:40

Spró­bujmy udo­sko­na­lić nasz ser­wer tak aby posia­dał moż­li­wo­ści pro­stego chatu — wysy­ła­nie wia­do­mo­ści ogól­nych do wszyst­kich użyt­kow­ni­ków oraz pry­wat­nych tylko do jed­nej osoby. Mój kod urósł pra­wie do 150 lini­jek przy czym nie uwzględ­nia on kilku rze­czy, o któ­rych wspo­mnę pod­czas pisa­nia pro­to­kołu lub two­rze­nia klientów.(w końcu nie piszę MUD-a i tel­net muszę zastą­pić czymś innym).

Popra­wiony kod wygląda tak:

<?php 
set_time_limit(0);
 
class GameClient
{
   public $id;
   private $socket;
 
   public function __construct(&$socket, $id)
   {
      $this->socket = $socket;
      $this->id = $id;
   }
 
   public function sendMessage($message)
   {
      socket_write($this->socket, $message."\n".chr(0));
      echo 'Wysylam wiadomosc do '.$this->id.': '.$message."\n";
   }
 
}
 
class GameServer
{
   private $master;
   private $address; 
   private $port; 
   private $maxClients;
   private $sockets = array();
   private $clients = array();
 
   public function __construct($address = '127.0.0.123', $port=14117, $maxClients = 10)
   {
      $this->address = $address;
      $this->port = $port;
      $this->maxClients = $maxClients;
 
      $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
      socket_bind($this->master, $this->address, $this->port);
      socket_listen($this->master);
      $this->sockets[] = $this -> master;
 
      while(true)
      {
         $changedSockets = $this->sockets;
         socket_select($changedSockets,$write=null,$exceptions=null,null);
 
         foreach($changedSockets as $socket)
         {
            // zmiana gniazda głównego => nowe polaczenie
            if($socket == $this->master)
            {
               $socket = socket_accept($this->master); 
 
               $this->sockets[] = $socket;
               $id = array_search($socket, $this->sockets);
                $this->clients[] = new GameClient(&$this->sockets[$id], $id);
 
            }
            // zmiana gniazda jednego z klientów
            else
            {
               $input = socket_read($socket, 1024); 
               $input = trim($input," \t\n\r");
 
               if(strlen($input)==0 || $input=='exit')
               {
                  $output = 'Bye bye!'."\n".chr(0); 
 
                  // send data to socket 
                  socket_write($socket, $output);
 
                  $id = array_search($socket, $this->sockets);
                  unset($this->sockets[$id]);
                  unset($this->clients[$id-1]);
 
                  socket_close($socket);
               }
               else
               {
                  $command = (strpos($input,' ')!==false)
                      ? substr($input,0, strpos($input,' '))
                      : $input;
 
                  switch($command)
                  {
                     case 'hello':
                        $output = 'Hello!';
                        break;
                     case 'list':
                        // identyfikatory wszystkich gniazd
                        $all = array_keys($this->sockets);
                         // identyfikator uzytkownika
                        $id = array_search($socket, $this->sockets);
                        // usuniecie ID glownego gniazda oraz gniazda uzytkownika
                        $all = array_diff($all, Array(0, $id));
 
                        if(empty($all))
                           $output='Nie ma innych osob';
                        else
                           $output='ID innych osob to: '.implode(', ',$all);
                        break;
                     default:
                        if(is_numeric($command))
                        {
                           // identyfikator uzytkownika
                           $id = array_search($socket, $this->sockets);
                           // wyslanie wiadomosci do wszystkich
                           if($command=='0')
                           {
                              foreach($this->clients as $client)
                              {
                                 // nie wysylamy do siebie
                                 if($client->id!=$id)
                                    $client->sendMessage(substr($input,strpos($input,' ')+1));
                              }
                              $output = 'Do wszystkich';
                           }
                           // nie wysylamy do siebie, uzytkownik istnieje
                           elseif($command!=$id && isset($this->sockets[$command]))
                           {
                              // jeden mniejszy ponieważ $this->master znajduje sie
                              // w tablicy gniazd a nie jest klientem
                              $this->clients[$command-1]->sendMessage(substr($input,strpos($input,' ')+1));
                              $output = 'OK';
                           }
                           else
                              $output ='Bledny ID'; 
                        }
                        else
                           $output = 'Niezrozumiale polecenie: '.$input.''; 
                  }
                  socket_write($socket, $output."\n".chr(0));
               }
            }
         }
      }
   }
 
   public function __desctruct()
   {
      socket_close($this->master); 
   }
}
 
$gameServer = new GameServer();
?>

Czas na krótką ana­lizę. Pierw­sze co rzuca się w oczy to nowa klasa GameC­lient. Jest to klasa, z którą wiążę pewne plany. Obiekty tej klasy prze­cho­wują dwie rze­czy — gniazdo oraz jego iden­ty­fi­ka­tor. Metoda send­Mes­sage() służy do wysy­ła­nia wia­do­mo­ści do użyt­kow­nika, który jest pod­pięty do tego gniazda.

W kla­sie Game­Se­rver poja­wiły się dwa nowe pola pry­watne, tablica soc­kets prze­cho­wu­jąca wszyst­kie gniazda dostępne dla ser­wera oraz tablicę clients prze­cho­wu­jącą obiekty klasy GameC­lient.

Ogólne zało­że­nia się nie zmie­niły — wciąż ist­nieje pętla nie­skoń­czona jed­nak jej zawar­tość została zmodyfikowana.

$changedSockets = $this->sockets;
socket_select($changedSockets,$write=null,$exceptions=null,null);

Uży­li­śmy tutaj funk­cji socket_select(). W para­me­trach poda­jemy referencje(dlatego pierw­sza linijka jest konieczna) do tablic gniazd, a funk­cja pozo­sta­wia w nich jedy­nie te ele­menty, w któ­rych zaszły pewne zda­rze­nia. Pierw­szy para­metr odpo­wiada czy czy­ta­nie, czyli wybrane zostaną jedy­nie te gniazda, które coś prze­sy­łają do ser­wera. Drugi i trzeci para­metr odpo­wia­dają za pisa­nie oraz wyjątki lecz nas one nie inte­re­sują. Ostatni para­metr doty­czy limitu czasu ocze­ki­wa­nia na wybra­nie poszcze­gól­nych gniazd. Null ozna­cza brat limitu czasowego.

Następ­nie spraw­dzamy każde z gniazd z tablicy $chan­ged­Soc­kets. Jeśli gniazdo jest tym samym co gniazdo główne ser­wera ozna­cza to, że nowy użyt­kow­nik chce usta­no­wić połą­cze­nie. Robimy to two­rząc nowe gniazdo i dopi­su­jąc infor­ma­cje o nim do tablic $this->sockets oraz $this->clients. W przy­padku innych gniazd odczy­ty­wane są dane prze­sy­łane do serwera.

$input = socket_read($socket, 1024); 
$input = trim($input," \t\n\r");

Uży­wam funk­cji trim() aby wyczy­ścić dane przy­cho­dzące do ser­wera z róż­nych nie­po­trzeb­nych bia­łych znaków.

Pozo­stały kod to pseudo-protokół chatu. W przy­padku prze­sła­nia komu­ni­katu pustego lub wia­do­mo­ści exit połą­cze­nie z użyt­kow­ni­kiem jest zry­wane. Pole­ce­nie hello jest czy­sto demon­stra­cyjne. W przy­padku wpi­sa­nia list poja­wia się lista iden­ty­fi­ka­to­rów innych użyt­kow­ni­ków dostęp­nych aktu­al­nie lub komu­ni­kat o ich braku. Wysy­ła­nie komu­ni­ka­tów do innych użyt­kow­ni­ków odbywa się w nastę­pu­jący sposób:

ID komu­ni­kat

Jeżeli ID wynosi zero wia­do­mość jest prze­sy­łana do wszyst­kich uczest­ni­ków chatu.

Ser­wer ten wciąż nie jest ide­alny. Nie zabez­pie­cza on przed połą­cze­niem się zbyt dużej ilo­ści użyt­kow­ni­ków. Pro­to­kół rów­nież jest dosyć dziwny. W zasa­dzie stwo­rzy­łem go jedy­nie w celach edu­ka­cyj­nych. Pro­po­no­wał­bym jego wymianę na coś przy­jaź­niej­szego. Co warto dopi­sać do takiego ser­wera? Można spró­bo­wać upo­rać się z pro­ble­mem poko­jów dla róż­nych grup użyt­kow­ni­ków. Zapewne te rze­czy opi­szę nie­ba­wem na blogu lecz tym­cza­sowo zro­bię prze­rwę z php. Już nie­ba­wem sporo infor­ma­cji na temat ryso­wa­nia w obiek­cie Canvas. Może zro­bić jakąś fajną plan­szę w rzu­cie izometrycznym?

Dodaj arty­kuł do:

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Gwar
  • Reddit
  • Technorati
  • Twitter
  • Wykop

Komentarzy: 2 »

  1. Bar­dzo cie­kawy pomysł z tą grą. Zasta­na­wia mnie czy ty robisz tą grę sam czy w jakimś więk­szym zespole? Bo stwo­rze­nie takiej gry w poje­dynkę raczej się nie uda, acz­kol­wiek trzy­mam kciuki :)

    Komentarz by kalkulator kredytowy — 15 maja 2010, 23:25

  2. Sam, lecz mam kolegę, który myślę, że mi pomoże.Daję sobie mie­siąc aby stwo­rzyć pierw­szą gry­walną wersję(rejestracja, logo­wa­nie, roz­grywka z kom­pu­te­rem i innymi gra­czami). Naj­wię­cej pro­ble­mów będę miał z grafiką(nie umiem ryso­wać :D ) oraz z opty­ma­li­za­cją jej ren­de­ro­wa­nia tak aby canvas wycią­gał przy­naj­mniej jakieś 25fps

    Komentarz by Michał Środek — 16 maja 2010, 0:02

Kanał RSS z komentarzami do tego wpisu. TrackBack URL

Dodaj komentarz