changeset 103:616421834e19

updated pygnetic, added discovery server handling
author Szymon Wróblewski <bluex0@gmail.com>
date Fri, 07 Sep 2012 02:22:10 +0200
parents 361178f45eb6
children 86f4b5469bd8
files zrpn/discovery_server.py zrpn/main.py zrpn/pygnetic/discovery.py zrpn/pygnetic/message.py zrpn/server.py
diffstat 5 files changed, 115 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zrpn/discovery_server.py	Fri Sep 07 02:22:10 2012 +0200
@@ -0,0 +1,26 @@
+import logging
+import pygnetic
+from pygnetic.discovery import DiscoveryServer
+
+_logger = logging.getLogger(__name__)
+
+if __name__ == '__main__':
+    import argparse
+    parser = argparse.ArgumentParser(description='Server discovery service.')
+    parser.add_argument('-i', '--host', type=str, default='',
+                        help='Server host IP')
+    parser.add_argument('-p', '--port', type=int, default=5000,
+                        help='Server port')
+    parser.add_argument('-d', '--debug', action='store_true',
+                        help='Enable debugging')
+    args = parser.parse_args()
+    if args.debug:
+        pygnetic.init(logging_lvl=logging.DEBUG)
+    else:
+        pygnetic.init()
+    _logger.info('Server started (Use Control-C to exit)')
+    try:
+        DiscoveryServer(args.host, args.port).serve_forever()
+    except KeyboardInterrupt:
+        pass
+    _logger.info('Exiting')
--- a/zrpn/main.py	Thu Sep 06 21:35:41 2012 +0200
+++ b/zrpn/main.py	Fri Sep 07 02:22:10 2012 +0200
@@ -43,8 +43,8 @@
 
 
 class MainMenu(sgc.Menu):
-    func_dict = lambda self: {"single": self.single, "multi": self.multi,
-                              "exit": sys.exit}
+    func_dict = lambda self: {"single": self.single, "auto_m": self.auto_m,
+                              "multi": self.multi, "exit": sys.exit}
 
     def single(self):
         self.remove()
@@ -60,6 +60,24 @@
         ip = str(ip)
         client.Client(ip, 10000).run()
 
+    def auto_m(self):
+        import pygnetic as net
+        net.init(True, logging_lvl=logging.DEBUG)
+        discovery_host = "zrpn.pygame.org"
+        discovery_host = "localhost"
+        discovery = net.discovery.DiscoveryClient(discovery_host)
+        oid = discovery.get_servers()
+        discovery.update(500)
+        if discovery.response.oid == oid:
+            self.remove()
+            for name, host, port in discovery.response.value.itervalues():
+                logging.info("Found %s (%s:%s)", name, host, port)
+                # TODO: checking for connection timeout
+                client.Client(host, port).run()
+                break
+            else:
+                logging.info("No server found")
+
 
 def main():
     screen = display.Display()
@@ -68,12 +86,16 @@
 
     sgc.Font.set_fonts({"title": data_dir("pincoyablack.otf")}, size=60)
 
-    default_host = "zrpn.pygame.org"
     default_host = "localhost"
-    menu = MainMenu(menu=("m:Zombies Robots Pirates Ninjas", ("f:single", "Single Player"),
-                          ("w:input_box", "name=ip", "text={}".format(default_host),
-                           "label=Enter IP for multiplayer server(eg. localhost)"),
-                          ("f:multi", "Multiplayer"), ("f:exit", "Quit")))
+    menu = MainMenu(menu=(
+        "m:Zombies Robots Pirates Ninjas",
+        ("f:single", "Single Player"),
+        ("f:auto_m", "Auto Multiplayer"),
+        ("w:input_box", "name=ip", "text={}".format(default_host),
+            "label=Enter IP for multiplayer server(eg. localhost)"),
+        ("f:multi", "Multiplayer"),
+        ("f:exit", "Quit"),
+    ))
     menu.screen = screen
 
     clock = pygame.time.Clock()
--- a/zrpn/pygnetic/discovery.py	Thu Sep 06 21:35:41 2012 +0200
+++ b/zrpn/pygnetic/discovery.py	Fri Sep 07 02:22:10 2012 +0200
@@ -12,7 +12,7 @@
 message_factory = message.MessageFactory()
 
 register = message_factory.register('register', (
-    'oid', 'name', 'host', 'port'
+    'oid', 'name', 'port'
 ))
 get_servers = message_factory.register('get_servers', (
     'oid', 'current', 'part_size'
@@ -21,7 +21,7 @@
     'oid', 'sid'
 ))
 response = message_factory.register('response', (
-    'oid', 'value', 'error'
+    'oid', 'mid', 'value', 'error'
 ))
 
 
@@ -40,7 +40,8 @@
         name = message.__class__.__name__
         _logger.info('Received %s message from %s', name, self.client_address)
         ret_val, err = getattr(self, 'net_' + name)(message)
-        ack_data = message_factory.pack(response(message.oid, ret_val, err))
+        mid = message_factory.get_params(message.__class__)[0]
+        ack_data = message_factory.pack(response(message.oid, mid, ret_val, err))
         cnt = socket.sendto(ack_data, self.client_address)
         _logger.info('Sent response to %s', self.client_address)
         _logger.debug('Sent %d bytes: %r', cnt, ack_data)
@@ -52,7 +53,7 @@
 
     def net_register(self, message):
         sid = self.server.id_cnt = self.server.id_cnt + 1
-        self.server.servers[sid] = [message[1:], 0]
+        self.server.servers[sid] = [(message.name, self.client_address[0], message.port), 0]
         return sid, Errors.NO_ERROR
 
     def net_get_servers(self, message):
@@ -108,37 +109,51 @@
         self.address = socket.gethostbyname(host), port
         self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         self.response = None
-        self.callback = None
+        self.callbacks = {}
+        self.default_callback = lambda r: None
         self.oid = 0
 
-    def _send_msg(self, message, *args):
+    def _send_msg(self, message_cls, *args):
         oid = self.oid = self.oid + 1
-        data = message_factory.pack(message(oid, *args))
+        data = message_factory.pack(message_cls(oid, *args))
         cnt = self.socket.sendto(data, self.address)
         self.response = None
-        name = message.__class__.__name__
-        _logger.info('Sent %s message to %s', name, self.address)
+        _logger.info('Sent %s message to %s', message_cls.__name__, self.address)
         _logger.debug('Sent %d bytes: %r', cnt, data)
         return oid
 
     def update(self, timeout=0):
         r = select.select([self.socket], [], [], timeout / 1000.0)[0]
+        # sending to closed socket puts some data in receive buffer causing
+        # error while calling recvfrom
         if len(r) > 0:
-            data, address = self.socket.recvfrom(self.max_packet_size)
+            try:
+                data, address = self.socket.recvfrom(self.max_packet_size)
+            except Exception as e:
+                _logger.debug('Error: %s', e)
+                return
             if address == self.address:
                 _logger.debug('Received data: %r', data)
                 r = self.response = message_factory.unpack(data)
+                if r.__class__ != response:
+                    return
                 _logger.info('Received response from %s', address)
-                if callable(self.callback):
-                    self.callback(r)
+                self.callbacks.pop(r.oid, self.default_callback)(r)
             else:
                 _logger.info('Unexpected data from %s', address)
 
     def close(self):
         self.socket.close()
 
-    def register(self, name, host, port):
-        return self._send_msg(register, name, host, port)
+    def add_callback(self, oid, callback):
+        if callback is not None and callable(callback):
+            self.callbacks[oid] = callback
+        if len(self.callbacks) > 10:
+            keep = sorted(self.callbacks.iterkeys())[:-10]
+            self.callbacks = {k: v for k, v in self.callbacks.iter(keep)}
+
+    def register(self, name, port):
+        return self._send_msg(register, name, port)
 
     def ping(self, sid):
         return self._send_msg(ping, sid)
--- a/zrpn/pygnetic/message.py	Thu Sep 06 21:35:41 2012 +0200
+++ b/zrpn/pygnetic/message.py	Fri Sep 07 02:22:10 2012 +0200
@@ -115,25 +115,25 @@
         """Returns message class with given name.
 
         :param name: name of message
-        :return: message class (namedtuple)
+        :return: message class (namedtuple) or None if not found
         """
-        return self._message_names[name]
+        return self._message_names.get(name)
 
     def get_by_type(self, type_id):
         """Returns message class with given type_id.
 
         :param type_id: type identifier of message
-        :return: message class (namedtuple)
+        :return: message class (namedtuple) or None if not found
         """
-        return self._message_types[type_id]
+        return self._message_types.get(type_id)
 
     def get_params(self, message_cls):
         """Return tuple containing type_id, and sending keyword arguments
 
         :param message_cls: message class created by register
-        :return: int, dict
+        :return: int, dict or None if not found
         """
-        return self._message_params[message_cls]
+        return self._message_params.get(message_cls)
 
     def get_hash(self):
         """Calculate and return hash.
--- a/zrpn/server.py	Thu Sep 06 21:35:41 2012 +0200
+++ b/zrpn/server.py	Fri Sep 07 02:22:10 2012 +0200
@@ -1,5 +1,6 @@
 import time
 import logging
+import threading
 import pygnetic as net
 import board
 import messages
@@ -51,11 +52,35 @@
     def __init__(self, port):
         self.server = net.Server(port=port, conn_limit=10)
         self.server.handler = ClientReceiver
+        discovery_host = "zrpn.pygame.org"
+        discovery_host = "localhost"
+        logging.info('Registering server')
+        self.dicovery = net.discovery.DiscoveryClient(discovery_host)
+        oid = self.dicovery.register('no name', self.server.address[1])
+        self.dicovery.add_callback(oid, self.enable_discovery)
+        self.sid = None
+        self.p_thread = None
 
     def run(self):
         while True:
+            self.dicovery.update()
             self.server.update(1000)
 
+    def enable_discovery(self, response):
+        if response:
+            logging.info('Server registered')
+            self.dicovery.callback = None
+            self.sid = response.value
+            self.p_thread = threading.Thread(target=self.ping)
+            self.p_thread.daemon = True
+            self.p_thread.start()
+
+    def ping(self):
+        while True:
+            logging.debug('Sending discovery ping')
+            self.dicovery.ping(self.sid)
+            time.sleep(30)
+
 
 def main():
     net.init(logging_lvl=logging.DEBUG)