Class: Chingu::GameStates::NetworkServer

Inherits:
NetworkState show all
Defined in:
lib/chingu/game_states/network_server.rb

Overview

The following callbacks can be overwritten to add your game logic:

on_connect(socket)        # when the TCP connection to the server is opened
on_disconnect(socket)     # when server dies or disconnects you
on_data(socket, data)     # when raw data arrives from server, if not overloaded this will unpack and call on_msg
on_msg(socket, msg)       # an incoming msgs, could be a ruby hash or array or whatever datastructure you've chosen to send from server
on_start                  # called when socket is listening and ready
on_start_error(msg)       # callback for any error during server setup process

Usage:

 ServerState < Chingu::GameStates::NetworkServer
   # incoming client/connection...
   def on_connect(socket)
     send_msg(:cmd => :ping, :timestamp => Gosu::milliseconds)
   end

   def on_msg(socket, msg)
     if msg[:cmd] == :pong
       latency = Gosu::milliseconds - msg[:timestamp]
       puts "Server/Client roundtrip #{latency}ms"
     end
   end
 end

 push_game_state NetworkServer.new(:address => "127.0.0.1", :port => 7778).start

NetworkServer works mostly like NetworkClient with a few differences
- since a server handles many sockets (1 for each connected client) all callbacks first argument is 'socket'
- same with outgoing packets, #send_data and #send_msg, first argument is a socket.

A good idea is to have a socket-ivar in your Player-model and a Player.find_by_socket(socket)

Constant Summary

Constants inherited from NetworkState

Chingu::GameStates::NetworkState::DEFAULT_PORT, Chingu::GameStates::NetworkState::PACKET_HEADER_FORMAT, Chingu::GameStates::NetworkState::PACKET_HEADER_LENGTH

Instance Attribute Summary collapse

Attributes inherited from NetworkState

#address, #bytes_received, #bytes_sent, #packets_received, #packets_sent, #port

Attributes inherited from Chingu::GameState

#game_objects, #game_state_manager, #options, #previous_game_state

Attributes included from Helpers::InputDispatcher

#input_clients

Attributes included from Helpers::GameObject

#game_objects

Instance Method Summary collapse

Methods inherited from NetworkState

#reset_counters

Methods inherited from Chingu::GameState

#button_down, #button_up, #close_game, #draw, #draw_trait, #filename, #setup, #setup_trait, #to_s, #to_sym, trait, #trait_options, traits, #update_trait

Methods included from Helpers::ClassInheritableAccessor

included

Methods included from Helpers::InputClient

#add_inputs, #holding?, #holding_all?, #holding_any?, #input, #input=, #on_input

Methods included from Helpers::InputDispatcher

#add_input_client, #dispatch_button_down, #dispatch_button_up, #dispatch_input_for, #remove_input_client

Methods included from Helpers::GameObject

#game_objects_of_class, #load_game_objects, #save_game_objects

Methods included from Helpers::GFX

#draw_arc, #draw_circle, #draw_rect, #fill, #fill_arc, #fill_circle, #fill_gradient, #fill_rect

Constructor Details

#initialize(options = {}) ⇒ NetworkServer

Returns a new instance of NetworkServer



74
75
76
77
78
79
80
81
82
83
# File 'lib/chingu/game_states/network_server.rb', line 74

def initialize(options = {})
  super(options)

  @max_read_per_update = options[:max_read_per_update] || 20000
  @max_connections = options[:max_connections] || 256

  @socket = nil
  @sockets = []
  @packet_buffers = Hash.new
end

Instance Attribute Details

#max_connectionsObject (readonly)

Returns the value of attribute max_connections



72
73
74
# File 'lib/chingu/game_states/network_server.rb', line 72

def max_connections
  @max_connections
end

#socketObject (readonly)

Returns the value of attribute socket



72
73
74
# File 'lib/chingu/game_states/network_server.rb', line 72

def socket
  @socket
end

#socketsObject (readonly)

Returns the value of attribute sockets



72
73
74
# File 'lib/chingu/game_states/network_server.rb', line 72

def sockets
  @sockets
end

Instance Method Details

#broadcast_msg(msg) ⇒ Object

Broadcast 'msg' to all connected clients. Returns amount of data sent.



207
208
209
210
# File 'lib/chingu/game_states/network_server.rb', line 207

def broadcast_msg(msg)
  data = Marshal.dump(msg)
  @sockets.inject(0) {|tot, s| tot + send_data(s, data) }
end

#disconnect_client(socket) ⇒ Object

Shuts down all communication (closes socket) with a specific socket



236
237
238
239
240
241
242
243
# File 'lib/chingu/game_states/network_server.rb', line 236

def disconnect_client(socket)
  socket.close
rescue Errno::ENOTCONN
ensure
  @sockets.delete socket
  @packet_buffers.delete socket
  on_disconnect(socket)
end

#flushObject

Ensure that the buffer is cleared of data to write (call at the end of update or, at least after all sends).



246
247
248
249
250
251
252
253
254
# File 'lib/chingu/game_states/network_server.rb', line 246

def flush
  @sockets.each do |socket| 
    begin
      socket.flush 
    rescue IOError
      disconnect_client(socket)
    end
  end
end

#handle_incoming_connectionsObject



150
151
152
153
154
155
156
157
158
159
# File 'lib/chingu/game_states/network_server.rb', line 150

def handle_incoming_connections
  begin
    while @sockets.size < @max_connections and @socket and socket = @socket.accept_nonblock
      @sockets << socket
      @packet_buffers[socket] = PacketBuffer.new
      on_connect(socket)
    end
  rescue IO::WaitReadable, Errno::EAGAIN, Errno::EINTR
  end
end

#handle_incoming_data(max_size = @max_read_per_update) ⇒ Object

Call this from your update() to read from socket. handle_incoming_data will call on_data(raw_data) when stuff comes on on the socket.



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/chingu/game_states/network_server.rb', line 165

def handle_incoming_data(max_size = @max_read_per_update)
  @sockets.each do |socket|
    if IO.select([socket], nil, nil, 0.0)
      begin
        packet, sender = socket.recvfrom(max_size)
        on_data(socket, packet)
      rescue Errno::ECONNABORTED, Errno::ECONNRESET, IOError
        disconnect_client(socket)
      end
    end
  end
end

#on_connect(socket) ⇒ Object

on_connect will be called when client successfully makes a connection to server



139
140
141
# File 'lib/chingu/game_states/network_server.rb', line 139

def on_connect(socket)
  puts "[Client Connected: #{socket}]"      if @debug
end

#on_data(socket, data) ⇒ Object

on_data(data) will be called from handle_incoming_data() by default.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/chingu/game_states/network_server.rb', line 181

def on_data(socket, data)
  buffer = @packet_buffers[socket]

  buffer.buffer_data data

  @bytes_received += data.length

  while packet = buffer.next_packet
    @packets_received += 1
    begin
      on_msg(socket, Marshal.load(packet))
    rescue TypeError
      disconnect_client(socket)
      break
    end
  end
end

#on_disconnect(socket) ⇒ Object

on_disconnect will be called when server disconnects client for whatever reason



146
147
148
# File 'lib/chingu/game_states/network_server.rb', line 146

def on_disconnect(socket)
  puts "[Client Disconnected: #{socket}]"   if @debug
end

#on_msg(socket, packet) ⇒ Object

Handler when message packets are received. Should be overriden in your code.



200
201
202
# File 'lib/chingu/game_states/network_server.rb', line 200

def on_msg(socket, packet)
  # should be overridden.
end

#on_startObject

Callback for when Socket listens correctly on given host/port



104
105
106
# File 'lib/chingu/game_states/network_server.rb', line 104

def on_start
  puts "* Server listening on #{address}:#{port}"          if @debug
end

#on_start_error(msg) ⇒ Object

Callback for when something goes wrong with startup (when making TCP socket listen to a port)



111
112
113
114
115
116
# File 'lib/chingu/game_states/network_server.rb', line 111

def on_start_error(msg)
  if @debug
    puts "Can't start server on #{address}:#{port}:\n"
    puts msg
  end
end

#send_data(socket, data) ⇒ Object

Send raw 'data' to the 'socket' Returns amount of data sent, including headers.



222
223
224
225
226
227
228
229
230
231
# File 'lib/chingu/game_states/network_server.rb', line 222

def send_data(socket, data)
  length = socket.write([data.length].pack(PACKET_HEADER_FORMAT))
  length += socket.write(data)
  @packets_sent += 1
  @bytes_sent += length
  length
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
  disconnect_client(socket)
  0
end

#send_msg(socket, msg) ⇒ Object

Send 'msg' to a specific client 'socket'. Returns amount of data sent.



215
216
217
# File 'lib/chingu/game_states/network_server.rb', line 215

def send_msg(socket, msg)
  send_data(socket, Marshal.dump(msg))
end

#start(address = nil, port = nil) ⇒ Object

Start server



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/chingu/game_states/network_server.rb', line 88

def start(address = nil, port = nil)
  @address = address if address
  @port = port  if port
  begin
    @socket = TCPServer.new(@address, @port)
    on_start
  rescue
    on_start_error($!)
  end
  
  return self
end

#stopObject Also known as: close

Stops server



259
260
261
262
263
264
265
266
267
268
# File 'lib/chingu/game_states/network_server.rb', line 259

def stop
  begin
    @socket.close if @socket and not @socket.closed?
  rescue Errno::ENOTCONN
  end

  @socket = nil
  @sockets.each {|socket| disconnect_client(socket) }
  @sockets = []
end

#updateObject

Default network loop: 1) Save incoming connections with #handle_incoming_connections 2) read raw data from server with #handle_incoming_data 3) #handle_incoming_data call #on_data(data) 4) #on_data(data) will call #on_msgs(msg) 5) send all buffered broadcast data in one fell swoop



127
128
129
130
131
132
133
134
# File 'lib/chingu/game_states/network_server.rb', line 127

def update
  if @socket && !@socket.closed?
    handle_incoming_connections
    handle_incoming_data
  end

  super
end