O websocketoch

V tomto príklade vytvoríme malú webovú aplikáciu - hru pre viacerých používateľov, ktorí hádajú tajné číslo vygenerované na serveri. Ten, ktorému sa to podarí skôr, získa bod. Hociktorý používateľ môže hru kedykoľvek reštartovať, potom sa háda od začiatku a ten, čo uhádne získa ďalší bod. Vytvoríme dve verzie: prvá bude založená len na statických stránkach a requestoch, používateľ sa niečo dozvie vždy až po odoslaní svojho pokusu o uhádnutie čísla. Druhá verzia bude využívať websocket: v prípade, že niekto číslo uhádne, všetkým hráčom sa o tom hneď zobrazí správa. Naviac, všetkým hráčom sa zobrazuje aktuálny počet hráčov pripojených na server.

Prvá, statická, verzia aplikácie je vybudovaná čiste pomocou JSF 2.2. Obsahuje dve webstránky:

index.xhtml
kde používateľ zadá svoje meno čo ho v zápätí presunie na hlavnú stránku guess.xhtml
guess.xhtml
obsahuje formulár v ktorom používateľ háda náhodné číslo 1-10000, ktoré bolo vopred vygenerované na serveri, zobrazuje sa mu správa o výsledku každého jeho pokusu a celkový počet dosiahnutých bodov.

Na serveri fungujú dva objekty:

guess.Guess
obsahuje vygenerované číslo, má platnosť pre celú aplikáciu (ApplicationScope) a flag isOpen, ktorý udáva, či hra stále beží (je otvorená), alebo skončila a treba ju reštartovať. Nové číslo sa vygeneruje buď pri deploynutí aplikácie, alebo pri stlačení Restart na stránke guess.xhtml, ktoré je s metódou restart() priamo previazané pomocou JSF / Expression Language.
guess.Userguess
objekt s platnosťou session jedného hráča (každý hráč má svoj objekt Userguess). Obsahuje jeho meno, aktuálny pokus, celkové dosiahnuté skóre (počet čísel, ktoré už v rámci tejto session uhádol), a momentálne vypísanú správu - reakciu na jeho pokus. Odpoveď na jeho pokus sa mu generuje v metóde getMessage(), pričom sa v prípade uhádnutia čísla v aplikačnom objekte Guess overuje, či už niekto iný číslo neuhádol skôr.


Hra sa dá zahrať tu: http://kempelen.ii.fmph.uniba.sk:8080/guessBasic/. Otvorte si ju naraz z rôznych prehliadačov alebo rôznych sessions prehliadača.

Za povšimnutie stojí spôsob, akým sa bean Userguess vie odkázať na bean Guess - používa na to anotáciu @ManagedProperty pred deklaráciou objektovej premennej guessBean. Toto prepojenie potrebujeme, aby sa správa, ktorá je pre každého hráča špecifická mohla vytvoriť porovnaním s hádaným číslom, ktoré je spoločné pre všetkých hráčov.

Druhá, interaktívna a dynamická, verzia aplikácie využíva websocket, ktorý sa vytvára nanovo pri každom novom requeste zaslanom z klientského webového prehliadača. Cez websocket sa do browsera posieľajú dva druhy správ: počet aktuálne hrajúcich hráčov (ten sa získava podľa toho, koľko websocketových sessions je práve otvorených, tieto správy majú formát "1;pocet_hracov") a správa o uhádnutí čísla nejakým iným hráčom a o reštartovaní hry (tieto správy majú formát "2;sprava"). Aby browser mohol správy kedykoľvek prijať a spracovať, potrebuje kúsok Javascriptového kódu, ktorý pri otvorení stránky guess.xhtml vytvorí websocket-ové spojenie na server a pridá handler, ktorý bude spracovávať všetky správy doručené cez websocket. Spracovanie správ je v našom prípade triviálne - len sa vyrenderuje nový počet hráčov, alebo správa zo servera do príslušného outputText komponentu.

Na strane servera nám pribúda tretia trieda

guess.GameOverNotifier
ktorá reprezentuje serverovský koniec websocketu - vyveseného pomocou anotácie @ServerEndpoint na adrese ws://kempelen.ii.fmph.uniba.sk/guess/guessnotify. Websocket protokol je rozšírením HTTP protokolu a má teda výhodu, že komunikácia prebieha po rovnakých portoch ako bežná komunikácia s HTTP serverom a nepodlieha obmedzeniam firewallov, ako sme mohli vidieť v prípade bežných socketov, ktoré nám komunikovali na iných portoch na začiatku semestra.

Keďže objekt tejto triedy je nový pri každom novom requeste (načítaní stránky), musíme objektu Userguess vždy oznámiť referenciu na seba, aby v prípade potreby mohla všetkým hráčom odoslať správu o reštarte alebo ukončení hry. Preto v tejto verzií aplikácie opustíme hranice sveta JSF a namiesto @ManagedBean-ov použijeme @Named CDI beany. Namiesto automatického zaradenia beanov (injection) pomocou JSF-ovskej anotácie @ManagedProperty použijeme CDI-čkovskú anotáciu @Inject, ktorá nám dovoľuje do requestového websocketového endpointu vložiť vždy platnú referenciu na applikačný objekt triedy Guess a session-ovský objekt triedy Userguess. Potrebné importy sa ale v Netbeansovo-glassfishových knižniciach nemusia nachádzať a v tom prípade treba ku knižniciam projektu pridať knižnicu Java EE 7 API Library (cez Properties - Libraries - Add Library...). Ostatné úpravy kódu sú priamočiare.

Výsledná hra sa dá zahrať tu: http://kempelen.ii.fmph.uniba.sk:8080/guess/.

Zaujímavé odkazy: Marrying Socket.IO Client with Java EE 7’s WebSocket, Atmosphere: Getting Started with Socket.IO, Websockets everywhere with Socket.IO.