#
# # `accept_clients` in non-blocking,
# # sleep before tying again, or do something else.
-# nanosleep(0, 50000000)
+# 0.5.sleep
# printn "."
# end
# ~~~
module server
-import common
+intrude import common
# Game server controller
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]
+ #
+ # If `add_to_clients`, the default, the new clients are added to `clients`.
+ # Otherwise, the return value of `accept_clients` may be added to `clients`
+ # explicitly by the caller after an extra verification or sorting.
+ fun accept_clients(add_to_clients: nullable Bool): Array[RemoteClient]
do
+ add_to_clients = add_to_clients or else true
assert not listening_socket.closed
var new_clients = new Array[RemoteClient]
client_socket.close
end
end
+
+ if add_to_clients then clients.add_all new_clients
+
return new_clients
end
# Broadcast a `message` to all `clients`, then flush the connection
- fun broadcast(message: Serializable)
+ #
+ # The client `except` is skipped and will not receive the `message`.
+ fun broadcast(message: Serializable, except: nullable RemoteClient)
do
- for client in clients do
+ for client in clients do if client != except then
client.writer.serialize(message)
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
# Is this client connected?
fun connected: Bool do return socket.connected
- # `BinarySerializer` used to send data to this client through `socket`
- var writer: BinarySerializer is noinit
+ # `MsgPackSerializer` used to send data to this client through `socket`
+ var writer: MsgPackSerializer is noinit
- # `BinaryDeserializer` used to receive data from this client through `socket`
- var reader: BinaryDeserializer is noinit
+ # `MsgPackDeserializer` used to receive data from this client through `socket`
+ var reader: MsgPackDeserializer is noinit
init
do
# Setup serialization
- writer = new BinarySerializer(socket)
+ writer = new MsgPackSerializer(socket)
writer.cache = new AsyncCache(true)
- reader = new BinaryDeserializer(socket)
+ reader = new MsgPackDeserializer(socket)
writer.link reader
end
# Check for compatibility with the client
fun handshake: Bool
do
- print "Server: Handshake requested by {socket.address}"
+ print "Server: Handshake initiated by {socket.address}"
# Make sure it is the same app
var server_app = sys.handshake_app_name
- var client_app = socket.read_string
+ var client_app = socket.deserialize_msgpack
if server_app != client_app then
- print_error "Server Error: Client app name is '{client_app}'"
+ print_error "Server Error: Client app name is '{client_app or else "<invalid>"}'"
# Send an empty string so the client read it and give up
- socket.write_string ""
+ socket.serialize_msgpack ""
socket.close
return false
end
- socket.write_string server_app
+ socket.serialize_msgpack server_app
# App version
var app_version = sys.handshake_app_version
- var client_version = socket.read_string
+ var client_version = socket.deserialize_msgpack
if client_version != app_version then
- print_error "Handshake Error: client version is different '{client_version}'"
- socket.write_string ""
+ print_error "Handshake Error: client version is different '{client_version or else "<invalid>"}'"
+ socket.serialize_msgpack ""
socket.close
return false
end
- socket.write_string app_version
+ socket.serialize_msgpack app_version
return true
end