mirror of
https://github.com/chocolate-doom/master-server.git
synced 2024-11-10 07:11:38 +00:00
Decode and save metadata acquired when querying new servers. Add a
metadata request message to retrieve the metadata that was obtained. Subversion-branch: /master Subversion-revision: 2194
This commit is contained in:
parent
071f2aeffa
commit
2d40ae549a
2 changed files with 132 additions and 30 deletions
110
chocolate-master
110
chocolate-master
|
@ -23,6 +23,7 @@
|
|||
|
||||
import socket
|
||||
import struct
|
||||
import simplejson
|
||||
from select import select
|
||||
from time import time, strftime
|
||||
|
||||
|
@ -50,32 +51,49 @@ NET_MASTER_PACKET_TYPE_ADD = 0
|
|||
NET_MASTER_PACKET_TYPE_ADD_RESPONSE = 1
|
||||
NET_MASTER_PACKET_TYPE_QUERY = 2
|
||||
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE = 3
|
||||
NET_MASTER_PACKET_TYPE_GET_METADATA = 4
|
||||
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE = 5
|
||||
|
||||
# Address and port to listen on.
|
||||
|
||||
UDP_ADDRESS = socket.inet_ntoa(struct.pack(">l", socket.INADDR_ANY))
|
||||
UDP_PORT = 2342
|
||||
|
||||
def read_string(packet):
|
||||
""" Given binary packet data, read a NUL-terminated string, returning
|
||||
the remainder of the packet data and the decoded string. """
|
||||
|
||||
terminator = struct.pack("b", 0)
|
||||
|
||||
if terminator not in packet:
|
||||
raise Exception("String terminator not found")
|
||||
|
||||
strlen = packet.index(terminator)
|
||||
|
||||
result, = struct.unpack("%ss" % strlen, packet[0:strlen])
|
||||
|
||||
return packet[strlen + 1:], result
|
||||
|
||||
class Server:
|
||||
""" A server that has registered itself. """
|
||||
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.verified = False
|
||||
self.metadata = {}
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
self.add_time = time()
|
||||
|
||||
def set_metadata(self, metadata):
|
||||
self.metadata = metadata
|
||||
|
||||
def timed_out(self):
|
||||
return time() - self.add_time > SERVER_TIMEOUT
|
||||
|
||||
def encode_addr(self):
|
||||
s = "%s:%i" % self.addr
|
||||
|
||||
# Encode string along with terminating NUL.
|
||||
|
||||
return struct.pack("%is" % (len(s) + 1), s)
|
||||
def __str__(self):
|
||||
return "%s:%i" % self.addr
|
||||
|
||||
class MasterServer:
|
||||
def open_log_file(self):
|
||||
|
@ -100,10 +118,30 @@ class MasterServer:
|
|||
""" Send a query to the specified server. """
|
||||
|
||||
packet = struct.pack(">h", NET_PACKET_TYPE_QUERY)
|
||||
|
||||
|
||||
self.query_sock.sendto(packet, server.addr)
|
||||
|
||||
def parse_query_response(self, data, addr):
|
||||
def parse_query_data(self, data):
|
||||
""" Read the data from a query response. """
|
||||
|
||||
data, version = read_string(data)
|
||||
|
||||
server_state, num_players, max_players, mode, mission \
|
||||
= struct.unpack("bbbbb", data[0:5])
|
||||
|
||||
data, server_name = read_string(data[5:])
|
||||
|
||||
# Not all of this is of interest to us. Some of it will
|
||||
# be out of date fairly quickly because the master doesn't
|
||||
# query the servers very often.
|
||||
|
||||
return {
|
||||
"version": version,
|
||||
"max_players": max_players,
|
||||
"name": server_name
|
||||
}
|
||||
|
||||
def process_query_response(self, data, addr):
|
||||
""" Parse a packet received (presumably) in response to a
|
||||
query that we sent to a server. """
|
||||
|
||||
|
@ -121,8 +159,11 @@ class MasterServer:
|
|||
if packet_type != NET_PACKET_TYPE_QUERY_RESPONSE:
|
||||
return
|
||||
|
||||
# TODO: Process rest of details so that we can maintain
|
||||
# some information about list of servers?
|
||||
# Read metadata from query and store it for future use.
|
||||
|
||||
metadata = self.parse_query_data(data[2:])
|
||||
metadata["address"], metadata["port"] = addr
|
||||
server.set_metadata(metadata)
|
||||
|
||||
# Server responded to our query, so it is verified.
|
||||
# We can send a positive response to its add request.
|
||||
|
@ -141,26 +182,24 @@ class MasterServer:
|
|||
|
||||
self.sock.sendto(packet, addr)
|
||||
|
||||
def response_packets(self):
|
||||
""" Convert the list of servers into a list of payload strings
|
||||
def strings_to_packets(self, strings):
|
||||
""" Convert a list of strings into a list of payload strings
|
||||
for responding to queries. """
|
||||
|
||||
packets = [struct.pack("")]
|
||||
|
||||
for server in self.servers.values():
|
||||
encoded_addr = server.encode_addr()
|
||||
for string in strings:
|
||||
|
||||
# Only include verified servers.
|
||||
# Encode string along with terminating NUL.
|
||||
|
||||
if not server.verified:
|
||||
continue
|
||||
encoded_str = struct.pack("%is" % (len(string) + 1), string)
|
||||
|
||||
# Start a new packet?
|
||||
|
||||
if len(packets[-1]) + len(encoded_addr) > MAX_RESPONSE_LEN:
|
||||
if len(packets[-1]) + len(encoded_str) > MAX_RESPONSE_LEN:
|
||||
packets.append(struct.pack(""))
|
||||
|
||||
packets[-1] += encoded_addr
|
||||
packets[-1] += encoded_str
|
||||
|
||||
return packets
|
||||
|
||||
|
@ -199,13 +238,38 @@ class MasterServer:
|
|||
""" Process a query message received from a client. """
|
||||
|
||||
self.log_output(addr, "Query")
|
||||
packets = self.response_packets()
|
||||
|
||||
for packet in packets:
|
||||
# Generate a list of strings representing servers. Only include
|
||||
# verified servers.
|
||||
|
||||
verified_servers = filter(lambda s: s.verified, self.servers.values())
|
||||
strings = [ str(server) for server in verified_servers]
|
||||
|
||||
# Send response packets.
|
||||
|
||||
for packet in self.strings_to_packets(strings):
|
||||
self.send_message(addr,
|
||||
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE,
|
||||
packet)
|
||||
|
||||
def process_metadata_request(self, addr):
|
||||
""" Process a metadata request from a client. """
|
||||
|
||||
self.log_output(addr, "Metadata request")
|
||||
|
||||
# Generate a list of strings containing JSON-encoded metadata
|
||||
# about servers. Only include verified servers.
|
||||
|
||||
verified_servers = filter(lambda s: s.verified, self.servers.values())
|
||||
strings = [ simplejson.dumps(s.metadata) for s in verified_servers]
|
||||
|
||||
# Send response packets.
|
||||
|
||||
for packet in self.strings_to_packets(strings):
|
||||
self.send_message(addr,
|
||||
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE,
|
||||
packet)
|
||||
|
||||
def process_packet(self, data, addr):
|
||||
""" Process a packet received from a server. """
|
||||
|
||||
|
@ -215,6 +279,8 @@ class MasterServer:
|
|||
self.process_add_to_master(addr)
|
||||
elif packet_type == NET_MASTER_PACKET_TYPE_QUERY:
|
||||
self.process_query(addr)
|
||||
elif packet_type == NET_MASTER_PACKET_TYPE_GET_METADATA:
|
||||
self.process_metadata_request(addr)
|
||||
|
||||
def rx_packet(self):
|
||||
""" Invoked when a packet is received. """
|
||||
|
@ -232,7 +298,7 @@ class MasterServer:
|
|||
data, addr = self.query_sock.recvfrom(1024)
|
||||
|
||||
try:
|
||||
self.parse_query_response(data, addr)
|
||||
self.process_query_response(data, addr)
|
||||
except Exception, e:
|
||||
print e
|
||||
|
||||
|
|
|
@ -24,11 +24,14 @@
|
|||
import socket
|
||||
import sys
|
||||
import struct
|
||||
import simplejson
|
||||
|
||||
NET_MASTER_PACKET_TYPE_ADD = 0
|
||||
NET_MASTER_PACKET_TYPE_ADD_RESPONSE = 1
|
||||
NET_MASTER_PACKET_TYPE_QUERY = 2
|
||||
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE = 3
|
||||
NET_MASTER_PACKET_TYPE_GET_METADATA = 4
|
||||
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE = 5
|
||||
|
||||
UDP_PORT = 2342
|
||||
|
||||
|
@ -62,7 +65,7 @@ def read_string(packet):
|
|||
terminator = struct.pack("b", 0)
|
||||
strlen = packet.index(terminator)
|
||||
|
||||
result = struct.unpack("%ss" % strlen, packet[0:strlen])
|
||||
result, = struct.unpack("%ss" % strlen, packet[0:strlen])
|
||||
|
||||
return packet[strlen + 1:], result
|
||||
|
||||
|
@ -91,15 +94,17 @@ def add_to_master(addr_str):
|
|||
|
||||
print "Address added to master."
|
||||
|
||||
def parse_query_response(packet):
|
||||
servers = []
|
||||
def decode_string_list(packet):
|
||||
""" Decode binary data containing NUL-terminated strings. """
|
||||
|
||||
strings = []
|
||||
|
||||
while len(packet) > 0:
|
||||
packet, addr_str = read_string(packet)
|
||||
packet, string = read_string(packet)
|
||||
|
||||
servers.append(addr_str)
|
||||
strings.append(string)
|
||||
|
||||
return servers
|
||||
return strings
|
||||
|
||||
def query_master(addr_str):
|
||||
""" Query a master server for its list of server IP addresses. """
|
||||
|
@ -119,20 +124,51 @@ def query_master(addr_str):
|
|||
|
||||
response = get_response(sock, addr, NET_MASTER_PACKET_TYPE_QUERY_RESPONSE)
|
||||
|
||||
servers = parse_query_response(response)
|
||||
servers = decode_string_list(response)
|
||||
|
||||
print "%i servers" % len(servers)
|
||||
|
||||
for s in servers:
|
||||
print "\t%s" % s
|
||||
|
||||
def get_metadata(addr_str):
|
||||
""" Query a master server for metadata about its servers. """
|
||||
|
||||
addr = (socket.gethostbyname(addr_str), UDP_PORT)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
# Send request
|
||||
|
||||
print "Sending metadata query to master at %s" % str(addr)
|
||||
|
||||
send_message(sock, addr, NET_MASTER_PACKET_TYPE_GET_METADATA)
|
||||
|
||||
# Receive response
|
||||
|
||||
print "Waiting for response..."
|
||||
|
||||
response = get_response(sock, addr, NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE)
|
||||
|
||||
servers = decode_string_list(response)
|
||||
|
||||
print "%i servers" % len(servers)
|
||||
|
||||
for json in servers:
|
||||
metadata = simplejson.loads(json)
|
||||
print "\tServer: %s:%i" % (metadata["address"], metadata["port"])
|
||||
print "\t\tName: %s" % metadata["name"]
|
||||
print "\t\tVersion: %s" % metadata["version"]
|
||||
print "\t\tMax. players: %i" % metadata["max_players"]
|
||||
|
||||
if len(sys.argv) > 2 and sys.argv[1] == "query":
|
||||
query_master(sys.argv[2])
|
||||
elif len(sys.argv) > 2 and sys.argv[1] == "add":
|
||||
add_to_master(sys.argv[2])
|
||||
elif len(sys.argv) > 2 and sys.argv[1] == "get-metadata":
|
||||
get_metadata(sys.argv[2])
|
||||
else:
|
||||
print "Usage:"
|
||||
print "chocolate-master-test.py query <address>"
|
||||
print "chocolate-master-test.py add <address>"
|
||||
|
||||
print "chocolate-master-test.py get-metadata <address>"
|
||||
|
||||
|
|
Loading…
Reference in a new issue