fcff7a221bcc7e6193e2c9c3e1fef71bdb1c991f
[nit.git] / lib / gamnit / network / server.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Server-side network services for games and such
16 #
17 # The following code creates a server that continuously listen for new clients,
18 # and exchange with them briefly before disconnecting.
19 #
20 # ~~~nitish
21 # redef fun handshake_app_name do return "nitwork_test"
22 # redef fun handshake_app_version do return "1.0"
23 #
24 # Open a server on port 4444
25 # var server = new Server(4444)
26 #
27 # loop
28 # # Accept new clients
29 # var new_clients = server.accept_clients
30 # for client in new_clients do
31 # # A client is connected, communicate!
32 # print ""
33 # print client.reader.deserialize.as(Object)
34 # client.writer.serialize "Goodbye client"
35 #
36 # # Done, close socket
37 # client.socket.close
38 # end
39 #
40 # # `accept_clients` in non-blocking,
41 # # sleep before tying again, or do something else.
42 # nanosleep(0, 50000000)
43 # printn "."
44 # end
45 # ~~~
46 module server
47
48 import common
49
50 # Game server controller
51 class Server
52
53 # Port for the `listening_socket`
54 var port: Int
55
56 # All connected `RemoteClient`
57 var clients = new Array[RemoteClient]
58
59 # Socket accepting new connections
60 var listening_socket: TCPServer is lazy do
61 print port
62 var socket = new TCPServer(port)
63 socket.listen 8
64 socket.blocking = false
65 return socket
66 end
67 init do listening_socket
68
69 # Accept currently waiting clients and return them as an array
70 fun accept_clients: Array[RemoteClient]
71 do
72 assert not listening_socket.closed
73
74 var new_clients = new Array[RemoteClient]
75 loop
76 var client_socket = listening_socket.accept
77 if client_socket == null then break
78
79 var rc = new RemoteClient(client_socket)
80
81 var handshake_success = rc.handshake
82 if handshake_success then
83 new_clients.add rc
84 print "Server: Client at {client_socket.address} passed the handshake"
85 else
86 print_error "Server Error: Client at {client_socket.address} failed the handshake"
87 client_socket.close
88 end
89 end
90 return new_clients
91 end
92
93 # Broadcast a `message` to all `clients`, then flush the connection
94 fun broadcast(message: Serializable)
95 do
96 for client in clients do
97 client.writer.serialize(message)
98 client.socket.flush
99 end
100 end
101 end
102
103 # Reference to a remote client connected to this server
104 class RemoteClient
105
106 # Communication socket with the client
107 var socket: TCPStream
108
109 # Is this client connected?
110 fun connected: Bool do return socket.connected
111
112 # `BinarySerializer` used to send data to this client through `socket`
113 var writer: BinarySerializer is noinit
114
115 # `BinaryDeserializer` used to receive data from this client through `socket`
116 var reader: BinaryDeserializer is noinit
117
118 init
119 do
120 # Setup serialization
121 writer = new BinarySerializer(socket)
122 writer.cache = new AsyncCache(true)
123 reader = new BinaryDeserializer(socket)
124 writer.link reader
125 end
126
127 # Check for compatibility with the client
128 fun handshake: Bool
129 do
130 print "Server: Handshake requested by {socket.address}"
131
132 # Make sure it is the same app
133 var server_app = sys.handshake_app_name
134 var client_app = socket.read_string
135 if server_app != client_app then
136 print_error "Server Error: Client app name is '{client_app}'"
137
138 # Send an empty string so the client read it and give up
139 socket.write_string ""
140 socket.close
141 return false
142 end
143
144 socket.write_string server_app
145
146 # App version
147 var app_version = sys.handshake_app_version
148 var client_version = socket.read_string
149 if client_version != app_version then
150 print_error "Handshake Error: client version is different '{client_version}'"
151 socket.write_string ""
152 socket.close
153 return false
154 end
155
156 socket.write_string app_version
157
158 return true
159 end
160 end