gamnit: move up UDP discovery logic from Tinks! to the lib
authorAlexis Laferrière <alexis.laf@xymus.net>
Sun, 28 May 2017 02:03:21 +0000 (22:03 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Tue, 30 May 2017 14:51:23 +0000 (10:51 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

contrib/tinks/src/client/base.nit
contrib/tinks/src/common.nit
contrib/tinks/src/server/server.nit
lib/gamnit/network/client.nit
lib/gamnit/network/common.nit
lib/gamnit/network/server.nit

index bca5ae3..b3ebd09 100644 (file)
@@ -35,22 +35,10 @@ redef class App
                else
                        print "Looking for a server..."
 
-                       var s = new UDPSocket
-                       s.enable_broadcast = true
-                       s.blocking = false
-                       s.broadcast(discovery_port, "Server? {handshake_app_name}")
-                       nanosleep(0, 100_000_000)
-
-                       var ptr = new Ref[nullable SocketAddress](null)
-                       var resp = s.recv_from(1024, ptr)
-                       var src = ptr.item
-
-                       if not resp.is_empty then
-                               var words = resp.split(" ")
-                               if words.length == 3 and words[0] == "Server!" and words[1] == handshake_app_name and words[2].is_numeric then
-                                       address = src.address
-                                       port = words[2].to_i
-                               end
+                       var servers = discover_local_servers
+                       if servers.not_empty then
+                               address = servers.first.address
+                               port = servers.first.port
                        end
                end
 
index b215d09..e7a82c9 100644 (file)
@@ -26,6 +26,5 @@ redef class Sys
        # Default listening port of the server
        fun default_listening_port: Int do return 18721
 
-       # Port to which clients send discovery requests
-       fun discovery_port: Int do return 18722
+       redef fun discovery_port do return 18722
 end
index 58555ae..6c63670 100644 (file)
@@ -26,13 +26,6 @@ redef class RemoteClient
 end
 
 redef class Server
-       # `UDPSocket` to which clients send discovery requests
-       var discovery_socket: UDPSocket do
-               var s = new UDPSocket
-               s.blocking = false
-               s.bind(null, discovery_port)
-               return s
-       end
 
        # The current game
        var game = new TGame is lazy, writable
@@ -62,24 +55,8 @@ redef class Server
                # Do game logic
                var turn = game.do_turn
 
-               # Respond to discovery requests
-               loop
-                       var ptr = new Ref[nullable SocketAddress](null)
-                       var read = discovery_socket.recv_from(1024, ptr)
-
-                       # No sender means there is no request (an error would also do it)
-                       var sender = ptr.item
-                       if sender == null then break
-
-                       var words = read.split(" ")
-                       if words.length != 2 or words[0] != "Server?" or words[1] != handshake_app_name then
-                               print "Server Warning: Rejected discovery request '{read}'"
-                               continue
-                       end
-
-                       discovery_socket.send_to(sender.address, sender.port,
-                               "Server! {handshake_app_name} {self.port}")
-               end
+               # Respond to discovery requests sent over UDP
+               answer_discovery_requests
 
                # Setup clients
                var new_clients = accept_clients
index 38db2db..8aad96f 100644 (file)
@@ -40,7 +40,7 @@
 # ~~~
 module client
 
-import common
+intrude import common
 
 # Information of the remove server
 class RemoteServerConfig
@@ -126,3 +126,55 @@ class RemoteServer
                return true
        end
 end
+
+# Discover local servers responding on UDP `discovery_port`
+#
+# Sends a message in the format `gamnit::network? handshake_app_name` and
+# looks for the response `gamnit::network! handshake_app_name port_number`.
+# Waits for `timeout`, or the default 0.1 seconds, after sending the message.
+#
+# The server usually responds using the method `answer_discovery_requests`.
+# When receiving responses, the client may then choose a server and
+# connect via `new RemoteServer`.
+#
+# ~~~
+# var servers = discover_local_servers
+# if servers.not_empty then
+#     var server = new RemoteServer(servers.first)
+#     server.connect
+#     server.writer.serialize "hello server"
+#     server.socket.close
+# end
+# ~~~
+fun discover_local_servers(timeout: nullable Float): Array[RemoteServerConfig]
+do
+       timeout = timeout or else 0.1
+
+       var s = new UDPSocket
+       s.enable_broadcast = true
+       s.blocking = false
+       s.broadcast(discovery_port, "{discovery_request_message} {handshake_app_name}")
+       timeout.sleep
+
+       var r = new Array[RemoteServerConfig]
+       loop
+               var ptr = new Ref[nullable SocketAddress](null)
+               var resp = s.recv_from(1024, ptr)
+               var src = ptr.item
+
+               if resp.is_empty then
+                       # No response
+                       break
+               else
+                       assert src != null
+                       var words = resp.split(" ")
+                       if words.length == 3 and words[0] == discovery_response_message and
+                          words[1] == handshake_app_name and words[2].is_int then
+                               var address = src.address
+                               var port = words[2].to_i
+                               r.add new RemoteServerConfig(address, port)
+                       end
+               end
+       end
+       return r
+end
index 54e764a..f534f1f 100644 (file)
@@ -33,3 +33,14 @@ fun handshake_app_name: String do return program_name
 #
 # Both client and server refuse connections with a different version.
 fun handshake_app_version: String do return "0.0"
+
+# Server port listening for discovery requests
+#
+# This name must be the same between client/server.
+fun discovery_port: Int do return 18722
+
+# First word in discovery requests
+private fun discovery_request_message: String do return "gamnit::network?"
+
+# First word in discovery responses
+private fun discovery_response_message: String do return "gamnit::network!"
index fcff7a2..926e620 100644 (file)
@@ -45,7 +45,7 @@
 # ~~~
 module server
 
-import common
+intrude import common
 
 # Game server controller
 class Server
@@ -56,15 +56,15 @@ class Server
        # All connected `RemoteClient`
        var clients = new Array[RemoteClient]
 
-       # Socket accepting new connections
+       # TCP socket accepting new connections
+       #
+       # Opened on the first call to `accept_clients`.
        var listening_socket: TCPServer is lazy do
-               print port
                var socket = new TCPServer(port)
                socket.listen 8
                socket.blocking = false
                return socket
        end
-       init do listening_socket
 
        # Accept currently waiting clients and return them as an array
        fun accept_clients: Array[RemoteClient]
@@ -98,6 +98,47 @@ class Server
                        client.socket.flush
                end
        end
+
+       # Respond to pending discovery requests by sending the TCP listening address and port
+       #
+       # Returns the number of valid requests received.
+       #
+       # The response messages includes the TCP listening address and port
+       # for remote clients to connect with TCP using `connect`.
+       # These connections are accepted by the server with `accept_clients`.
+       fun answer_discovery_requests: Int
+       do
+               var count = 0
+               loop
+                       var ptr = new Ref[nullable SocketAddress](null)
+                       var read = discovery_socket.recv_from(1024, ptr)
+
+                       # No sender means there is no discovery request
+                       var sender = ptr.item
+                       if sender == null then break
+
+                       var words = read.split(" ")
+                       if words.length != 2 or words[0] != discovery_request_message or words[1] != handshake_app_name then
+                               print "Server Warning: Rejected discovery request '{read}' from {sender.address}:{sender.port}"
+                               continue
+                       end
+
+                       var msg = "{discovery_response_message} {handshake_app_name} {self.port}"
+                       discovery_socket.send_to(sender.address, sender.port, msg)
+                       count += 1
+               end
+               return count
+       end
+
+       # UDP socket responding to discovery requests
+       #
+       # Usually opened on the first call to `answer_discovery_request`.
+       var discovery_socket: UDPSocket is lazy do
+               var s = new UDPSocket
+               s.blocking = false
+               s.bind(null, discovery_port)
+               return s
+       end
 end
 
 # Reference to a remote client connected to this server