-
-
Notifications
You must be signed in to change notification settings - Fork 752
/
Copy pathgames.html
618 lines (563 loc) · 32.4 KB
/
games.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="keywords" content="Java,WebSockets,Server Side Events,AJAX,JavaScript">
<script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?lang=css&skin=sunburst"></script>
<link rel="stylesheet" type="text/css" href="stylesheets/stylesheet.css" media="screen"/>
<link rel="stylesheet" type="text/css" href="stylesheets/pygment_trac.css" media="screen"/>
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print"/>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href='https://fonts.googleapis.com/css?family=Chivo:900' rel='stylesheet' type='text/css'>
<title>Async-IO.org: Powering the Atmosphere Framework</title>
</head>
<body>
<div id="container">
<div class="inner">
<header>
<div class="asyncIO">
<div class="asyncIO-left">
<br>
<div style="text-align: center;">
<a href="http://async-io.org"><img title="logo" alt="" src="./images/AsyncIO.png"/></a>
</div>
</div>
<div class="asyncIO-right">
<h1>Snakes on the NettoSphere!</h1>
<h2><strong>Building Games using WebSocket, Server-Side Events and Long-Polling with the Netty Framework and
Atmosphere</strong>
</h2>
</div>
</div>
</header>
<section id="main_content">
<h3>Introduction</h3>
<p>The Atmosphere Framework is a Java/Javascript framework which allows the creation of portable asynchronous
applications
using Groovy, Scala and Java. The Atmosphere Framework ships with a JavaScript component supporting all modern
browsers
and several server components supporting all major Java-based WebServers and Framework. The aim of the framework is
to allow a
developer to write an application and let the framework discover the best communication channel (transport) between
the client
and
the server, transparently.</p>
<p>This article will use <a
href="https://github.com/Atmosphere/nettosphere">NettoSphere</a>, a framework build on top of the popular <a
href="http://netty.io/">Netty Framework</a> and <a href="http://github.com/Atmosphere">Atmosphere</a> with
support of WebSockets, Server Side Events and Long-Polling. NettoSphere
allows
Atmosphere's applications to run on top of the Netty Framework.
</p>
<p>
The game we will develop is the famous <a
href="http://en.wikipedia.org/wiki/Snake_(video_game)">Snake</a>(*). The difference with the original game is
instead of having to avoid walls and obstacles, your snake will have to avoid hitting other "social snake", e.g
snakes from other users. Each browser will control a snake and share
it's position with all other snake, e.g Browsers!!
</p>
<p>This article assumes you have a little understanding of what is the Atmosphere Framework. See this <a
href="./tutorial.html">article</a> for an introduction to Atmosphere</p>
<p>
The Games will supports for it's primary transport WebSocket and will fallback to HTML5 Server Side Events (SSE) and
Long-Polling.
Long-Polling is required because only IE version 10 supports WebSockets and SSE is not supported at all by IE.
</p>
<p>
The game will use NettoSphere 2.0.0.RC1, which is build on top of Netty 3.6.3.Final. The game can be <a
href="http://search.maven.org/#artifactdetails%7Corg.atmosphere.nettosphere.samples%7Cnettosphere-games%7C2.0.0.RC1%7Cjar">downloaded
here</a>.
</p>
<p>
Note that we won't go into the
details of the game itself and instead focus on how games can be build using Atmosphere API.</a>.
</p>
<h3>Snakes on the NettoSphere!</h3>
<p>Before going into the details of the Snake, let's just explain how an Atmosphere Snake can be installed on the Netty
Framework. As simple as</p>
<div class="example">
<div class="example-left-long">
<h4>Listing 0: Bootstrap.java</h4>
<pre class="prettyprint">
Config.Builder b = new Config.Builder();
b.resource(SnakeManagedService.class)
.resource("./webapps")
.port(8080)
.host("127.0.0.1")
.build();
Nettosphere s = new Nettosphere.Builder().config(b.build()).build();
s.start();
</pre>
</div>
</div>
<p>That's all we need to do: create Config, associate our server component (SnakeManagedService.class), client component
("./webapps"), build and start!</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Atmosphere is using org.atmosphere.cpr.DefaultAnnotationProcessor for processing annotation
[main] INFO o.a.cpr.DefaultAnnotationProcessor - Found Annotation in org.nettosphere.samples.games.SnakeManagedService being scanned: interface org.atmosphere.config.service.ManagedService
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Installed AtmosphereHandler org.atmosphere.handler.ManagedAtmosphereHandler mapped to context-path: /snake
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Installed the following AtmosphereInterceptor mapped to AtmosphereHandler org.atmosphere.handler.ManagedAtmosphereHandler
[main] INFO o.atmosphere.cpr.AtmosphereFramework - AtmosphereResourceLifecycleInterceptor : Atmosphere LifeCycle
[main] INFO o.atmosphere.cpr.AtmosphereFramework - BroadcastOnPostAtmosphereInterceptor : Broadcast POST Body Interceptor
[main] INFO o.atmosphere.cpr.AtmosphereFramework - TrackMessageSizeInterceptor : Track Message Size Interceptor using |
[main] INFO o.atmosphere.cpr.AtmosphereFramework - HeartbeatInterceptor : Heartbeat Interceptor Support
[main] INFO o.atmosphere.cpr.AtmosphereFramework - : Managed Event Listeners
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Installed WebSocketProtocol org.atmosphere.websocket.protocol.SimpleHttpProtocol
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Atmosphere is using async support: org.atmosphere.nettosphere.NettyAtmosphereHandler$1 running under container: Nettosphere/2.0
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Installing Default AtmosphereInterceptor
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.OnDisconnectInterceptor : Browser disconnection detection
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.JavaScriptProtocol : Atmosphere JavaScript Protocol
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.JSONPAtmosphereInterceptor : JSONP Interceptor Support
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.SSEAtmosphereInterceptor : SSE Interceptor Support
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.AndroidAtmosphereInterceptor : Android Interceptor Support
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.PaddingAtmosphereInterceptor : Browser Padding Interceptor Support
[main] INFO o.atmosphere.cpr.AtmosphereFramework - org.atmosphere.interceptor.DefaultHeadersInterceptor : Default Response Headers Interceptor
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Set org.atmosphere.cpr.AtmosphereInterceptor.disableDefaults in your xml to disable them.
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Using BroadcasterCache: org.atmosphere.cache.UUIDBroadcasterCache
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Shared ExecutorService supported: true
[main] INFO o.atmosphere.cpr.AtmosphereFramework - HttpSession supported: false
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Using BroadcasterFactory: org.atmosphere.cpr.DefaultBroadcasterFactory
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Using WebSocketProcessor: org.atmosphere.websocket.DefaultWebSocketProcessor
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Using Broadcaster: org.atmosphere.cpr.DefaultBroadcaster
[main] INFO o.atmosphere.cpr.AtmosphereFramework - Atmosphere Framework 1.1.0.RC1 started.
</pre>
</div>
</div>
<h3>The Server Side</h3>
<p>Let's start by first writing the server component, as described by listing 1. Remember, our goal is to write a Snake
game
that can be played by ALL browsers including mobile, using the best transport available.</p>
<div class="example">
<div class="example-left-long">
<h4>Listing 1: SnakeManagedService.java</h4>
<pre class="prettyprint">
1 package org.nettosphere.samples.games;
2
3 import org.atmosphere.config.service.Get;
4 import org.atmosphere.config.service.ManagedService;
5 import org.atmosphere.config.service.Post;
6 import org.atmosphere.cpr.AtmosphereRequest;
7 import org.atmosphere.cpr.AtmosphereResource;
8 import org.atmosphere.cpr.AtmosphereResourceEvent;
9 import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
10 import org.atmosphere.cpr.AtmosphereResourceFactory;
11 import org.atmosphere.cpr.HeaderConfig;
12
13 import java.io.IOException;
14 import java.util.concurrent.ConcurrentLinkedQueue;
15
16 @ManagedService(path = "/snake")
17 public class SnakeManagedService extends SnakeGame {
18
19 private final ConcurrentLinkedQueue>String< uuids = new ConcurrentLinkedQueue>String<();
20
21 @Get
22 public void onOpen(final AtmosphereResource resource) {
23 resource.addEventListener(new AtmosphereResourceEventListenerAdapter() {
24 @Override
25 public void onSuspend(AtmosphereResourceEvent event) {
26 try {
27 if (!uuids.contains(resource.uuid())) {
28 SnakeManagedService.super.onOpen(resource);
29 uuids.add(resource.uuid());
30 }
31 } catch (IOException e) {
32 e.printStackTrace();
33 }
34
35 }
36
37 @Override
38 public void onDisconnect(AtmosphereResourceEvent event) {
39 AtmosphereRequest request = event.getResource().getRequest();
40 String s = request.getHeader(HeaderConfig.X_ATMOSPHERE_TRANSPORT);
41 if (s != null && s.equalsIgnoreCase(HeaderConfig.DISCONNECT)) {
42 SnakeManagedService.super.onClose(resource);
43 uuids.remove(resource.uuid());
44 }
45 }
46 });
47 }
48
49 @Post
50 public void onMessage(AtmosphereResource resource) {
51 try {
52 // Here we need to find the suspended AtmosphereResource
53 super.onMessage(AtmosphereResourceFactory.getDefault().find(resource.uuid()),
54 resource.getRequest().getReader().readLine());
55 } catch (IOException e) {
56 e.printStackTrace();
57 }
58 }
59
60 }
</pre>
</div>
</div>
<p>The ManagedService annotation (line 16) tells the framework to route every '/snake' request to the
SnakeManagedService class. The ManagedService is a meta component in Atmosphere which transparently enable the
following services (called AtmosphereInterceptor):</p>
<ul>
<li><strong>Messages Caching</strong>: a connection between the browsers and the server can always be closed by a
third party.
Proxy, Firewall, Network outage etc. can always cause the connection to be unexpected closed. If messages where
about to be send to the client, those messages will be lost if the server doesn't cache them and make them
available when the client reconnect. This is quite important for any real time application, and event more
important for our Snakes' users!!!
</li>
<li><strong>Connection LifeCycle Support</strong>: Atmosphere's allow the management of the connection between the
browsers and
the
server. For example, you can add special headers to the WebSocket's Handshake response, add some padding to a
Server
Side Events connection etc. Since most of the application doesn't need to do such thing, it is better to
delegate
the handling to the LifeCycle Support component and focus on the application business logic instead.
</li>
<li><strong>Track Messages Size</strong>: Messages sent by the server back to the browser may be chunked. In order
to
help the browser with incomplete messages (or messages spawn in more than one packet), this service adds meta
information to the message. The browser use this meta information to reconstruct the message before delivering
it to the application.
</li>
<li><strong>Heartbeat</strong>: some Proxy closes idle connections between the browser and the server. This service
make sure there are always activities on the connection, preventing unexpected closes, reconnects, etc.
</li>
<li><strong>Browser disconnect</strong>: The Atmosphere's Javascript client is able to sent messages when a
tab/window is getting closed. This service use this information to clean up resources associated with that
browser.
</li>
</ul>
<h5>As you can see, you SAVES a lot of code by using the @ManagedService annotation!</h5>
<p></p>
<p>Next, on line 21, we use the @Get annotation to tell the framework to route GET request to the onOpen method. When
the browser's connect, the onOpen will be invoked with an AtmosphereResource, who represent a connection between
the browser and server. The 'onOpen' method's role is to attach an Atmosphere's listener to the connection and gets
invoked
first when the connection is ready to be manipulated (line 25). Inside the onSuspend, we track our current alive
Snake
by using the browser's unique is 'AtmosphereResource.uuid()' and initiate the game (listing 2).
</p>
<div class="example">
<div class="example-left-long">
<h4>Listing 2: SnakeGame.java</h4>
<pre class="prettyprint">
78 public void onOpen(AtmosphereResource resource) throws IOException {
79 int id = snakeIds.getAndIncrement();
80 resource.session().setAttribute("id", id);
81 Snake snake = new Snake(id, resource);
82
83 resource.session().setAttribute("snake", snake);
84 snakeBroadcaster.addSnake(snake);
85 StringBuilder sb = new StringBuilder();
86 for (Iterator>Snake< iterator = snakeBroadcaster.getSnakes().iterator();
87 iterator.hasNext(); ) {
88 snake = iterator.next();
89 sb.append(String.format("{id: %d, color: '%s'}",
90 Integer.valueOf(snake.getId()), snake.getHexColor()));
91 if (iterator.hasNext()) {
92 sb.append(',');
93 }
94 }
95 snakeBroadcaster.broadcast(String.format("{'type': 'join','data':[%s]}",
96 sb.toString()));
97 }
98
99 public void onClose(AtmosphereResource resource) {
100 snakeBroadcaster.removeSnake(snake(resource));
101 snakeBroadcaster.broadcast(String.format("{'type': 'leave', 'id': %d}",
102 ((Integer) resource.session().getAttribute("id"))));
103 }
104
105 protected Snake snake(AtmosphereResource resource) {
106 return (Snake) resource.session().getAttribute("snake");
107 }
108
109 protected void onMessage(AtmosphereResource resource, String message) {
110 Snake snake = snake(resource);
111 if ("west".equals(message)) {
112 snake.setDirection(Direction.WEST);
113 } else if ("north".equals(message)) {
114 snake.setDirection(Direction.NORTH);
115 } else if ("east".equals(message)) {
116 snake.setDirection(Direction.EAST);
117 } else if ("south".equals(message)) {
118 snake.setDirection(Direction.SOUTH);
119 }
120 }
</pre>
</div>
</div>
<p>In the onOpen method we store some state information about the current Snake user and add our Snake to an
Atmosphere's Broadcaster (called SnakeBroadcaster). A Broadcaster implements the publish/subscribe paradigm. An
application can subscribe to one or many Broadcasters to get notified about events. By default, a single Broadcaster
is created by the framework and associated with every new AtmosphereResource. For our game, we just create one
Broadcaster called "/snake", which maps our original request's URI. The important code here is line 95, where the
SnakeBroadcaster is used here to broadcast the position of all snakes to all connected users. We use JSON for
encoding our data.</p>
<p>The onDisconnect method (listing 1, line 38) is simply used to clean our resources when the browser gets closed (tab
or
window). We do check for the 'disconnect' message in order to differentiate when the browser close the connection
versus when the connection get closed unexpectedly. When closed unexpectedly, we know the client will reconnect so
we don't kill the associated Snake! Listing 2 line 99 broadcast to all remaining connection browser
that snake X is now gone, so the browser can kill that snake by stopping rendering it!</p>
<p>Finally, the @Post method (listing 1, line 50) just broadcast the information received from a browser to others.
That means every time a snake is moving, it's position will be broadcasted to all others and vice versa (Listing 2,
line 109)</p>
<p>That's it for the server side component, which TRANSPARENTLY support WebSocket, Server Side Events and
Long-Polling!</p>
<h3>Wait!!!!!!!</h3>
<p>There will be a lot of information exchanged between snakes: every time a snake move its information will be send to
the server, and broadcasted back to all other snakes. For a WebSocket connection this is quite easy to handle
because
the connection is bi-directional. But realize how complex the code can be when Server Side Events and Long-Polling
are
used: one connection is used to receive messages, and another connection is used for sending information. Realize
that without
Atmosphere, a lot of code would have been required to makes the game work!</p>
<h3>The client side</h3>
<p>That’s it for the server side. Now let’s use the atmosphere.js to write the client side. First, let’s look at the code
(Listing 3). As for the server side, the game's logic won't be described </p>
<div class="example">
<div class="example-left-long">
<h4>Listing 3: Atmosphere.js Client Code</h4>
<pre class="prettyprint">
188 Game.connect = (function (host) {
189 var request = {url: host,
190 transport: 'websocket',
191 enableProtocol: true,
192 trackMessageLength: true,
193 logLevel: 'debug'};
194
195 request.onOpen = function (response) {
196 // Socket open.. start the game loop.
197 Console.log('Info: ' + Game.transport + ' connection opened.');
198 Console.log('Info: Press an arrow key to begin.');
199 Game.startGameLoop();
200 };
201
202 request.onClose = function (response) {
203 if (response.state == "unsubscribe") {
204 Console.log('Info: ' + Game.transport + ' closed.');
205 Game.stopGameLoop();
206 }
207 };
208
209 request.onTransportFailure = function (errorMsg, request) {
210 jQuery.atmosphere.info(errorMsg);
211 if (window.EventSource) {
212 request.fallbackTransport = "sse";
213 } else {
214 request.fallbackTransport = 'long-polling'
215 }
216 Game.transport = request.fallbackTransport;
217 };
218
219 request.onMessage = function (response) {
220 var message = response.responseBody;
221 var packet;
222 try {
223 packet = eval('(' + message + ')'); //jQuery.parseJSON(message);
224 } catch (e) {
225 console.log('Message: ', message);
226 return;
227 }
228
229 switch (packet.type) {
230 case 'update':
231 for (var i = 0; i < packet.data.length; i++) {
232 Game.updateSnake(packet.data[i].id, packet.data[i].body);
233 }
234 break;
235 case 'join':
236 for (var j = 0; j < packet.data.length; j++) {
237 Game.addSnake(packet.data[j].id, packet.data[j].color);
238 }
239 break;
240 case 'leave':
241 Game.removeSnake(packet.id);
242 break;
243 case 'dead':
244 Console.log('Info: Your snake is dead, bad luck!');
245 Game.direction = 'none';
246 break;
247 case 'kill':
248 Console.log('Info: Head shot!');
249 break;
250 }
251 };
252 Game.socket = $.atmosphere.subscribe(request)
253
254 });</pre>
</div>
</div>
<p>There is a lot of extra in the code in Listing 3 related to the game's logic itself, so let’s only describe the
Atmosphere's client JavaScript called atmosphere.js important parts. First, we initialize a connection (line
252)</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
Game.socket = $.atmosphere.subscribe(request)
</pre>
</div>
</div>
<p>The next step is to define some functions callback. For this game, we will define the important one: onOpen,
onClose, onTransportFailure and onMessage. First, we
define an onOpen function that gets invoked when the underlying transport is connected to the server. There we
just initialize the Snake. The preferred transport is specified on the request object, which is defined as:</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
var request = {
url: host,
transport: 'websocket',
enableProtocol: true,
trackMessageLength: true
};
</pre>
</div>
</div>
<p>Here we want to use the WebSocket transport by default, and fallback to Server Side events or Long-Polling if not
supported</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
request.onTransportFailure = function (errorMsg, request) {
jQuery.atmosphere.info(errorMsg);
if (window.EventSource) {
request.fallbackTransport = "sse";
} else {
request.fallbackTransport = 'long-polling'
}
Game.transport = request.fallbackTransport;
};
</pre>
</div>
</div>
<p>The beauty here is: you don’t need to use a special API. All transports are handled
the same way using the atmosphere.js.</p>
<p>Next we define the onMessage function, which will be invoked every time we receive data from the server:</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
request.onMessage = function (response) {
var message = response.responseBody;
var packet;
try {
packet = jQuery.parseJSON(message);
} catch (e) {
console.log('Message: ', message);
return;
}
switch (packet.type) {
case 'update':
for (var i = 0; i < packet.data.length; i++) {
Game.updateSnake(packet.data[i].id, packet.data[i].body);
}
break;
case 'join':
for (var j = 0; j < packet.data.length; j++) {
Game.addSnake(packet.data[j].id, packet.data[j].color);
}
break;
case 'leave':
Game.removeSnake(packet.id);
break;
case 'dead':
Console.log('Info: Your snake is dead, bad luck!');
Game.direction = 'none';
break;
case 'kill':
Console.log('Info: Head shot!');
break;
}
</pre>
</div>
</div>
<p>Here we just displaying snake's position. To send Snake's position to the server, all we need to do is to
invoke:</p>
<div class="example">
<div class="example-left-long">
<pre class="prettyprint">
120 Game.setDirection = function (direction) {
121 Game.direction = direction;
122 Game.socket.push(direction);
123 Console.log('Sent: Direction ' + direction);
124 }; </pre>
</div>
</div>
<p>That's it, we have both client and server components ready to Snake!</p>
<h3>Back in 1970! As good as Space Invaders!</h3>
<a href="./images/image1.png"><img title="snake" alt="" src="./images/snake.png"/></a>
<h3>Supported Browsers and their associate transports</h3>
<p>
Our Snake application will first negotiate the best transport to use between the client and the
server. For example, the following transport will be used
</p>
<ul>
<li>Chrome 21 : WebSockets</li>
<li>Internet Explorer 9 : Long-Polling</li>
<li>FireFox 15: Server Side Events</li>
<li>Safari/iOS 6: WebSockets</li>
<li>Internet Explorer 10: WebSockets</li>
<li>Android 2.3: Long-Polling</li>
<li>FireFox 3.5 : Long-Polling</li>
</ul>
<p>
All of this transparently, allowing a developer to focus on the application instead of
transport/portability issues.</p>
<p><strong>For this article we have used Netty, but the same code will run UNMODIFIED in <a href="https://github.com/Atmosphere/atmosphere-play">Play! Framework</a> and any WebServer supporting Servlet
2.4 Specification.</strong></p>
<h3>Conclusions and Considerations</h3>
<p>WebSockets and Server Sides Events are technologies on the rise and their adoption within the
enterprise
is accelerating. Some things to think about before jumping in:
</p>
<ul>
<li>Is the API portable, e.g. will it work on all well-known WebServer?</li>
<li>Is the framework already offering a transport fallback mechanism? For example, Internet Explorer 7/8/9 neither
support WebSockets and Server Side Events, and unfortunately for us, those browsers are still widely used.
</li>
<li>Is the framework cloud enabled, and more important, will it scale?</li>
<li>Is it easy to write application, is the framework well established?</li>
<li>Do we really need a Java EE Server? Why not using NettoSphere or Play!</li>
</ul>
<p>Clearly, the Atmosphere Framework is the response for those five really important questions.</p>
<h3>Help Making The Atmosphere Framework Better: Donate to keep the project alive!</h3>
<form style="text-align: center;" class="form-all" action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHPwYJKoZIhvcNAQcEoIIHMDCCBywCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCgqByafUteBOH/12jDK8vrw4XPkWF9l5ovM9BLG3EqHQxckLrs/OH83SRTagd6yAwcyLzVrScIiuDtLqR2gXL+7p+cEqlmzgpOZTM0I5lKXpldFw19Ezixq2lPAoONdgh5Rn7wUz6lMt4UarqQQDdJC7e4Qt4QSdvB+qAFa10QQTELMAkGBSsOAwIaBQAwgbwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIDrep0bW6iCKAgZhs3nUIzgaNf+2rw4Ejq1RvDPdGoL/9Clmv9LzEMMxBoCpVNkDSNoJboWO6MD6FBOV5O/TP9ffb22UnGDASu4H/u9HWS7qiwD5/E/KabX4AKau99tAI+m/UX48dTrAnYdJRppMvifZDWpYTC1Jgee4ntv6nLhfcKjQn43KRZJkZqdIWBohKQdgWv9M8LZornYLMM2dBak74VKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEzMDMwNjE3NTExN1owIwYJKoZIhvcNAQkEMRYEFCdsYpjKVxlazWU/LUemXED7eziEMA0GCSqGSIb3DQEBAQUABIGAE4w/ZoC1xqP/FKmhUEeck2ASQGUh5nc3NNPESNSuct8aQZ6uefNjXPmY/kOet+5ShbQjBt0o3Q2HtGsdX5C39dO3uuSQAoTZdHSorT5yeHeWAy6NXacmOR1WWnaXkIZTlS57OD1KUBMiOTpIXPH9kClxm+RWF8CFghF5Y0esdls=-----END PKCS7-----
">
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" border="0"
name="submit" alt="PayPal - The safer, easier way to pay online!">
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1"
height="1">
</form>
</section>
<footer>
<p>This game is a
port of "Tomcat's 7 WebSocket only" game developed by <a href="http://www.tomcatexpert.com/users/mthomas">Mark
Thomas</p>
<p>©2008-2021 All Rights Reserved - <a href="http://async-io.org/">Async-IO.org</a>. Follow us on <a
href="http://twitter.com/asyncio">Twitter</a></p>
</footer>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-31990725-2");
pageTracker._trackPageview();
} catch (err) {
}
</script>
</div>
</div>
</body>
</html>