1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Client-side network services for games and such
17 # The following code implements a client to connect to a local server and
18 # briefly exchange with it.
21 # redef fun handshake_app_name do return "nitwork_test"
22 # redef fun handshake_app_version do return "1.0"
24 # # Prepare connection with remote server
25 # var config = new RemoteServerConfig("localhost", 4444)
26 # var server = new RemoteServer(config)
29 # if not server.connect then return
31 # # Make sure the server is compatible
32 # if not server.handshake then return
34 # # Connection up! communicate
35 # server.writer.serialize "hello server"
36 # print server.reader.deserialize.as(Object)
38 # # Done, close socket
45 # Information of the remove server
46 class RemoteServerConfig
48 # Address of the remote server, either a domain name or an Internet address
51 # Listening port of the server
55 # Connection to a remote server
58 # `RemoteServerConfig` used to initiate connection to the server
59 var config
: RemoteServerConfig
61 # Communication socket with the server
62 var socket
: nullable TCPStream = null
64 # Is this connection connected?
68 return socket
!= null and socket
.connected
71 # `MsgPackSerializer` used to send data to this client through `socket`
72 var writer
: MsgPackSerializer is noinit
74 # `MsgPackDeserializer` used to receive data from this client through `socket`
75 var reader
: MsgPackDeserializer is noinit
77 # Attempt connection with the remote server
80 print
"Connecting to {config.address}:{config.port}..."
81 var socket
= new TCPStream.connect
(config
.address
.to_s
, config
.port
)
84 if not socket
.connected
then
85 print
"Connection failed: {socket.last_error or else "Internal error"}"
90 writer
= new MsgPackSerializer(socket
)
91 writer
.cache
= new AsyncCache(false)
92 reader
= new MsgPackDeserializer(socket
)
98 # Attempt handshake with server
100 # Validates compatibility between `handshake_app_name` and `handshake_app_version`.
102 # On error, close `socket`.
105 # The client goes first so that the server doesn't show its hand
107 assert socket
!= null
110 var app_name
= sys
.handshake_app_name
111 socket
.serialize_msgpack app_name
113 var server_app
= socket
.deserialize_msgpack
("String")
114 if server_app
!= app_name
then
115 print_error
"Handshake Error: server app name is '{server_app or else "<invalid>"}'"
121 socket
.serialize_msgpack sys
.handshake_app_version
123 var server_version
= socket
.deserialize_msgpack
("String")
124 if server_version
!= sys
.handshake_app_version
then
125 print_error
"Handshake Error: server version is different '{server_version or else "<invalid>"}'"
134 # Discover local servers responding on UDP `discovery_port`
136 # Sends a message in the format `gamnit::network? handshake_app_name` and
137 # looks for the response `gamnit::network! handshake_app_name port_number`.
138 # Waits for `timeout`, or the default 0.1 seconds, after sending the message.
140 # The server usually responds using the method `answer_discovery_requests`.
141 # When receiving responses, the client may then choose a server and
142 # connect via `new RemoteServer`.
145 # var servers = discover_local_servers
146 # if servers.not_empty then
147 # var server = new RemoteServer(servers.first)
149 # server.writer.serialize "hello server"
150 # server.socket.close
153 fun discover_local_servers
(timeout
: nullable Float): Array[RemoteServerConfig]
155 timeout
= timeout
or else 0.1
157 var s
= new UDPSocket
158 s
.enable_broadcast
= true
160 s
.broadcast
(discovery_port
, "{discovery_request_message} {handshake_app_name}")
163 var r
= new Array[RemoteServerConfig]
165 var ptr
= new Ref[nullable SocketAddress](null)
166 var resp
= s
.recv_from
(1024, ptr
)
169 if resp
.is_empty
then
174 var words
= resp
.split
(" ")
175 if words
.length
== 3 and words
[0] == discovery_response_message
and
176 words
[1] == handshake_app_name
and words
[2].is_int
then
177 var address
= src
.address
178 var port
= words
[2].to_i
179 r
.add
new RemoteServerConfig(address
, port
)