gamespy_query 0.0.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,266 +1,325 @@
1
- # encoding: utf-8
2
- # GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
3
-
4
-
5
-
6
- require 'yaml'
7
- require_relative 'base'
8
- require_relative 'parser'
9
-
10
- module GamespyQuery
11
- class Socket < Base
12
- TIMEOUT = 3
13
- MAX_PACKETS = 7
14
-
15
- ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize
16
- BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
17
- CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
18
-
19
- FULL_INFO_PACKET_MP = [0xFF, 0xFF, 0xFF, 0x01].pack("c*")
20
- FULL_INFO_PACKET = [0xFF, 0xFF, 0xFF].pack("c*")
21
- SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
22
- PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
23
-
24
- STR_HOSTNAME = "hostname"
25
- STR_PLAYERS = "players"
26
- STR_DEATHS = "deaths_\x00\x00"
27
- STR_PLAYER = "player_\x00\x00"
28
- STR_TEAM = "team_\x00\x00"
29
- STR_SCORE = "score_\x00\x00"
30
-
31
- SPLIT = STR_X0
32
- STR_END = "\x00\x02"
33
- STR_EMPTY = Tools::STR_EMPTY
34
- STR_BLA = "%c%c%c%c".encode("ASCII-8BIT")
35
- STR_GARBAGE = "\x00\x04\x05\x06\a"
36
-
37
- RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
38
- RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
39
-
40
- RX_NO_CHALLENGE = /0@0$/
41
- RX_CHALLENGE = /0@/
42
- RX_CHALLENGE2 = /[^0-9\-]/si
43
- RX_SPLITNUM = /^splitnum\x00(.)/i
44
-
45
- def create_socket(*params)
46
- puts "Creating socket #{params}"
47
- _create_socket(*params)
48
- end
49
-
50
- def socket_send(*params)
51
- puts "Sending socket #{params}"
52
- _socket_send(*params)
53
- end
54
-
55
- def socket_receive(*params)
56
- puts "Receiving socket #{params}"
57
- _socket_receive(*params)
58
- end
59
-
60
- def socket_close(*params)
61
- puts "Closing socket #{params}"
62
- _socket_close(*params)
63
- end
64
-
65
- def get_string(*params)
66
- puts "Getting string #{params}"
67
- _get_string(*params)
68
- end
69
-
70
- if RUBY_PLATFORM =~ /mswin32/
71
- include System::Net
72
- include System::Net::Sockets
73
-
74
- def get_string(str)
75
- str.map {|e| e.chr}.join # begin; System::Text::Encoding.USASCII.GetString(reply[0]).to_s; rescue nil, Exception => e; Tools.log_exception(e); reply[0].map {|e| e.chr}.join; end
76
- end
77
-
78
- def _create_socket(host, port)
79
- @ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
80
- @s = UdpClient.new
81
- @s.client.receive_timeout = TIMEOUT * 1000
82
- @s.connect(host, port.to_i)
83
- end
84
-
85
- def _socket_send(packet)
86
- @s.Send(packet, packet.length)
87
- end
88
-
89
- def _socket_receive
90
- @s.Receive(@ip_end_point)
91
- end
92
-
93
- def _socket_close
94
- @s.close
95
- end
96
- else
97
- require 'socket'
98
- require 'timeout'
99
-
100
- def get_string(str)
101
- str
102
- end
103
-
104
- def _create_socket(host, port)
105
- @s = UDPSocket.new
106
- @s.connect(host, port)
107
- end
108
-
109
- def _socket_send(packet)
110
- @s.puts(packet)
111
- end
112
-
113
- def _socket_receive
114
- begin
115
- Timeout::timeout(TIMEOUT) do
116
- @s.recvfrom(4096)
117
- end
118
- rescue Timeout::Error
119
- #socket_close
120
- raise TimeoutError
121
- end
122
- end
123
-
124
- def _socket_close
125
- @s.close
126
- end
127
- end
128
-
129
- attr_accessor :silent
130
- def initialize(host, port, silent = nil)
131
- @host, @port, @silent = host, port, silent
132
- end
133
-
134
- # Supports challenge/response and multi-packet
135
- def sync
136
- game_data, key, reply = {}, nil, self.fetch
137
- return game_data if reply.nil?
138
-
139
- parser = Parser.new(reply)
140
- data = parser.parse
141
-
142
- game_data.merge!(data[:game])
143
- game_data["players"] = Parser.pretty_player_data2(data[:players]).sort {|a, b| a[:name].downcase <=> b[:name].downcase }
144
-
145
- game_data["ping"] = @ping unless @ping.nil?
146
-
147
- game_data
148
- end
149
-
150
- def fetch
151
- data = {}
152
- status, reply = nil, nil
153
-
154
- # Prepare socket / endpoint and connect
155
- create_socket(@host, @port)
156
-
157
- # Prepare and send challenge request
158
- # TODO: Randomize
159
- id_packet = ID_PACKET
160
- packet = CHALLENGE_PACKET + id_packet
161
- Tools.debug{"Sending Challenge (#{packet.length}): #{packet.inspect}"}
162
- sent = Time.now
163
-
164
- socket_send(packet)
165
-
166
- pings = []
167
-
168
- challenge, received = nil, nil
169
- begin
170
- # By default, Blocks until a message returns on this socket from a remote host.
171
- reply = socket_receive
172
- received = Time.now
173
- # TODO: Improve ping test?
174
- ping = received - sent
175
- pings << ping
176
- Tools.debug {"PingTest: #{ping}"}
177
- challenge = reply[0]
178
- rescue nil, Exception => e # Cannot use ensure as we want to keep the socket open :P
179
- socket_close
180
- raise e
181
- end
182
- return nil if challenge.nil? || challenge.empty?
183
-
184
- # Prepare challenge response, if needed
185
- str = get_string(challenge)
186
- Tools.debug{"Received challenge response (#{str.length}): #{str.inspect}"}
187
- need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
188
-
189
- if need_challenge
190
- Tools.debug {"Needs challenge!"}
191
- str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
192
- challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
193
- end
194
-
195
- # Prepare and send info request packet
196
- packet = need_challenge ? BASE_PACKET + id_packet + challenge_packet + FULL_INFO_PACKET_MP : BASE_PACKET + id_packet + FULL_INFO_PACKET_MP
197
- Tools.debug{"Sending:\n#{packet.inspect}"}
198
- sent = Time.now
199
- socket_send(packet)
200
-
201
- # Receive response to info request packet, up to 7 packets of information, each limited to 1400 bytes
202
- max_packets = MAX_PACKETS # Default max
203
- begin
204
- # In case some server info didn't fit in a single packet, there will be no proper END OF DATA signal
205
- # So we manually quit after reaching MAX_PACKETS.
206
- until data.size >= max_packets
207
- reply = socket_receive
208
-
209
- if data.empty?
210
- received = Time.now
211
- ping = received - sent
212
- pings << ping
213
- Tools.debug {"PingTest: #{ping}"}
214
- end
215
- index = 0
216
-
217
- game_data = get_string(reply[0])
218
- Tools.debug {"Received (#{data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
219
-
220
- if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
221
- splitnum = $1
222
- flag = splitnum.unpack("C")[0]
223
- index = (flag & 127).to_i
224
- last = flag & 0x80 > 0
225
- # Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
226
- max_packets = index + 1 if last # update the max
227
- Tools.debug {"Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{max_packets}"}
228
- else
229
- max_packets = 1
230
- end
231
- data[index] = game_data #.sub(RX_X0_S, STR_EMPTY) # Cut off first \x00 from package
232
- end
233
- ensure
234
- socket_close
235
- end
236
-
237
- pings.map!{|ping| (ping * 1000).round}
238
- pings_c = 0
239
- pings.each { |ping| pings_c += ping }
240
-
241
- ping = pings_c / pings.size
242
- Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
243
-
244
- return nil if data.keys.empty?
245
- @ping = ping
246
- data.each_pair {|k, d| Tools.debug {"GSPY Infos: #{k} #{d.size}"} } unless @silent || !$debug
247
-
248
- data
249
- end
250
-
251
- def handle_chr(number)
252
- number = ((number % 256)+256) if number < 0
253
- number = number % 256 if number > 255
254
- number
255
- end
256
- end
257
- end
258
-
259
- if $0 == __FILE__
260
- host = ARGV[0]
261
- port = ARGV[1]
262
- g = GamespyQuery::Socket.new(host, port)
263
- r = g.sync
264
- exit unless r
265
- puts r.to_yaml
266
- end
1
+ # encoding: utf-8
2
+ # GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
3
+
4
+
5
+
6
+ require 'yaml'
7
+ require_relative 'base'
8
+ require_relative 'parser'
9
+ require 'socket'
10
+
11
+ module GamespyQuery
12
+ module MultiSocket
13
+ def create_socket(*params)
14
+ puts "Creating socket #{params}"
15
+ _create_socket(*params)
16
+ end
17
+
18
+ def socket_send(*params)
19
+ puts "Sending socket #{params}"
20
+ _socket_send(*params)
21
+ end
22
+
23
+ def socket_receive(*params)
24
+ puts "Receiving socket #{params}"
25
+ _socket_receive(*params)
26
+ end
27
+
28
+ def socket_close(*params)
29
+ puts "Closing socket #{params}"
30
+ _socket_close(*params)
31
+ end
32
+
33
+ if RUBY_PLATFORM =~ /mswin32/
34
+ include System::Net
35
+ include System::Net::Sockets
36
+
37
+ def _create_socket(host, port)
38
+ @ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
39
+ @s = UdpClient.new
40
+ @s.client.receive_timeout = DEFAULT_TIMEOUT * 1000
41
+ @s.connect(host, port.to_i)
42
+ end
43
+
44
+ def _socket_send(packet)
45
+ @s.Send(packet, packet.length)
46
+ end
47
+
48
+ def _socket_receive
49
+ @s.Receive(@ip_end_point)
50
+ end
51
+
52
+ def _socket_close
53
+ @s.close
54
+ end
55
+ else
56
+ def _create_socket(host, port)
57
+ @s = UDPSocket.new
58
+ @s.connect(host, port)
59
+ end
60
+
61
+ def _socket_send(packet)
62
+ @s.puts(packet)
63
+ end
64
+
65
+ def _socket_receive
66
+ begin
67
+ Timeout::timeout(DEFAULT_TIMEOUT) do
68
+ @s.recvfrom(RECEIVE_SIZE)
69
+ end
70
+ rescue Timeout::Error
71
+ #socket_close
72
+ raise TimeoutError
73
+ end
74
+ end
75
+
76
+ def _socket_close
77
+ @s.close
78
+ end
79
+ end
80
+ end
81
+
82
+ class Socket < UDPSocket
83
+ DEFAULT_TIMEOUT = 3
84
+ MAX_PACKETS = 7
85
+
86
+ ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize
87
+ BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
88
+ CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
89
+
90
+ FULL_INFO_PACKET_MP = [0xFF, 0xFF, 0xFF, 0x01].pack("c*")
91
+ FULL_INFO_PACKET = [0xFF, 0xFF, 0xFF].pack("c*")
92
+ SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
93
+ PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
94
+
95
+ RECEIVE_SIZE = 1500
96
+
97
+ STR_HOSTNAME = "hostname"
98
+ STR_PLAYERS = "players"
99
+ STR_DEATHS = "deaths_\x00\x00"
100
+ STR_PLAYER = "player_\x00\x00"
101
+ STR_TEAM = "team_\x00\x00"
102
+ STR_SCORE = "score_\x00\x00"
103
+
104
+ SPLIT = STR_X0
105
+ STR_END = "\x00\x02"
106
+ STR_EMPTY = Tools::STR_EMPTY
107
+ STR_BLA = "%c%c%c%c".encode("ASCII-8BIT")
108
+ STR_GARBAGE = "\x00\x04\x05\x06\a"
109
+
110
+ RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
111
+ RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
112
+
113
+ RX_NO_CHALLENGE = /0@0$/
114
+ RX_CHALLENGE = /0@/
115
+ RX_CHALLENGE2 = /[^0-9\-]/si
116
+ RX_SPLITNUM = /^splitnum\x00(.)/i
117
+
118
+ # TODO: Support pings
119
+ # TODO: Handle .NET native sockets
120
+ include Funcs
121
+ STATE_INIT, STATE_SENT_CHALLENGE, STATE_RECEIVED_CHALLENGE, STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA, STATE_READY = 0, 1, 2, 3, 4, 5
122
+
123
+ attr_accessor :addr, :data, :state, :stamp, :needs_challenge, :max_packets, :failed
124
+
125
+ def initialize(addr, address_family = ::Socket::AF_INET)
126
+ @addr, @data, @state, @max_packets = addr, {}, 0, MAX_PACKETS
127
+ @id_packet = ID_PACKET
128
+ @packet = CHALLENGE_PACKET + @id_packet
129
+
130
+ super(address_family)
131
+ self.connect(*addr.split(":"))
132
+ end
133
+
134
+ def state=(state); @stamp = Time.now; @state = state; end
135
+
136
+ def valid?; @state == STATE_READY; end
137
+
138
+ def handle_write
139
+ #STDOUT.puts "Write: #{self.inspect}, #{self.state}"
140
+
141
+ r = true
142
+ begin
143
+ case self.state
144
+ when STATE_INIT
145
+ STDOUT.puts "Write (0): #{self.inspect}"
146
+ # Send Challenge
147
+ self.puts @packet
148
+ self.state = STATE_SENT_CHALLENGE
149
+ when STATE_RECEIVED_CHALLENGE
150
+ STDOUT.puts "Write (2): #{self.inspect}"
151
+ # Send Challenge response
152
+ self.puts self.needs_challenge ? BASE_PACKET + @id_packet + self.needs_challenge + FULL_INFO_PACKET_MP : BASE_PACKET + @id_packet + FULL_INFO_PACKET_MP
153
+ self.state = STATE_SENT_CHALLENGE_RESPONSE
154
+ end
155
+ rescue => e
156
+ STDOUT.puts "Error: #{e.message}, #{self.inspect}"
157
+ self.failed = true
158
+ r = false
159
+ close
160
+ end
161
+
162
+ =begin
163
+ if Time.now - self.stamp > @timeout
164
+ STDOUT.puts "TimedOut: #{self.inspect}"
165
+ self.failed = true
166
+ r = false
167
+ close unless closed?
168
+ end
169
+ =end
170
+ r
171
+ end
172
+
173
+ def handle_read
174
+ #STDOUT.puts "Read: #{self.inspect}, #{self.state}"
175
+
176
+ r = true
177
+ case self.state
178
+ when STATE_SENT_CHALLENGE
179
+ begin
180
+ data = self.recvfrom_nonblock(RECEIVE_SIZE)
181
+ STDOUT.puts "Read (1): #{self.inspect}: #{data}"
182
+
183
+ handle_challenge get_string(data[0])
184
+
185
+ self.state = STATE_RECEIVED_CHALLENGE
186
+ rescue => e
187
+ STDOUT.puts "Error: #{e.message}, #{self.inspect}"
188
+ self.failed = true
189
+ r = false
190
+ close
191
+ end
192
+ when STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA
193
+ begin
194
+ data = self.recvfrom_nonblock(RECEIVE_SIZE)
195
+ STDOUT.puts "Read (3,4): #{self.inspect}: #{data}"
196
+ self.state = STATE_RECEIVE_DATA
197
+
198
+ game_data = get_string(data[0])
199
+ Tools.debug {"Received (#{self.data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
200
+
201
+ index = handle_splitnum game_data
202
+
203
+ self.data[index] = game_data
204
+
205
+ if self.data.size >= self.max_packets # OR we received the end-packet and all packets required
206
+ STDOUT.puts "Received packet limit: #{self.inspect}"
207
+ self.state = STATE_READY
208
+ r = false
209
+ close unless closed?
210
+ end
211
+ rescue => e
212
+ STDOUT.puts "Error: #{e.message}, #{self.inspect}"
213
+ self.failed = true
214
+ r = false
215
+ close
216
+ end
217
+ end
218
+ r
219
+ end
220
+
221
+ def handle_exc
222
+ STDOUT.puts "Exception: #{self.inspect}"
223
+ close
224
+ self.failed = true
225
+
226
+ false
227
+ end
228
+
229
+
230
+ def handle_splitnum game_data
231
+ index = 0
232
+ if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
233
+ splitnum = $1
234
+ flag = splitnum.unpack("C")[0]
235
+ index = (flag & 127).to_i
236
+ last = flag & 0x80 > 0
237
+ # Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
238
+ self.max_packets = index + 1 if last # update the max
239
+ STDOUT.puts "Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{self.max_packets}"
240
+ else
241
+ self.max_packets = 1
242
+ end
243
+
244
+ index
245
+ end
246
+
247
+ def handle_challenge str
248
+ #STDOUT.puts "Received challenge response (#{str.length}): #{str.inspect}"
249
+ need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
250
+ if need_challenge
251
+ str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
252
+ challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
253
+ self.needs_challenge = challenge_packet
254
+ end
255
+ end
256
+
257
+ def handle_state; [STATE_INIT, STATE_RECEIVED_CHALLENGE].include? state; end
258
+
259
+ # Supports challenge/response and multi-packet
260
+ def sync
261
+ game_data, key, reply = {}, nil, self.fetch
262
+ return game_data if reply.nil?
263
+
264
+ parser = Parser.new(reply)
265
+ data = parser.parse
266
+
267
+ game_data.merge!(data[:game])
268
+ game_data["players"] = Parser.pretty_player_data2(data[:players]).sort {|a, b| a[:name].downcase <=> b[:name].downcase }
269
+
270
+ game_data["ping"] = @ping unless @ping.nil?
271
+
272
+ game_data
273
+ end
274
+
275
+ def fetch
276
+ pings = []
277
+ r = self.data
278
+ begin
279
+ until valid?
280
+ if handle_state
281
+ if IO.select(nil, [self], nil, DEFAULT_TIMEOUT)
282
+ handle_write
283
+ else
284
+ raise "TimeOut"
285
+ end
286
+ else
287
+ if IO.select([self], nil, nil, DEFAULT_TIMEOUT)
288
+ handle_read
289
+ else
290
+ raise "TimeOut"
291
+ end
292
+ end
293
+ end
294
+ data.each_pair {|k, d| Tools.debug {"GSPY Infos: #{k} #{d.size}"} } unless @silent || !$debug
295
+
296
+ pings.map!{|ping| (ping * 1000).round}
297
+ pings_c = 0
298
+ pings.each { |ping| pings_c += ping }
299
+
300
+ ping = pings.size == 0 ? nil : pings_c / pings.sizeha
301
+ Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
302
+ @ping = ping
303
+ rescue => e
304
+ STDOUT.puts "Error during fetch #{self.inspect}: #{e.message}"
305
+ r = nil
306
+ end
307
+ r
308
+ end
309
+ end
310
+ end
311
+
312
+ if $0 == __FILE__
313
+ host, port = if ARGV.size > 1
314
+ ARGV
315
+ else
316
+ ARGV[0].split(":")
317
+ end
318
+ time_start = Time.now
319
+ g = GamespyQuery::Socket.new("#{host}:#{port}")
320
+ r = g.sync
321
+ time_taken = Time.now - time_start
322
+ puts "Took: #{time_taken}s"
323
+ exit unless r
324
+ puts r.to_yaml
325
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'socket'
2
+
3
+ module GamespyQuery
4
+ class SocketMaster < Base
5
+ FILL_UP_ON_SPACE = true
6
+ DEFAULT_MAX_CONNECTIONS = 128
7
+
8
+ attr_accessor :timeout, :max_connections
9
+
10
+ def initialize addrs
11
+ @addrs = addrs
12
+
13
+ @timeout, @max_connections = Socket::DEFAULT_TIMEOUT, DEFAULT_MAX_CONNECTIONS # Per select iteration
14
+ end
15
+
16
+ def process!
17
+ sockets = []
18
+
19
+ until @addrs.empty?
20
+ addrs = @addrs.shift @max_connections
21
+ queue = addrs.map { |addr| Socket.new(addr) }
22
+
23
+ sockets += queue
24
+
25
+ until queue.empty?
26
+ # Fill up the Sockets pool until max_conn
27
+ if FILL_UP_ON_SPACE && queue.size < @max_connections
28
+ addrs = @addrs.shift (@max_connections - queue.size)
29
+ socks = addrs.map { |addr| Socket.new(addr) }
30
+
31
+ queue += socks
32
+ sockets += socks
33
+ end
34
+
35
+ write_sockets, read_sockets = queue.reject {|s| s.valid? }.partition {|s| s.handle_state }
36
+
37
+ unless ready = IO.select(read_sockets, write_sockets, nil, @timeout)
38
+ puts "Timeout, no usable sockets in current queue, within timeout period"
39
+ queue.each{|s| s.close unless s.closed?}
40
+ queue = []
41
+ next
42
+ end
43
+
44
+ puts "Sockets: #{queue.size}, AddrsLeft: #{@addrs.size}, ReadReady: #{"#{ready[0].size} / #{read_sockets.size}, WriteReady: #{ready[1].size} / #{write_sockets.size}, ExcReady: #{ready[2].size} / #{queue.size}" unless ready.nil?}"
45
+
46
+ # Read
47
+ ready[0].each { |s| queue.delete(s) unless s.handle_read() }
48
+
49
+ # Write
50
+ ready[1].each { |s| queue.delete(s) unless s.handle_write() }
51
+
52
+ # Exceptions
53
+ #ready[2].each { |s| queue.delete(s) unless s.handle_exc }
54
+ end
55
+ end
56
+
57
+ return sockets
58
+ end
59
+ end
60
+ end
61
+
62
+ if $0 == __FILE__
63
+ require_relative 'master'
64
+ srv = File.open(ARGV[0] || "servers.txt") { |f| f.read }
65
+ master = GamespyQuery::Master.new
66
+ addrs = master.get_server_list srv
67
+
68
+ time_start = Time.now
69
+ sm = GamespyQuery::SocketMaster.new(addrs)
70
+ sockets = sm.process!
71
+ time_taken = Time.now - time_start
72
+
73
+ cool = sockets.count {|v| v.valid? }
74
+ dude = sockets.size - cool
75
+
76
+ puts "Success: #{cool}, Failed: #{dude}"
77
+ puts "Took: #{time_taken}s"
78
+ end