--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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"
--- /dev/null
+# 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
--- /dev/null
+# 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