PHP
Bertrand Estellon
Aix-Marseille Université
13 mars 2012
13 mars 2012 1 / 214
Problématique : Nous souhaitons réaliser un jeu de morpion en réseau.
I Les clients se connectent au jeu;
I Les clients jouent chacun leur tour;
I Les coups d’un joueur sont répercutés sur la grille de l’autre joueur;
I Le serveur organise la partie (débute la partie, décide le gagnant...);
Problème : En PHP, chaque requête du client est traitée indépendamment.
Il n’y a donc pas de :
I Processus persistant et de donnée en mémoire persistante
? Difficulté pour conserver l’état courant de la partie
(i.e. orchestrer plusieurs clients)
I Connexion persistante (le client demande et le serveur répond) ? Difficulté pour envoyer des événements aux clients.
Solution :
I Côté client : WebSocket de HTML 5
I Côté serveur : Java et Jetty (serveur HTTP et moteur de servlet libre) Autres solutions :
I AJAX et l’approche Comet
I (développement du client et du serveur en JavaScript) I ...
Objectif (Wikipedia) : Obtenir un canal de communication bidirectionnel et full-duplex sur un socket TCP pour les navigateurs et les serveurs web.
Demande de connexion du client et“handshake”:
GET /ws HTTP/1.1
Host: pmx
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 6
Sec-WebSocket-Origin: http://pmx
Sec-WebSocket-Extensions: deflate-stream Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Réponse du serveur :
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
WebSocket et JavaScript
Demande d’ouverture de la connexion avec le serveur :
i f ( ’ WebSocket ’ in window ) ws = new WebSocket ( ’ws :// toto . com:8081 ’ ) ;
e l s e i f ( ’MozWebSocket ’ in window ) ws = new MozWebSocket( ’ws :// toto . com:8081 ’ ) ; e l s e a l e r t ( ”Pas de WebSocket ”) ; Mise en place des“callbacks”:
ws . onopen = onOpen ; ws . onclose = onClose ; ws . onerror = onError ; ws . onmessage = onMessage ; Exemples de“callbacks”:
function onOpen( event ) { a l e r t ( ”Connexione t a b l i e . ”) ; } function onClose ( event ) { a l e r t ( ”Connexionperdue . ”) ; } function onError ( event ) { a l e r t ( ”Erreur ”) ; } function onMessage ( event ) { a l e r t ( event . data ) ; }
WebSocket et JavaScript
Envoie des messages vers le serveur :
f o r ( var i = 0; i < 3; i++) {
f o r ( var j = 0; j < 3; j++) {
$( ’#g r i d ’ ) . append (
’<divid =”c ’+i+””+j+’ ”c l a s s =”case”></div>’ ) ; }
$( ’#g r i d ’ ) . append ( ”<br/>”) ;
}
$( ’ . case ’ ) . c l i c k ( function () { var id = $( t h i s ) . a t t r ( ’ id ’ ) ; var r = parseInt ( id . charAt ( 1 ) ) ; var c = parseInt ( id . charAt ( 2 ) ) ;
ws . send (”P#”+r+”#”+c ) ;
});
Création d’un serveur HTTP avec Jetty :
p u b l i c s t a t i c void main ( String [ ] args ) throws Exception { Server httpServer = new Server (8080);
ResourceHandler resourceHandler = new ResourceHandler ( ) ; resourceHandler . s e t D i r e c t o r i e s L i s t e d ( true ) ; resourceHandler . setWelcomeFiles (new String [ ]
{ ”index . html ” });
resourceHandler . setResourceBase ( ”/home/ toto / c l i e n t ”) ; httpServer . setHandler ( resourceHandler ) ; httpServer . s t a r t ( ) ; httpServer . j o i n ( ) ;
}
Création d’un serveur WebSocket avec Jetty :
p u b l i c s t a t i c void main ( String [ ] args ) throws Exception { Server wsServer = new Server (8081); wsServer . setHandler (new MyServer ( ) ) ; wsServer . s t a r t ( ) ; wsServer . j o i n ( ) ;
}
La classe qui re¸coit les notifications de connexion :
p u b l i c c l a s s MyServer extends WebSocketHandler {
p u b l i c WebSocket doWebSocketConnect (
HttpServletRequest request ,
String protocol ) { C l i e n t c l i e n t = new MyClient ( t h i s ) ; return c l i e n t ;
}
}
Gestion d’une connexion entre un client et le serveur :
p u b l i c c l a s s MyClient implements WebSocket . OnTextMessage { p r i v a t e Connection connection ;
p u b l i c void onOpen( Connection connection ) { t h i s . connection = connection ;
//TODO
}
p u b l i c void onMessage ( String data ) { //TODO } p u b l i c void onClose ( i n t code , String msg) { // TODO }
p u b l i c void send ( String data ) { try { connection . sendMessage ( data ) ; } catch ( IOException e ) { connection . cl o se () ; }
}
}
Les messages du serveur :
I W : Attendre le début de la partie.
I P : Vous devez jouer un coup.
I O : Votre adversaire est en train de jouer.
I V : Vous avez gagné.
I L : Vous avez perdu.
I D#r#c#j : Le joueur j à jouer la case (r,c).
Les messages du client :
I P#r#c : Je joue la case (r,c).
La page HTML du client :
<html>
<head>
<s c r i p t type=”text / j a v a s c r i p t ” src=”jquery . j s ”> </ s c r i p t>
<s c r i p t type=”text / j a v a s c r i p t ” src=”c l i e n t . j s ”>
</ s c r i p t>
. . .
</head>
<body onload=” i n i t () ”>
<div id=”gameMessage ” c l a s s=”message ”></ div><br />
<div id=”g r i d ”></ div>
<div id=”connectionMessage ” c l a s s=”message ”></ div> </body>
</html>
La fonction init() :
function i n i t () {
i f ( ’ WebSocket ’ in window )
ws = new WebSocket ( ’ws :// l o c a l h o s t :8081 ’ ) ;
e l s e i f ( ’MozWebSocket ’ in window )
ws = new MozWebSocket( ’ws :// l o c a l h o s t :8081 ’ ) ;
ws . onopen = onOpen ; ws . onmessage = onMessage ; ws . onclose = onClose ;
// Code pour creer l a g r i l l e .
// Code pour ecouter l e s c l i c s de s o u r i s .
}
Code pour créer la grille :
var i , j ;
f o r ( i = 0; i < 3; i++) {
f o r ( j = 0; j < 3; j++)
$( ’#g r i d ’ ) . append (
’<divid =”c ’+i+””+j+’ ”c l a s s =”case”></div>’ ) ;
$( ’#g r i d ’ ) . append ( ”<br/>”) ;
}
Code pour écouter les clics de souris :
$( ’ . case ’ ) . c l i c k ( function () { var id = $( t h i s ) . a t t r ( ’ id ’ ) ; var r = parseInt ( id . charAt ( 1 ) ) ; var c = parseInt ( id . charAt ( 2 ) ) ;
ws . send ( ”P#”+r+”#”+c ) ;
});
function | onMessage ( event ) { | |
switch | ( event . data . charAt (0)) { | |
case | ’W’ : setGameMessage ( ”Attendre . ”) ; break ; | |
case | ’P ’ : setGameMessage ( ”Jouer . ”) ; break ; | |
case | ’O’ : setGameMessage ( ”Votre adv . joue . ”) ; | break ; |
Traitement des messages du serveur :
case | ’V ’ | : | setGameMessage ( ”V i c t o i r e . ”) ; break ; |
case | ’L ’ | : | setGameMessage ( ”Perdu . ”) ; break ; |
case } } | ’D’ | : | var r = parseInt ( event . data . charAt ( 2 ) ) ; var c = parseInt ( event . data . charAt ( 4 ) ) ; var p = parseInt ( event . data . charAt ( 6 ) ) ; i f (p==1) $( ”#c ”+r+””+c ) . addClass ( ”red ”) ; e l s e $( ”#c ”+r+””+c ) . addClass ( ”blue ”) ; break ; |
function | setGameMessage (m) { $( ’#gameMessage ’ ) . html (m) ; } |
Traitement des connexions et déconnexions :
function onOpen () {
$( ’#connectionMessage ’ ) . html ( ”Connexione t a b l i e . ”) ;
}
function onClose () {
$( ’#connectionMessage ’ ) . html ( ”Connexionperdue . ”) ; }
p u b l i c c l a s s Main {
p u b l i c s t a t i c void main ( String [ ] args ) throws Exception { Server wsServer = new Server (8081); wsServer . setHandler (new MorpionServer ( ) ) ; wsServer . s t a r t ( ) ;
Server htmlServer = new Server (8080);
ResourceHandler rHandler = new ResourceHandler ( ) ; rHandler . s e t D i r e c t o r i e s L i s t e d ( true ) ; rHandler . setWelcomeFiles (new String [ ] { ”index . html ”}); rHandler . setResourceBase ( ”c l i e n t ”) ; htmlServer . setHandler ( rHandler ) ; htmlServer . s t a r t ( ) ;
wsServer . j o i n ( ) ; htmlServer . j o i n ( ) ;
}
}
p u b l i c c l a s s MorpionServer extends WebSocketHandler { p r i v a t e Game game = new Game ( ) ;
p u b l i c WebSocket doWebSocketConnect (
HttpServletRequest request ,
String protocol ) {
C l i e n t c l i e n t = new C l i e n t ( t h i s ) ; return c l i e n t ;
}
p u b l i c void addPlayer ( C l i e n t c l i e n t ) {
game . addPlayer ( c l i e n t ) ;
i f (game . isComplete ()) {
game . s t a r t ( ) ;
game = new Game ( ) ;
}
}
}
p u b l i c c l a s s C l i e n t implements WebSocket . OnTextMessage { p r i v a t e Connection connection ; p r i v a t e MorpionServer s e r v e r ; p r i v a t e Game game ; p r i v a t e i n t p o s i t i o n ;
p u b l i c C l i e n t ( MorpionServer s e r v e r ) {
t h i s . s e r v e r = s e r v e r ;
}
p u b l i c void onOpen( Connection connection ) { t h i s . connection = connection ; s e r v e r . addPlayer ( t h i s ) ;
}
p u b l i c void onMessage ( String data ) {
game . onMessage ( position , data ) ; }
p u b l i c void onClose ( i n t closeCode , String message ) {
game . f i n i s h ( ) ;
}
p u b l i c void setGame (Game game , i n t p o s i t i o n ) {
t h i s . game = game ; t h i s . p o s i t i o n = p o s i t i o n ;
}
p u b l i c void send ( String data ) {
try { connection . sendMessage ( data ) ; }
catch ( IOException e ) { connection . cl o se ( ) ; }
}
p u b l i c void c lo s e () { connection . cl o se ( ) ; }
}
p u b l i c c l a s s Game {
p r i v a t e C l i e n t [ ] p l a y e r s ; p r i v a t e i n t curPlayer ; p r i v a t e i n t g r i d [ ] [ ] ;
p u b l i c Game() {
p l a y e r s = new C l i e n t [ 2 ] ;
g r i d = new i n t [ 3 ] [ 3 ] ;
}
p u b l i c void addPlayer ( C l i e n t c l i e n t ) {
i f ( p l a y e r s [ 0 ] == n u l l ) {
p l a y e r s [ 0 ] = c l i e n t ; c l i e n t . setGame ( this , 1); c l i e n t . send ( ”W”) ;
} e l s e { p l a y e r s [ 1 ] = c l i e n t ;
c l i e n t . setGame ( this , 2); }
}
p u b l i c boolean isComplete () {
return ( p l a y e r s [ 1 ] != n u l l ) ;
}
p u b l i c void s t a r t () {
curPlayer = 1; p l a y e r s [ curPlayer ? 1 ] . send ( ”P”) ; p l a y e r s [2 ? curPlayer ] . send ( ”O”) ;
}
p u b l i c void f i n i s h () {
f o r ( i n t i = 0; i < 2; i++)
i f ( p l a y e r s [ i ] != n u l l ) {
p l a y e r s [ i ] . cl o se ( ) ; p l a y e r s [ i ] = n u l l ; }
}
p r i v a t e isWinner ( i n t p o s i t i o n ) { . . . }
p u b l i c void onMessage ( i n t position , String d) { i f ( p o s i t i o n != curPlayer ) return ; i f (! d . matches ( ”ˆP#[0?9]#[0?9]$ ”)) return ;
i n t c = d . charAt (2) ? ’0 ’ ; i n t r = d . charAt (4) ? ’0 ’ ; i f ( g r i d [ c ] [ r ] != 0) return ; g r i d [ c ] [ r ] = p o s i t i o n ;
p l a y e r s [ 0 ] . send ( ”D#” + c + ”#” + r + ”#” + curPlayer ) ; p l a y e r s [ 1 ] . send ( ”D#” + c + ”#” + r + ”#” + curPlayer ) ;
i f ( isWinner ( p o s i t i o n )) {
p l a y e r s [ p o s i t i o n ? 1 ] . send ( ”V”) ;
p l a y e r s [2 ? p o s i t i o n ] . send ( ”L ”) ; f i n i s h ( ) ;
} e l s e { curPlayer = 3 ? curPlayer ; p l a y e r s [ curPlayer ? 1 ] . send ( ”P”) ; p l a y e r s [2 ? curPlayer ] . send ( ”O”) ; }
}
}