em-websocket-server 0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -0,0 +1,63 @@
1
+ #em-websocket-server
2
+
3
+ * em-websocket-server allows the creation of efficient, evented, websocket services
4
+
5
+ ##Installation
6
+
7
+ If you don't have gemcutter
8
+
9
+ gem install gemcutter
10
+ gem tumble
11
+
12
+ Otherwise
13
+
14
+ gem install em-websocket-server
15
+
16
+ Or
17
+
18
+ gem install em-websocket-server -s http://gemcutter.org
19
+
20
+ ##Dependencies
21
+ - eventmachine http://github.com/eventmachine/eventmachine
22
+
23
+ ##Docs
24
+
25
+ Not yet... coming soon
26
+
27
+ ##Quick Example
28
+
29
+ require 'rubygems'
30
+ require 'em-websocket-server'
31
+ require 'json'
32
+
33
+ #create a channel for pub sub
34
+ $chatroom = EM::Channel.new
35
+
36
+ class ChatServer < WebSocketServer
37
+
38
+ #subscribe to the channel on client connect
39
+ def on_connect
40
+ @sid = $chatroom.subscribe do |msg|
41
+ send_message msg
42
+ end
43
+ end
44
+
45
+ #unsubscribe on client disconnect
46
+ def on_disconnect
47
+ $chatroom.unsubscribe(@sid)
48
+ end
49
+
50
+ #publish the message to the channel on
51
+ #client message received
52
+ def on_receive msg
53
+ $chatroom.push msg
54
+ end
55
+
56
+ end
57
+
58
+ #start the event machine on port 8000 and have
59
+ #it instantiate ChatServer objects for each
60
+ #client connection
61
+ EM.run do
62
+ EM.start_server "0.0.0.0", 8000, ChatServer
63
+ end
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'em-websocket-server'
3
- s.version = '0.1'
3
+ s.version = '0.1.1'
4
4
  s.date = '2009-12-14'
5
5
  s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
6
  s.email = "dan.simpson@gmail.com"
@@ -18,6 +18,11 @@ spec = Gem::Specification.new do |s|
18
18
  "em-websocket-server.gemspec",
19
19
  "examples/chat.rb",
20
20
  "examples/chat.html",
21
+ "examples/timesync.html",
22
+ "examples/timesync.rb",
23
+ "examples/tictactoe/tictactoe.html",
24
+ "examples/tictactoe/tictactoe.js",
25
+ "examples/tictactoe/tictactoe.rb",
21
26
  "lib/em-websocket-server.rb"
22
27
  ]
23
28
  end
data/examples/chat.html CHANGED
@@ -1,38 +1,40 @@
1
1
  <!doctype html>
2
2
  <html>
3
- <head>
4
- <title>node.websocket.js test</title>
3
+ <head>
4
+ <title>em-websocket-server test</title>
5
+ </head>
6
+ <body>
5
7
 
6
- <style type="text/css" media="screen">
7
- #time { background: #ccc; }
8
- </style>
9
- </head>
10
- <body>
11
8
 
12
- <h1>websocketed demo</h1>
9
+ <h1>em-websocket-server chat demo</h1>
10
+ <div id="chat">connecting....</div>
13
11
 
14
- <h2>The time is <span id="time">opening socket</span></h2>
15
- <p>The information for the time is provided by the server. No Date() or intervals here!</p>
16
12
 
17
- <script>
18
- var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
19
13
 
20
- webSocket.onopen = function(event){
21
- document.getElementById('time').innerHTML = 'waiting for socket';
22
- webSocket.send('start');
23
- };
14
+ <script>
24
15
 
25
- webSocket.onmessage = function(event){
26
- console.log(event);
27
- var object = JSON.parse(event.data);
28
- document.getElementById('time').innerHTML = object.time;
29
- };
16
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
30
17
 
31
- webSocket.onclose = function(event){
32
- document.getElementById('time').innerHTML = 'socket closed';
33
- };
18
+ webSocket.onopen = function(event){
19
+ document.getElementById('chat').innerHTML = 'connected';
20
+ };
34
21
 
35
- </script>
22
+ webSocket.onmessage = function(event){
23
+ var object = JSON.parse(event.data);
24
+ document.getElementById('chat').innerHTML += "<b>" + object.user + "</b>" + object.message;
25
+ };
36
26
 
37
- </body>
27
+ webSocket.onclose = function(event){
28
+ document.getElementById('chat').innerHTML = 'socket closed';
29
+ };
30
+
31
+ document.getElementById('chatty').addEventListener("onkeypress", function() {
32
+ alert("X")
33
+ });
34
+
35
+ </script>
36
+
37
+ <input type="text" onkeypress="webSocket.send('hi');" />
38
+
39
+ </body>
38
40
  </html>
data/examples/chat.rb CHANGED
@@ -1,24 +1,25 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
1
3
  require 'rubygems'
4
+ require 'em-websocket-server'
2
5
  require 'json'
3
6
 
4
- class TimeServer < WebSocketServer
7
+ $chatroom = EM::Channel.new
8
+
9
+ class ChatServer < WebSocketServer
5
10
 
6
11
  def on_connect
7
- @timer = EM.add_periodic_timer(5) do
8
- sync_time
12
+ @sid = $chatroom.subscribe do |msg|
13
+ send_message msg
9
14
  end
10
15
  end
11
16
 
12
17
  def on_disconnect
13
- @timer
18
+ $chatroom.unsubscribe(@sid)
14
19
  end
15
20
 
16
21
  def on_receive msg
17
- puts "msg rcv"
18
- end
19
-
20
- def sync_time
21
- send_message({ :time => Time.now }.to_json)
22
+ $chatroom.push msg
22
23
  end
23
24
 
24
25
  end
@@ -28,5 +29,5 @@ EM.epoll
28
29
  EM.set_descriptor_table_size(10240)
29
30
 
30
31
  EM.run do
31
- EM.start_server "0.0.0.0", 8000, TimeServer
32
+ EM.start_server "0.0.0.0", 8000, ChatServer
32
33
  end
@@ -0,0 +1,51 @@
1
+ <!doctype html>
2
+
3
+ <html>
4
+ <head>
5
+ <title>Tic Tac Toe</title>
6
+ <meta name="author" content="Jordan Fowler (TheBreeze)">
7
+
8
+ <style type="text/css" media="screen">
9
+ #navigation ul {
10
+ list-style-type: none;
11
+ padding: 0;
12
+ margin: 25px 0px;
13
+ }
14
+
15
+ #navigation ul li {
16
+ display: inline;
17
+ padding: 0;
18
+ margin: 0;
19
+ margin-right: 10px;
20
+ }
21
+
22
+ #game-board .cell {
23
+ width: 100px;
24
+ height: 100px;
25
+ padding: 5px;
26
+ background: #000;
27
+ color: #FFF;
28
+ font-size: 100px;
29
+ text-align: center;
30
+ vertical-align: middle;
31
+ }
32
+ </style>
33
+ </head>
34
+ <body>
35
+
36
+ <h1>Tic Tac Toe</h1>
37
+
38
+ <div id="user-count"></div>
39
+ <div id="game-stats"></div>
40
+ <div id="status"></div>
41
+
42
+ <table id="game-board">
43
+ <tr><td id="cell-0-0" class="cell"></td><td id="cell-1-0" class="cell"></td><td id="cell-2-0" class="cell"></td></tr>
44
+ <tr><td id="cell-0-1" class="cell"></td><td id="cell-1-1" class="cell"></td><td id="cell-2-1" class="cell"></td></tr>
45
+ <tr><td id="cell-0-2" class="cell"></td><td id="cell-1-2" class="cell"></td><td id="cell-2-2" class="cell"></td></tr>
46
+ </table>
47
+
48
+ <script type="text/javascript" charset="utf-8" src="mootools-1.2.4-core-nc.js"></script>
49
+ <script type="text/javascript" charset="utf-8" src="tictactoe.js"></script>
50
+ </body>
51
+ </html>
@@ -0,0 +1,130 @@
1
+ // Author: Jordan Fowler (TheBreeze)
2
+
3
+ var TicTacToe = new Class({
4
+ wins: 0,
5
+ loses: 0,
6
+ draws: 0,
7
+ methodMap: {
8
+ 'queued': 'onQueued',
9
+ 'start': 'onStart',
10
+ 'turn': 'onTurn',
11
+ 'move': 'onMove',
12
+ 'game_over': 'onGameOver',
13
+ 'win': 'onWin',
14
+ 'loss': 'onLoss',
15
+ 'draw': 'onDraw',
16
+ 'user_count': 'onUserCount'
17
+ },
18
+
19
+ initialize: function() {
20
+ if (TicTacToe.connection == null) {
21
+ TicTacToe.connection = new WebSocket('ws://192.168.0.2:8000/tictactoe');
22
+ };
23
+
24
+ TicTacToe.connection.onopen = this.join.bind(this);
25
+ TicTacToe.connection.onmessage = this.onMessage.bind(this);
26
+ TicTacToe.connection.onclose = this.onGameOver.bind(this);
27
+
28
+ this.setGameStats();
29
+ },
30
+
31
+ onMessage: function(event) {
32
+ var command = JSON.decode(event.data);
33
+
34
+ console.log('[RCV] ' + command.msg);
35
+
36
+ this[this.methodMap[command.msg]].call(this, command);
37
+ },
38
+
39
+ message: function(msg, options) {
40
+ var command = JSON.encode({msg: msg, data: options});
41
+
42
+ console.log('[SENT] ' + msg);
43
+
44
+ TicTacToe.connection.send(command);
45
+ },
46
+
47
+ setStatus: function(status) {
48
+ $('status').set('text', status);
49
+ },
50
+
51
+ setGameStats: function() {
52
+ $('game-stats').set('text', 'Wins: '+this.wins+' / Losses: '+this.wins+' / Draws: '+this.draws);
53
+ },
54
+
55
+ setUserCount: function(userCount) {
56
+ $('user-count').set('text', 'Number of players: ' + userCount);
57
+ },
58
+
59
+ join: function(event) {
60
+ this.message('join');
61
+
62
+ this.setStatus('Connecting you to a game...');
63
+ },
64
+
65
+ reset: function() {
66
+ $$('.cell').set('text', '');
67
+
68
+ this.join();
69
+ },
70
+
71
+ onQueued: function(command) {
72
+ this.setStatus('Waiting for another player...');
73
+ },
74
+
75
+ onStart: function(command) {
76
+ this.setStatus('Game found! Their turn first...');
77
+ },
78
+
79
+ onTurn: function(command) {
80
+ this.setStatus('Your turn...');
81
+ },
82
+
83
+ onMove: function(command) {
84
+ $('cell-'+command.data.x+'-'+command.data.y).set('text', command.key);
85
+
86
+ this.setStatus('Their turn...');
87
+ },
88
+
89
+ move: function(x, y) {
90
+ this.message('move', {x: x, y: y});
91
+ },
92
+
93
+ onGameOver: function(command) {
94
+ this.setStatus('Game over.');
95
+ this.setGameStats();
96
+ this.reset();
97
+ },
98
+
99
+ onWin: function(command) {
100
+ this.wins += 1;
101
+ this.setStatus('Game over. You win!');
102
+ this.setGameStats();
103
+ },
104
+
105
+ onLoss: function(command) {
106
+ this.losses += 1;
107
+ this.setStatus('Game over. You lose!');
108
+ this.setGameStats();
109
+ },
110
+
111
+ onDraw: function(command) {
112
+ this.draws += 1;
113
+ this.setStatus('Game over. It was a draw!');
114
+ this.setGameStats();
115
+ },
116
+
117
+ onUserCount: function(command) {
118
+ this.setUserCount(command.data);
119
+ }
120
+ });
121
+
122
+ $$('.cell').addEvent('click', function(event) {
123
+ try {
124
+ currentGame.move($(this).get('id').split('-')[1], $(this).get('id').split('-')[2]);
125
+ } catch(error) {
126
+ alert('Please wait while we connect you to a game...');
127
+ }
128
+ });
129
+
130
+ currentGame = new TicTacToe();
@@ -0,0 +1,215 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+
3
+ require 'rubygems'
4
+ require 'em-websocket-server'
5
+ require 'json'
6
+ require 'uuid'
7
+ require 'pp'
8
+
9
+ $games = {}
10
+ $waiting = nil
11
+ $status = nil
12
+
13
+ class StatusChannel < EM::Channel
14
+
15
+ def initialize
16
+ super
17
+ @count = 0
18
+ end
19
+
20
+ def increment
21
+ @count += 1
22
+ push @count
23
+ end
24
+
25
+ def decrement
26
+ @count -= 1
27
+ push @count
28
+ end
29
+ end
30
+
31
+ class Game < EM::Channel
32
+
33
+ attr_accessor :id, :player1, :player2, :current, :matrix
34
+
35
+
36
+
37
+ def initialize player1, player2
38
+ super()
39
+ @id = UUID.new
40
+ @player1 = player1
41
+ @player2 = player2
42
+ @matrix = (0..2).collect { [false, false, false] }
43
+ end
44
+
45
+ def set_turn p
46
+ @current = p
47
+ @current.turn!
48
+ end
49
+
50
+ def start!
51
+ @current = nil
52
+ @player1.start!
53
+ @player2.start!
54
+ toggle
55
+ end
56
+
57
+ def move p, data
58
+ if @current == p
59
+ unless @matrix[data["x"].to_i][data["y"].to_i]
60
+ @matrix[data["x"].to_i][data["y"].to_i] = p.key
61
+
62
+ @player1.send_move(p.key, data)
63
+ @player2.send_move(p.key, data)
64
+
65
+
66
+ winner = has_winner?
67
+ full = full?
68
+
69
+ if winner || full
70
+ @player1.send_command("game_over")
71
+ @player2.send_command("game_over")
72
+ if winner
73
+ p.send_command("win")
74
+ opponent(p).send_command("loss")
75
+ else full?
76
+ @player1.send_command("draw")
77
+ @player2.send_command("draw")
78
+ end
79
+ else
80
+ toggle
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def full?
87
+ @matrix.each do |row|
88
+ row.each do |col|
89
+ return false unless col
90
+ end
91
+ end
92
+ return true
93
+ end
94
+
95
+ def has_winner?
96
+ return true if @matrix[1][1] && (@matrix[0][0] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][2])
97
+ return true if @matrix[1][1] && (@matrix[0][2] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][0])
98
+ @matrix.each do |row|
99
+ return true if row[1] && (row[0] == row[1]) && (row[1] == row[2])
100
+ end
101
+ return false
102
+ end
103
+
104
+ def opponent p
105
+ @player1 == p ? @player2 : @player1
106
+ end
107
+
108
+ def toggle
109
+ set_turn(@current == @player1 ? @player2 : @player1)
110
+ end
111
+ end
112
+
113
+ class TickTackToeServer < WebSocketServer
114
+
115
+ attr_accessor :game_id, :key, :status_key
116
+
117
+ def on_connect
118
+ @status_key = $status.subscribe do |c|
119
+ send_user_count c
120
+ end
121
+ $status.increment
122
+ end
123
+
124
+ def on_disconnect
125
+ $status.unsubscribe @status_key
126
+ $status.decrement
127
+ delete_game!
128
+ end
129
+
130
+ def on_receive data
131
+
132
+ begin
133
+ msg = JSON.parse(data)
134
+ rescue
135
+ send_command "error"
136
+ return
137
+ end
138
+
139
+ case msg["msg"]
140
+ when "join"
141
+ if $waiting.empty?
142
+ $waiting.push(self)
143
+ send_command "queued"
144
+ else
145
+ $waiting.pop do |opponent|
146
+ game = Game.new(self, opponent)
147
+ self.key = "X"
148
+ opponent.key = "O"
149
+ self.game_id = opponent.game_id = game.id
150
+ game.start!
151
+ $games[game_id] = game
152
+ end
153
+ end
154
+ when "move"
155
+ if game
156
+ game.move self, msg["data"]
157
+
158
+ else
159
+ log "Cannot move on a nil game!"
160
+ end
161
+ end
162
+ end
163
+
164
+ def delete_game!
165
+ if $games.key?(game_id)
166
+ $games.delete(game_id)
167
+ end
168
+ end
169
+
170
+ def game
171
+ $games[game_id]
172
+ end
173
+
174
+ def turn!
175
+ send_command "turn"
176
+ end
177
+
178
+ def game_over!
179
+ delete_game!
180
+ send_command "game_over"
181
+ end
182
+
183
+ def start!
184
+ send_command "start"
185
+ end
186
+
187
+ def send_move key, data
188
+ send_message({:msg => "move", :key => key, :data => data}.to_json)
189
+ end
190
+
191
+ def send_command cmd
192
+ send_message({:msg => cmd}.to_json)
193
+ end
194
+
195
+ def send_user_count count
196
+ send_message({:msg => :user_count, :data => count}.to_json)
197
+ end
198
+
199
+
200
+ def send_message msg
201
+ super msg
202
+ puts "Sent: #{msg}"
203
+ end
204
+ end
205
+
206
+
207
+ EM.epoll
208
+ EM.set_descriptor_table_size(10240)
209
+
210
+ EM.run do
211
+ $waiting = EM::Queue.new
212
+ $status = StatusChannel.new
213
+
214
+ EM.start_server "0.0.0.0", 8000, TickTackToeServer
215
+ end
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>em-websocket-server test</title>
5
+ </head>
6
+ <body>
7
+
8
+ <h1>em-websocket-server timesync demo</h1>
9
+ <h2 id="time">opening socket</h2>
10
+
11
+ <script>
12
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
13
+
14
+ webSocket.onopen = function(event){
15
+ document.getElementById('time').innerHTML = 'waiting for socket';
16
+ };
17
+
18
+ webSocket.onmessage = function(event){
19
+ var object = JSON.parse(event.data);
20
+ document.getElementById('time').innerHTML = object.time;
21
+ };
22
+
23
+ webSocket.onclose = function(event){
24
+ document.getElementById('time').innerHTML = 'socket closed';
25
+ };
26
+ </script>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,35 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'em-websocket-server'
5
+ require 'json'
6
+
7
+ class TimeServer < WebSocketServer
8
+
9
+ def on_connect
10
+ @timer = EM.add_periodic_timer(5) do
11
+ sync_time
12
+ end
13
+ end
14
+
15
+ def on_disconnect
16
+ @timer
17
+ end
18
+
19
+ def on_receive msg
20
+ puts "msg rcv"
21
+ end
22
+
23
+ def sync_time
24
+ send_message({ :time => Time.now }.to_json)
25
+ end
26
+
27
+ end
28
+
29
+
30
+ EM.epoll
31
+ EM.set_descriptor_table_size(10240)
32
+
33
+ EM.run do
34
+ EM.start_server "0.0.0.0", 8000, TimeServer
35
+ end
@@ -20,18 +20,16 @@ class WebSocketServer < EM::Connection
20
20
  end
21
21
 
22
22
  def valid_origin?
23
- true
24
- #@@accepted_origins.include?(@headers["Origin"])
23
+ @@accepted_origins.empty? || @@accepted_origins.include?(origin)
25
24
  end
26
25
 
26
+ #not doing anything with this yet
27
27
  def valid_path?
28
28
  true
29
- #@@callbacks.key?(@path)
30
29
  end
31
30
 
32
31
  def valid_upgrade?
33
- true
34
- #@headers["Upgrade"] =~ /websocket/i
32
+ @headers["Upgrade"] =~ /websocket/i
35
33
  end
36
34
 
37
35
  def origin
@@ -99,13 +97,7 @@ class WebSocketServer < EM::Connection
99
97
  unless @connected
100
98
  handshake data
101
99
  else
102
- msg = data.gsub(/^(\x00)|(\xff)$/, "")
103
-
104
- if msg == "start"
105
- on_connect
106
- else
107
- on_receive msg
108
- end
100
+ on_receive data.gsub(/^(\x00)|(\xff)$/, "")
109
101
  end
110
102
  end
111
103
 
@@ -132,6 +124,8 @@ class WebSocketServer < EM::Connection
132
124
  send_headers
133
125
 
134
126
  @connected = true
127
+
128
+ on_connect
135
129
  end
136
130
 
137
131
  # send the handshake response headers to
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-websocket-server
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Simpson
@@ -35,6 +35,11 @@ files:
35
35
  - em-websocket-server.gemspec
36
36
  - examples/chat.rb
37
37
  - examples/chat.html
38
+ - examples/timesync.html
39
+ - examples/timesync.rb
40
+ - examples/tictactoe/tictactoe.html
41
+ - examples/tictactoe/tictactoe.js
42
+ - examples/tictactoe/tictactoe.rb
38
43
  - lib/em-websocket-server.rb
39
44
  has_rdoc: true
40
45
  homepage: http://github.com/dansimpson/em-websocket-server