lib/socket: intro `UDPSocket` and its services
[nit.git] / lib / socket / socket.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Matthieu Lucas <lucasmatthieu@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Socket services
18 module socket
19
20 private import socket_c
21 intrude import standard::stream
22
23 # A general Socket, either TCP or UDP
24 abstract class Socket
25
26 # Underlying C socket
27 private var native: NativeSocket is noinit
28
29 # Is this socket closed?
30 var closed = false
31
32 # Set whether calls to `accept` are blocking
33 fun blocking=(value: Bool)
34 do
35 # We use the opposite from the native version as the native API
36 # is closer to the C API. In the Nity API, we use a positive version
37 # of the name.
38 native.non_blocking = not value
39 end
40 end
41
42 # A general TCP socket, either a `TCPStream` or a `TCPServer`
43 abstract class TCPSocket
44 super Socket
45
46 # Port used by the socket
47 var port: Int
48
49 # IPv4 address to which `self` is connected
50 #
51 # Formatted as xxx.xxx.xxx.xxx.
52 var address: String is noinit
53 end
54
55 # Simple communication stream with a remote socket
56 class TCPStream
57 super TCPSocket
58 super BufferedReader
59 super Writer
60 super PollableReader
61
62 # Real canonical name of the host to which `self` is connected
63 var host: String
64
65 private var addrin: NativeSocketAddrIn is noinit
66
67 redef var end_reached = false
68
69 # TODO make init private
70
71 # Creates a socket connection to host `host` on port `port`
72 init connect(host: String, port: Int)
73 do
74 _buffer = new NativeString(1024)
75 _buffer_pos = 0
76 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
77 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
78 if native.address_is_null then
79 end_reached = true
80 closed = true
81 return
82 end
83 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
84 end_reached = true
85 closed = true
86 return
87 end
88
89 var hostent = sys.gethostbyname(host.to_cstring)
90 if hostent.address_is_null then
91 # Error in name lookup
92 last_error = new IOError.from_h_errno
93
94 closed = true
95 end_reached = true
96
97 return
98 end
99
100 addrin = new NativeSocketAddrIn
101 addrin.fill_from_hostent hostent
102 addrin.port = port
103
104 address = addrin.address.to_s
105 init(addrin.port, hostent.h_name.to_s)
106
107 closed = not internal_connect
108 end_reached = closed
109 if closed then
110 # Connection failed
111 last_error = new IOError.from_errno
112 end
113
114 prepare_buffer(1024)
115 end
116
117 # Creates a client socket, this is meant to be used by accept only
118 private init server_side(h: SocketAcceptResult)
119 do
120 native = h.socket
121 addrin = h.addr_in
122 address = addrin.address.to_s
123
124 init(addrin.port, address)
125
126 prepare_buffer(1024)
127 end
128
129 redef fun poll_in do return ready_to_read(0)
130
131 # Returns an array containing an enum of the events ready to be read
132 #
133 # event_types : Combination of several event types to watch
134 #
135 # timeout : Time in milliseconds before stopping listening for events on this socket
136 private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
137 if end_reached then return new Array[NativeSocketPollValues]
138 return native.socket_poll(new PollFD(native.descriptor, event_types), timeout)
139 end
140
141 # Easier use of pollin to check for something to read on all channels of any priority
142 #
143 # timeout : Time in milliseconds before stopping to wait for events
144 fun ready_to_read(timeout: Int): Bool
145 do
146 if _buffer_pos < _buffer_length then return true
147 if end_reached then return false
148 var events = [new NativeSocketPollValues.pollin]
149 return pollin(events, timeout).length != 0
150 end
151
152 # Checks if the socket still is connected
153 fun connected: Bool
154 do
155 if closed then return false
156 var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr]
157 if pollin(events, 0).length == 0 then
158 return true
159 else
160 closed = true
161 return false
162 end
163 end
164
165 redef fun is_writable do return not end_reached
166
167 # Establishes a connection to socket addrin
168 #
169 # REQUIRES : not self.end_reached
170 private fun internal_connect: Bool
171 do
172 assert not closed
173 return native.connect(addrin) >= 0
174 end
175
176 # If socket.end_reached, nothing will happen
177 redef fun write(msg)
178 do
179 if closed then return
180 native.write(msg.to_cstring, msg.length)
181 end
182
183 redef fun write_byte(value)
184 do
185 if closed then return
186 native.write_byte value
187 end
188
189 redef fun write_bytes(bytes) do
190 if closed then return
191 var s = bytes.to_s
192 native.write(s.to_cstring, s.length)
193 end
194
195 fun write_ln(msg: Text)
196 do
197 if closed then return
198 write msg.to_s
199 write "\n"
200 end
201
202 redef fun fill_buffer
203 do
204 if not connected then return
205
206 var read = native.read(_buffer, _buffer_capacity)
207 if read == -1 then
208 close
209 end_reached = true
210 end
211
212 _buffer_length = read
213 _buffer_pos = 0
214 end
215
216 fun enlarge(len: Int) do
217 if _buffer_capacity >= len then return
218 _buffer_capacity = len
219
220 var ns = new NativeString(_buffer_capacity)
221 _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
222 _buffer = ns
223 end
224
225 redef fun close
226 do
227 if closed then return
228 if native.close >= 0 then
229 closed = true
230 end_reached = true
231 end
232 end
233
234 # Send the data present in the socket buffer
235 fun flush
236 do
237 if not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
238 not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
239 closed = true
240 end
241 end
242 end
243
244 # A socket listening on a given `port` for incomming connections
245 #
246 # Create streams to communicate with clients using `accept`.
247 class TCPServer
248 super TCPSocket
249
250 private var addrin: NativeSocketAddrIn is noinit
251
252 # Create and bind a listening server socket on port `port`
253 init
254 do
255 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
256 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
257 assert not native.address_is_null
258 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
259 closed = true
260 return
261 end
262
263 addrin = new NativeSocketAddrIn
264 addrin.family = new NativeSocketAddressFamilies.af_inet
265 addrin.port = port
266 addrin.address_any
267
268 address = addrin.address.to_s
269
270 # Bind it
271 closed = not bind
272 end
273
274 # Associates the socket to a local address and port
275 #
276 # Returns whether the socket has been be bound.
277 private fun bind: Bool do
278 return native.bind(addrin) >= 0
279 end
280
281 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
282 #
283 # Returns `true` if the socket could be set, `false` otherwise
284 fun listen(size: Int): Bool do
285 return native.listen(size) >= 0
286 end
287
288 # Accepts an incoming connection from a client
289 #
290 # Create and return a new socket to the client. May return null if not
291 # `blocking` and there's no waiting clients, or upon an interruption
292 # (whether `blocking` or not).
293 #
294 # Require: not closed
295 fun accept: nullable TCPStream
296 do
297 assert not closed
298 var native = native.accept
299 if native == null then return null
300 return new TCPStream.server_side(native)
301 end
302
303 # Close this socket
304 fun close
305 do
306 # FIXME unify with `SocketStream::close` when we can use qualified names
307
308 if closed then return
309 if native.close >= 0 then
310 closed = true
311 end
312 end
313 end
314
315 # A simple set of sockets used by `SocketObserver`
316 class SocketSet
317 private var native = new NativeSocketSet
318
319 init do clear
320
321 # Add `socket` to this set
322 fun add(socket: Socket) do native.set(socket.native)
323
324 # Remove `socket` from this set
325 fun remove(socket: Socket) do native.clear(socket.native)
326
327 # Does this set has `socket`?
328 fun has(socket: Socket): Bool do return native.is_set(socket.native)
329
330 # Clear all sockets from this set
331 fun clear do native.zero
332 end
333
334 # Service class to manage calls to `select`
335 class SocketObserver
336 private var native = new NativeSocketObserver
337
338 var read_set: nullable SocketSet = null
339
340 var write_set: nullable SocketSet = null
341
342 var except_set: nullable SocketSet = null
343
344 init(read: Bool, write: Bool, except: Bool)
345 is old_style_init do
346 if read then read_set = new SocketSet
347 if write then write_set = new SocketSet
348 if except then except_set = new SocketSet
349 end
350
351 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
352 do
353 # FIXME this implementation (see the call to nullable attributes below) and
354 # `NativeSockectObserver::select` is not stable.
355
356 var timeval = new NativeTimeval(seconds, microseconds)
357 return native.select(max.native, read_set.native, write_set.native, except_set.native, timeval) > 0
358 end
359 end
360
361 # Socket over UDP, sends and receive data without the need for a connection
362 class UDPSocket
363 super Socket
364
365 # Last error raised by this socket
366 var error: nullable Error = null
367
368 init do native = new NativeSocket.socket(
369 new NativeSocketAddressFamilies.af_inet,
370 new NativeSocketTypes.sock_dgram,
371 new NativeSocketProtocolFamilies.pf_null)
372
373 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
374 #
375 # On error, sets `error` appropriately.
376 fun bind(address: nullable Text, port: Int)
377 do
378 var addr_in = new NativeSocketAddrIn
379 addr_in.port = port
380 if address != null then
381 # FIXME replace all use of gethostbyname with something not obsolete
382 var hostent = sys.gethostbyname(address.to_cstring)
383 if hostent.address_is_null then
384 error = new IOError.from_h_errno
385 addr_in.free
386 return
387 end
388
389 addr_in.fill_from_hostent hostent
390 else
391 addr_in.family = new NativeSocketAddressFamilies.af_inet
392 addr_in.address_any
393 end
394
395 if native.bind(addr_in) != 0 then error = new IOError.from_errno
396
397 addr_in.free
398 end
399
400 # Receive `length` bytes of data from any sender
401 #
402 # On error, returns an empty string and sets `error` appropriately.
403 fun recv(length: Int): String
404 do
405 var buf = new NativeString(length)
406 var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
407 if len == -1 then
408 error = new IOError.from_errno
409 return ""
410 end
411 return buf.to_s_with_length(len)
412 end
413
414 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
415 #
416 # On error, returns an empty string and sets `error` appropriately.
417 fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
418 do
419 var src = new NativeSocketAddrIn
420 var buf = new NativeString(length)
421
422 var len = native.recvfrom(buf, length, 0, src)
423 if len == -1 then
424 error = new IOError.from_errno
425 src.free
426 return ""
427 end
428
429 sender.item = new SocketAddress(src)
430 return buf.to_s_with_length(len)
431 end
432
433 # Send `data` to `dest_address` on `port`
434 #
435 # On error, sets `error` appropriately.
436 fun send_to(dest_address: Text, port: Int, data: Text)
437 do
438 var hostent = sys.gethostbyname(dest_address.to_cstring)
439 if hostent.address_is_null then
440 error = new IOError.from_h_errno
441 return
442 end
443
444 var dest = new NativeSocketAddrIn
445 dest.fill_from_hostent hostent
446 dest.port = port
447 native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
448
449 var buf = data.to_cstring
450 if native.sendto(buf, data.length, 0, dest) == -1 then
451 error = new IOError.from_errno
452 end
453 dest.free
454 end
455
456 # Enable broadcasting for this socket
457 #
458 # On error, sets `error` appropriately.
459 fun enable_broadcast=(value: Bool) do
460 var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
461 if res == -1 then error = new IOError.from_errno
462 end
463
464 # Broadcast `data` on the network on `port`
465 #
466 # On error, sets `error` appropriately.
467 #
468 # Require: setting `enable_broadcast = true`
469 fun broadcast(port: Int, data: Text)
470 do
471 var addr_in = new NativeSocketAddrIn
472 addr_in.port = port
473 addr_in.family = new NativeSocketAddressFamilies.af_inet
474 addr_in.address_broadcast
475
476 var buf = data.to_cstring
477 if native.sendto(buf, data.length, 0, addr_in) == -1 then
478 error = new IOError.from_errno
479 end
480
481 addr_in.free
482 end
483 end
484
485 # Address of a socket in the Internet namespace
486 #
487 # Used in one of the out parameters of `UDPSocket::recv_from`.
488 class SocketAddress
489 super FinalizableOnce
490
491 # FIXME make init private
492
493 private var native: NativeSocketAddrIn
494
495 init
496 do
497 address = native.address.to_s
498 port = native.port
499 end
500
501 # Internet address
502 var address: String is noinit
503
504 # Port of the socket
505 var port: Int is noinit
506
507 redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
508
509 redef fun finalize_once do native.free
510 end
511
512 redef class IOError
513 # Fill a new `IOError` from the message of `errno`
514 init from_errno do init errno.strerror
515
516 # Fill a new `IOError` from the message of `h_errno`
517 #
518 # Used with `gethostbyname`.
519 init from_h_errno do init h_errno.to_s
520 end