lib: intro gamnit::network
authorAlexis Laferrière <alexis.laf@xymus.net>
Thu, 2 Jul 2015 11:50:58 +0000 (07:50 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Sun, 2 Aug 2015 16:25:23 +0000 (12:25 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/gamnit/gamnit.nit [new file with mode: 0644]
lib/gamnit/network/client.nit [new file with mode: 0644]
lib/gamnit/network/common.nit [new file with mode: 0644]
lib/gamnit/network/network.nit [new file with mode: 0644]
lib/gamnit/network/server.nit [new file with mode: 0644]

diff --git a/lib/gamnit/gamnit.nit b/lib/gamnit/gamnit.nit
new file mode 100644 (file)
index 0000000..2fa5b8e
--- /dev/null
@@ -0,0 +1,16 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Game and multimedia framework for Nit
+module gamnit
diff --git a/lib/gamnit/network/client.nit b/lib/gamnit/network/client.nit
new file mode 100644 (file)
index 0000000..d08b8b4
--- /dev/null
@@ -0,0 +1,127 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Client-side network services for games and such
+#
+# The following code implements a client to connect to a local server and
+# briefly exchange with it.
+#
+# ~~~
+# redef fun handshake_app_name do return "nitwork_test"
+# redef fun handshake_app_version do return "1.0"
+#
+# # Prepare connection with remote server
+# var config = new RemoteServerConfig("localhost", 4444)
+# var server = new RemoteServer(config)
+#
+# # Try to connect
+# if not server.connect then return
+#
+# # Make sure the server is compatible
+# if not server.handshake then return
+#
+# # Connection up! communicate
+# server.writer.serialize "hello server"
+# print server.reader.deserialize.as(Object)
+#
+# # Done, close socket
+# server.socket.close
+# ~~~
+module client
+
+import common
+
+# Information of the remove server
+class RemoteServerConfig
+
+       # Address of the remote server, either a domain name or an Internet address
+       var address: Text
+
+       # Listening port of the server
+       var port: Int
+end
+
+# Connection to a remote server
+class RemoteServer
+
+       # `RemoteServerConfig` used to initiate connection to the server
+       var config: RemoteServerConfig
+
+       # Communication socket with the server
+       var socket: nullable TCPStream = null
+
+       # Is this connection connected?
+       fun connected: Bool do return socket != null and socket.connected == true
+
+       # `BinarySerializer` used to send data to this client through `socket`
+       var writer: BinarySerializer is noinit
+
+       # `BinaryDeserializer` used to receive data from this client through `socket`
+       var reader: BinaryDeserializer is noinit
+
+       # Attempt connection with the remote server
+       fun connect: Bool
+       do
+               print "Connecting to {config.address}:{config.port}..."
+               var socket = new TCPStream.connect(config.address.to_s, config.port)
+               self.socket = socket
+
+               if not socket.connected then
+                       print "Connection failed: {socket.last_error or else "Internal error"}"
+                       return false
+               end
+
+               # Setup serialization
+               writer = new BinarySerializer(socket)
+               reader = new BinaryDeserializer(socket)
+               writer.link reader
+
+               return true
+       end
+
+       # Attempt handshake with server
+       #
+       # Validates compatibility between `handshake_app_name` and `handshake_app_version`.
+       #
+       # On error, close `socket`.
+       fun handshake: Bool
+       do
+               # The client goes first so that the server doesn't show its hand
+               var socket = socket
+               assert socket != null
+
+               # App name
+               var app_name = sys.handshake_app_name
+               socket.write_string app_name
+
+               var server_app = socket.read_string
+               if server_app != app_name then
+                       print_error "Handshake Error: server app name is '{server_app}'"
+                       socket.close
+                       return false
+               end
+
+               # App version
+               socket.write_string sys.handshake_app_version
+
+               var server_version = socket.read_string
+               if server_version != sys.handshake_app_version then
+                       print_error "Handshake Error: server version is different '{server_version}'"
+                       socket.close
+                       return false
+               end
+
+               return true
+       end
+end
diff --git a/lib/gamnit/network/common.nit b/lib/gamnit/network/common.nit
new file mode 100644 (file)
index 0000000..54e764a
--- /dev/null
@@ -0,0 +1,35 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Services common to the `client` and `server` modules
+module common
+
+import socket
+import binary::serialization
+
+# Unique name of the application to use in the handshake
+#
+# This name must be the same between client/server and
+# it should not be used by other programs that may interfere.
+#
+# Both client and server refuse connections with a different name.
+fun handshake_app_name: String do return program_name
+
+# Version of the communication protocol to use in the handshake
+#
+# Its value should change with the communication protocol in such a way
+# that different versions indicates incompatible protocols.
+#
+# Both client and server refuse connections with a different version.
+fun handshake_app_version: String do return "0.0"
diff --git a/lib/gamnit/network/network.nit b/lib/gamnit/network/network.nit
new file mode 100644 (file)
index 0000000..83fd35d
--- /dev/null
@@ -0,0 +1,19 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Easy client/server logic for games and simple distributed applications
+module network
+
+import server
+import client
diff --git a/lib/gamnit/network/server.nit b/lib/gamnit/network/server.nit
new file mode 100644 (file)
index 0000000..e4fde2e
--- /dev/null
@@ -0,0 +1,159 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Server-side network services for games and such
+#
+# The following code creates a server that continuously listen for new clients,
+# and exchange with them briefly before disconnecting.
+#
+# ~~~nitish
+# redef fun handshake_app_name do return "nitwork_test"
+# redef fun handshake_app_version do return "1.0"
+#
+# Open a server on port 4444
+# var server = new Server(4444)
+#
+# loop
+#     # Accept new clients
+#     var new_clients = server.accept_clients
+#     for client in new_clients do
+#         # A client is connected, communicate!
+#         print ""
+#         print client.reader.deserialize.as(Object)
+#         client.writer.serialize "Goodbye client"
+#
+#         # Done, close socket
+#         client.socket.close
+#     end
+#
+#     # `accept_clients` in non-blocking,
+#     # sleep before tying again, or do something else.
+#     nanosleep(0, 50000000)
+#     printn "."
+# end
+# ~~~
+module server
+
+import common
+
+# Game server controller
+class Server
+
+       # Port for the `listening_socket`
+       var port: Int
+
+       # All connected `RemoteClient`
+       var clients = new Array[RemoteClient]
+
+       # Socket accepting new connections
+       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]
+       do
+               assert not listening_socket.closed
+
+               var new_clients = new Array[RemoteClient]
+               loop
+                       var client_socket = listening_socket.accept
+                       if client_socket == null then break
+
+                       var rc = new RemoteClient(client_socket)
+
+                       var handshake_success = rc.handshake
+                       if handshake_success then
+                               new_clients.add rc
+                               print "Server: Client at {client_socket.address} passed the handshake"
+                       else
+                               print_error "Server Error: Client at {client_socket.address} failed the handshake"
+                               client_socket.close
+                       end
+               end
+               return new_clients
+       end
+
+       # Broadcast a `message` to all `clients`, then flush the connection
+       fun broadcast(message: Serializable)
+       do
+               for client in clients do
+                       client.writer.serialize(message)
+                       client.socket.flush
+               end
+       end
+end
+
+# Reference to a remote client connected to this server
+class RemoteClient
+
+       # Communication socket with the client
+       var socket: TCPStream
+
+       # 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
+
+       # `BinaryDeserializer` used to receive data from this client through `socket`
+       var reader: BinaryDeserializer is noinit
+
+       init
+       do
+               # Setup serialization
+               writer = new BinarySerializer(socket)
+               reader = new BinaryDeserializer(socket)
+               writer.link reader
+       end
+
+       # Check for compatibility with the client
+       fun handshake: Bool
+       do
+               print "Server: Handshake requested by {socket.address}"
+
+               # Make sure it is the same app
+               var server_app = sys.handshake_app_name
+               var client_app = socket.read_string
+               if server_app != client_app then
+                       print_error "Server Error: Client app name is '{client_app}'"
+
+                       # Send an empty string so the client read it and give up
+                       socket.write_string ""
+                       socket.close
+                       return false
+               end
+
+               socket.write_string server_app
+
+               # App version
+               var app_version = sys.handshake_app_version
+               var client_version = socket.read_string
+               if client_version != app_version then
+                       print_error "Handshake Error: client version is different '{client_version}'"
+                       socket.write_string ""
+                       socket.close
+                       return false
+               end
+
+               socket.write_string app_version
+
+               return true
+       end
+end