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 # Server-side network services for games and such
17 # The following code creates a server that continuously listen for new clients,
18 # and exchange with them briefly before disconnecting.
21 # redef fun handshake_app_name do return "nitwork_test"
22 # redef fun handshake_app_version do return "1.0"
24 # Open a server on port 4444
25 # var server = new Server(4444)
28 # # Accept new clients
29 # var new_clients = server.accept_clients
30 # for client in new_clients do
31 # # A client is connected, communicate!
33 # print client.reader.deserialize.as(Object)
34 # client.writer.serialize "Goodbye client"
36 # # Done, close socket
40 # # `accept_clients` in non-blocking,
41 # # sleep before tying again, or do something else.
50 # Game server controller
53 # Port for the `listening_socket`
56 # All connected `RemoteClient`
57 var clients
= new Array[RemoteClient]
59 # TCP socket accepting new connections
61 # Opened on the first call to `accept_clients`.
62 var listening_socket
: TCPServer is lazy
do
63 var socket
= new TCPServer(port
)
65 socket
.blocking
= false
69 # Accept currently waiting clients and return them as an array
71 # If `add_to_clients`, the default, the new clients are added to `clients`.
72 # Otherwise, the return value of `accept_clients` may be added to `clients`
73 # explicitly by the caller after an extra verification or sorting.
74 fun accept_clients
(add_to_clients
: nullable Bool): Array[RemoteClient]
76 add_to_clients
= add_to_clients
or else true
77 assert not listening_socket
.closed
79 var new_clients
= new Array[RemoteClient]
81 var client_socket
= listening_socket
.accept
82 if client_socket
== null then break
84 var rc
= new RemoteClient(client_socket
)
86 var handshake_success
= rc
.handshake
87 if handshake_success
then
89 print
"Server: Client at {client_socket.address} passed the handshake"
91 print_error
"Server Error: Client at {client_socket.address} failed the handshake"
96 if add_to_clients
then clients
.add_all new_clients
101 # Broadcast a `message` to all `clients`, then flush the connection
103 # The client `except` is skipped and will not receive the `message`.
104 fun broadcast
(message
: Serializable, except
: nullable RemoteClient)
106 for client
in clients
do if client
!= except
then
107 client
.writer
.serialize
(message
)
112 # Respond to pending discovery requests by sending the TCP listening address and port
114 # Returns the number of valid requests received.
116 # The response messages includes the TCP listening address and port
117 # for remote clients to connect with TCP using `connect`.
118 # These connections are accepted by the server with `accept_clients`.
119 fun answer_discovery_requests
: Int
123 var ptr
= new Ref[nullable SocketAddress](null)
124 var read
= discovery_socket
.recv_from
(1024, ptr
)
126 # No sender means there is no discovery request
127 var sender
= ptr
.item
128 if sender
== null then break
130 var words
= read
.split
(" ")
131 if words
.length
!= 2 or words
[0] != discovery_request_message
or words
[1] != handshake_app_name
then
132 print
"Server Warning: Rejected discovery request '{read}' from {sender.address}:{sender.port}"
136 var msg
= "{discovery_response_message} {handshake_app_name} {self.port}"
137 discovery_socket
.send_to
(sender
.address
, sender
.port
, msg
)
143 # UDP socket responding to discovery requests
145 # Usually opened on the first call to `answer_discovery_request`.
146 var discovery_socket
: UDPSocket is lazy
do
147 var s
= new UDPSocket
149 s
.bind
(null, discovery_port
)
154 # Reference to a remote client connected to this server
157 # Communication socket with the client
158 var socket
: TCPStream
160 # Is this client connected?
161 fun connected
: Bool do return socket
.connected
163 # `BinarySerializer` used to send data to this client through `socket`
164 var writer
: BinarySerializer is noinit
166 # `BinaryDeserializer` used to receive data from this client through `socket`
167 var reader
: BinaryDeserializer is noinit
171 # Setup serialization
172 writer
= new BinarySerializer(socket
)
173 writer
.cache
= new AsyncCache(true)
174 reader
= new BinaryDeserializer(socket
)
178 # Check for compatibility with the client
181 print
"Server: Handshake initiated by {socket.address}"
183 # Make sure it is the same app
184 var server_app
= sys
.handshake_app_name
185 var client_app
= socket
.read_string
186 if server_app
!= client_app
then
187 print_error
"Server Error: Client app name is '{client_app}'"
189 # Send an empty string so the client read it and give up
190 socket
.write_string
""
195 socket
.write_string server_app
198 var app_version
= sys
.handshake_app_version
199 var client_version
= socket
.read_string
200 if client_version
!= app_version
then
201 print_error
"Handshake Error: client version is different '{client_version}'"
202 socket
.write_string
""
207 socket
.write_string app_version