Merge: doc: fixed some typos and other misc. corrections
[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 core::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 PollableReader
59 super Duplex
60
61 # Real canonical name of the host to which `self` is connected
62 var host: String
63
64 private var addrin: NativeSocketAddrIn is noinit
65
66 redef var closed = false
67
68 # TODO make init private
69
70 # Creates a socket connection to host `host` on port `port`
71 init connect(host: String, port: Int)
72 do
73 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
74 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_unspec)
75 if native.address_is_null then
76 closed = true
77 return
78 end
79 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
80 closed = true
81 return
82 end
83
84 var hostent = sys.gethostbyname(host.to_cstring)
85 if hostent.address_is_null then
86 # Error in name lookup
87 last_error = new IOError.from_h_errno
88
89 closed = true
90
91 return
92 end
93
94 addrin = new NativeSocketAddrIn
95 addrin.fill_from_hostent hostent
96 addrin.port = port
97
98 address = addrin.address.to_s
99 init(addrin.port, hostent.h_name.to_s)
100
101 closed = not internal_connect
102 if closed then
103 # Connection failed
104 last_error = new IOError.from_errno
105 end
106 end
107
108 # Creates a client socket, this is meant to be used by accept only
109 private init server_side(h: SocketAcceptResult)
110 do
111 native = h.socket
112 addrin = h.addr_in
113 address = addrin.address.to_s
114
115 init(addrin.port, address)
116 end
117
118 redef fun poll_in do return ready_to_read(0)
119
120 # Returns an array containing an enum of the events ready to be read
121 #
122 # event_types : Combination of several event types to watch
123 #
124 # timeout : Time in milliseconds before stopping listening for events on this socket
125 private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
126 if closed then return new Array[NativeSocketPollValues]
127 return native.socket_poll(new PollFD.from_poll_values(native.descriptor, event_types), timeout)
128 end
129
130 # Easier use of pollin to check for something to read on all channels of any priority
131 #
132 # timeout : Time in milliseconds before stopping to wait for events
133 fun ready_to_read(timeout: Int): Bool
134 do
135 var events = [new NativeSocketPollValues.pollin]
136 return pollin(events, timeout).length != 0
137 end
138
139 # Is this socket still connected?
140 fun connected: Bool
141 do
142 if closed then return false
143 if native.poll_hup_err == 0 then
144 return true
145 else
146 closed = true
147 return false
148 end
149 end
150
151 redef fun is_writable do return not closed
152
153 # Establishes a connection to socket addrin
154 #
155 # REQUIRES : not self.end_reached
156 private fun internal_connect: Bool
157 do
158 assert not closed
159 return native.connect(addrin) >= 0
160 end
161
162 redef fun raw_read_byte do
163 var rd = native.read(write_buffer, 1)
164 if rd < 1 then return -1
165 return write_buffer[0].to_i
166 end
167
168 redef fun raw_read_bytes(ns, max) do
169 var rd = native.read(ns, max)
170 print "Read {rd} bytes"
171 if rd < 0 then return -1
172 return rd
173 end
174
175 # If socket.end_reached, nothing will happen
176 redef fun write(msg)
177 do
178 if closed then return
179 native.write(msg.to_cstring, msg.length)
180 end
181
182 redef fun write_byte(value)
183 do
184 if closed then return
185 native.write_byte value
186 end
187
188 redef fun write_bytes_from_cstring(ns, len) do
189 if closed then return
190 native.write(ns, len)
191 end
192
193 # Write `msg`, with a trailing `\n`
194 fun write_ln(msg: Text)
195 do
196 if closed then return
197 write msg.to_s
198 write "\n"
199 end
200
201 redef fun close
202 do
203 if closed then return
204 if native.close >= 0 then
205 closed = true
206 end
207 end
208
209 # Send the data present in the socket buffer
210 fun flush
211 do
212 if not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
213 not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
214 closed = true
215 end
216 end
217 end
218
219 # A socket listening on a given `port` for incomming connections
220 #
221 # Create streams to communicate with clients using `accept`.
222 class TCPServer
223 super TCPSocket
224
225 private var addrin: NativeSocketAddrIn is noinit
226
227 # Create and bind a listening server socket on port `port`
228 init
229 do
230 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
231 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_unspec)
232 assert not native.address_is_null
233 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
234 closed = true
235 return
236 end
237
238 addrin = new NativeSocketAddrIn
239 addrin.family = new NativeSocketAddressFamilies.af_inet
240 addrin.port = port
241 addrin.address_any
242
243 address = addrin.address.to_s
244
245 # Bind it
246 closed = not bind
247 end
248
249 # Associates the socket to a local address and port
250 #
251 # Returns whether the socket has been be bound.
252 private fun bind: Bool do
253 return native.bind(addrin) >= 0
254 end
255
256 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
257 #
258 # Returns `true` if the socket could be set, `false` otherwise
259 fun listen(size: Int): Bool do
260 return native.listen(size) >= 0
261 end
262
263 # Accepts an incoming connection from a client
264 #
265 # Create and return a new socket to the client. May return null if not
266 # `blocking` and there's no waiting clients, or upon an interruption
267 # (whether `blocking` or not).
268 #
269 # Require: not closed
270 fun accept: nullable TCPStream
271 do
272 assert not closed
273 var native = native.accept
274 if native == null then return null
275 return new TCPStream.server_side(native)
276 end
277
278 # Close this socket
279 fun close
280 do
281 # FIXME unify with `SocketStream::close` when we can use qualified names
282
283 if closed then return
284 if native.close >= 0 then
285 closed = true
286 end
287 end
288 end
289
290 # A simple set of sockets used by `SocketObserver`
291 class SocketSet
292 private var native = new NativeSocketSet
293
294 init do clear
295
296 # Add `socket` to this set
297 fun add(socket: Socket) do native.set(socket.native)
298
299 # Remove `socket` from this set
300 fun remove(socket: Socket) do native.clear(socket.native)
301
302 # Does this set has `socket`?
303 fun has(socket: Socket): Bool do return native.is_set(socket.native)
304
305 # Clear all sockets from this set
306 fun clear do native.zero
307 end
308
309 # Service class to manage calls to `select`
310 class SocketObserver
311 private var native = new NativeSocketObserver
312
313 # Set of file descriptors on which to watch read events
314 var read_set: nullable SocketSet = null
315
316 # Set of file descriptors on which to watch write events
317 var write_set: nullable SocketSet = null
318
319 # Set of file descriptors on which to watch exception events
320 var except_set: nullable SocketSet = null
321
322 # Initialize a socket observer
323 init with_sets(read: Bool, write: Bool, except: Bool) do
324 if read then read_set = new SocketSet
325 if write then write_set = new SocketSet
326 if except then except_set = new SocketSet
327 end
328
329 # Watch for changes in the states of several sockets.
330 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
331 do
332 # FIXME this implementation (see the call to nullable attributes below) and
333 # `NativeSockectObserver::select` is not stable.
334
335 var timeval = new NativeTimeval(seconds, microseconds)
336 var rd = if read_set != null then read_set.as(not null).native else null
337 var wrt = if write_set != null then write_set.as(not null).native else null
338 var expt = if except_set != null then except_set.as(not null).native else null
339 return native.select(max.native, rd, wrt, expt, timeval) > 0
340 end
341 end
342
343 # Socket over UDP, sends and receive data without the need for a connection
344 class UDPSocket
345 super Socket
346
347 # Last error raised by this socket
348 var error: nullable Error = null
349
350 init do native = new NativeSocket.socket(
351 new NativeSocketAddressFamilies.af_inet,
352 new NativeSocketTypes.sock_dgram,
353 new NativeSocketProtocolFamilies.pf_unspec)
354
355 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
356 #
357 # On error, sets `error` appropriately.
358 fun bind(address: nullable Text, port: Int)
359 do
360 var addr_in = new NativeSocketAddrIn
361 addr_in.port = port
362 if address != null then
363 # FIXME replace all use of gethostbyname with something not obsolete
364 var hostent = sys.gethostbyname(address.to_cstring)
365 if hostent.address_is_null then
366 error = new IOError.from_h_errno
367 addr_in.free
368 return
369 end
370
371 addr_in.fill_from_hostent hostent
372 else
373 addr_in.family = new NativeSocketAddressFamilies.af_inet
374 addr_in.address_any
375 end
376
377 if native.bind(addr_in) != 0 then error = new IOError.from_errno
378
379 addr_in.free
380 end
381
382 # Receive `length` bytes of data from any sender
383 #
384 # On error, returns an empty string and sets `error` appropriately.
385 fun recv(length: Int): String
386 do
387 var buf = new CString(length)
388 var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
389 if len == -1 then
390 error = new IOError.from_errno
391 return ""
392 end
393 return buf.to_s_unsafe(len, copy=false)
394 end
395
396 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
397 #
398 # On error, returns an empty string and sets `error` appropriately.
399 fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
400 do
401 var src = new NativeSocketAddrIn
402 var buf = new CString(length)
403
404 var len = native.recvfrom(buf, length, 0, src)
405 if len == -1 then
406 error = new IOError.from_errno
407 src.free
408 return ""
409 end
410
411 sender.item = new SocketAddress(src)
412 return buf.to_s_unsafe(len, copy=false)
413 end
414
415 # Send `data` to `dest_address` on `port`
416 #
417 # On error, sets `error` appropriately.
418 fun send_to(dest_address: Text, port: Int, data: Text)
419 do
420 var hostent = sys.gethostbyname(dest_address.to_cstring)
421 if hostent.address_is_null then
422 error = new IOError.from_h_errno
423 return
424 end
425
426 var dest = new NativeSocketAddrIn
427 dest.fill_from_hostent hostent
428 dest.port = port
429 native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
430
431 var buf = data.to_cstring
432 if native.sendto(buf, data.length, 0, dest) == -1 then
433 error = new IOError.from_errno
434 end
435 dest.free
436 end
437
438 # Enable broadcasting for this socket
439 #
440 # On error, sets `error` appropriately.
441 fun enable_broadcast=(value: Bool) do
442 var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
443 if res == -1 then error = new IOError.from_errno
444 end
445
446 # Broadcast `data` on the network on `port`
447 #
448 # On error, sets `error` appropriately.
449 #
450 # Require: setting `enable_broadcast = true`
451 fun broadcast(port: Int, data: Text)
452 do
453 var addr_in = new NativeSocketAddrIn
454 addr_in.port = port
455 addr_in.family = new NativeSocketAddressFamilies.af_inet
456 addr_in.address_broadcast
457
458 var buf = data.to_cstring
459 if native.sendto(buf, data.length, 0, addr_in) == -1 then
460 error = new IOError.from_errno
461 end
462
463 addr_in.free
464 end
465 end
466
467 # Address of a socket in the Internet namespace
468 #
469 # Used in one of the out parameters of `UDPSocket::recv_from`.
470 class SocketAddress
471 super FinalizableOnce
472
473 # FIXME make init private
474
475 private var native: NativeSocketAddrIn
476
477 init
478 do
479 address = native.address.to_s
480 port = native.port
481 end
482
483 # Internet address
484 var address: String is noinit
485
486 # Port of the socket
487 var port: Int is noinit
488
489 redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
490
491 redef fun finalize_once do native.free
492 end
493
494 redef class IOError
495 # Fill a new `IOError` from the message of `errno`
496 init from_errno do init errno.strerror
497
498 # Fill a new `IOError` from the message of `h_errno`
499 #
500 # Used with `gethostbyname`.
501 init from_h_errno do init h_errno.to_s
502 end