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.
42 # nanosleep(0, 50000000)
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
70 fun accept_clients
: Array[RemoteClient]
72 assert not listening_socket
.closed
74 var new_clients
= new Array[RemoteClient]
76 var client_socket
= listening_socket
.accept
77 if client_socket
== null then break
79 var rc
= new RemoteClient(client_socket
)
81 var handshake_success
= rc
.handshake
82 if handshake_success
then
84 print
"Server: Client at {client_socket.address} passed the handshake"
86 print_error
"Server Error: Client at {client_socket.address} failed the handshake"
93 # Broadcast a `message` to all `clients`, then flush the connection
94 fun broadcast
(message
: Serializable)
96 for client
in clients
do
97 client
.writer
.serialize
(message
)
102 # Respond to pending discovery requests by sending the TCP listening address and port
104 # Returns the number of valid requests received.
106 # The response messages includes the TCP listening address and port
107 # for remote clients to connect with TCP using `connect`.
108 # These connections are accepted by the server with `accept_clients`.
109 fun answer_discovery_requests
: Int
113 var ptr
= new Ref[nullable SocketAddress](null)
114 var read
= discovery_socket
.recv_from
(1024, ptr
)
116 # No sender means there is no discovery request
117 var sender
= ptr
.item
118 if sender
== null then break
120 var words
= read
.split
(" ")
121 if words
.length
!= 2 or words
[0] != discovery_request_message
or words
[1] != handshake_app_name
then
122 print
"Server Warning: Rejected discovery request '{read}' from {sender.address}:{sender.port}"
126 var msg
= "{discovery_response_message} {handshake_app_name} {self.port}"
127 discovery_socket
.send_to
(sender
.address
, sender
.port
, msg
)
133 # UDP socket responding to discovery requests
135 # Usually opened on the first call to `answer_discovery_request`.
136 var discovery_socket
: UDPSocket is lazy
do
137 var s
= new UDPSocket
139 s
.bind
(null, discovery_port
)
144 # Reference to a remote client connected to this server
147 # Communication socket with the client
148 var socket
: TCPStream
150 # Is this client connected?
151 fun connected
: Bool do return socket
.connected
153 # `BinarySerializer` used to send data to this client through `socket`
154 var writer
: BinarySerializer is noinit
156 # `BinaryDeserializer` used to receive data from this client through `socket`
157 var reader
: BinaryDeserializer is noinit
161 # Setup serialization
162 writer
= new BinarySerializer(socket
)
163 writer
.cache
= new AsyncCache(true)
164 reader
= new BinaryDeserializer(socket
)
168 # Check for compatibility with the client
171 print
"Server: Handshake requested by {socket.address}"
173 # Make sure it is the same app
174 var server_app
= sys
.handshake_app_name
175 var client_app
= socket
.read_string
176 if server_app
!= client_app
then
177 print_error
"Server Error: Client app name is '{client_app}'"
179 # Send an empty string so the client read it and give up
180 socket
.write_string
""
185 socket
.write_string server_app
188 var app_version
= sys
.handshake_app_version
189 var client_version
= socket
.read_string
190 if client_version
!= app_version
then
191 print_error
"Handshake Error: client version is different '{client_version}'"
192 socket
.write_string
""
197 socket
.write_string app_version