2d176569b14fdd4f3d63a1e8317791084e08bda7
[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 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 CString(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 # Is this socket still connected?
153 fun connected: Bool
154 do
155 if closed then return false
156 if native.poll_hup_err == 0 then
157 return true
158 else
159 closed = true
160 return false
161 end
162 end
163
164 redef fun is_writable do return not end_reached
165
166 # Establishes a connection to socket addrin
167 #
168 # REQUIRES : not self.end_reached
169 private fun internal_connect: Bool
170 do
171 assert not closed
172 return native.connect(addrin) >= 0
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(bytes) do
189 if closed then return
190 var s = bytes.to_s
191 native.write(s.to_cstring, s.length)
192 end
193
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 fill_buffer
202 do
203 if not connected then return
204
205 var read = native.read(_buffer, _buffer_capacity)
206 if read == -1 then
207 close
208 end_reached = true
209 end
210
211 _buffer_length = read
212 _buffer_pos = 0
213 end
214
215 fun enlarge(len: Int) do
216 if _buffer_capacity >= len then return
217 _buffer_capacity = len
218
219 var ns = new CString(_buffer_capacity)
220 _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
221 _buffer = ns
222 end
223
224 redef fun close
225 do
226 if closed then return
227 if native.close >= 0 then
228 closed = true
229 end_reached = true
230 end
231 end
232
233 # Send the data present in the socket buffer
234 fun flush
235 do
236 if not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
237 not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
238 closed = true
239 end
240 end
241 end
242
243 # A socket listening on a given `port` for incomming connections
244 #
245 # Create streams to communicate with clients using `accept`.
246 class TCPServer
247 super TCPSocket
248
249 private var addrin: NativeSocketAddrIn is noinit
250
251 # Create and bind a listening server socket on port `port`
252 init
253 do
254 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
255 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
256 assert not native.address_is_null
257 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
258 closed = true
259 return
260 end
261
262 addrin = new NativeSocketAddrIn
263 addrin.family = new NativeSocketAddressFamilies.af_inet
264 addrin.port = port
265 addrin.address_any
266
267 address = addrin.address.to_s
268
269 # Bind it
270 closed = not bind
271 end
272
273 # Associates the socket to a local address and port
274 #
275 # Returns whether the socket has been be bound.
276 private fun bind: Bool do
277 return native.bind(addrin) >= 0
278 end
279
280 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
281 #
282 # Returns `true` if the socket could be set, `false` otherwise
283 fun listen(size: Int): Bool do
284 return native.listen(size) >= 0
285 end
286
287 # Accepts an incoming connection from a client
288 #
289 # Create and return a new socket to the client. May return null if not
290 # `blocking` and there's no waiting clients, or upon an interruption
291 # (whether `blocking` or not).
292 #
293 # Require: not closed
294 fun accept: nullable TCPStream
295 do
296 assert not closed
297 var native = native.accept
298 if native == null then return null
299 return new TCPStream.server_side(native)
300 end
301
302 # Close this socket
303 fun close
304 do
305 # FIXME unify with `SocketStream::close` when we can use qualified names
306
307 if closed then return
308 if native.close >= 0 then
309 closed = true
310 end
311 end
312 end
313
314 # A simple set of sockets used by `SocketObserver`
315 class SocketSet
316 private var native = new NativeSocketSet
317
318 init do clear
319
320 # Add `socket` to this set
321 fun add(socket: Socket) do native.set(socket.native)
322
323 # Remove `socket` from this set
324 fun remove(socket: Socket) do native.clear(socket.native)
325
326 # Does this set has `socket`?
327 fun has(socket: Socket): Bool do return native.is_set(socket.native)
328
329 # Clear all sockets from this set
330 fun clear do native.zero
331 end
332
333 # Service class to manage calls to `select`
334 class SocketObserver
335 private var native = new NativeSocketObserver
336
337 var read_set: nullable SocketSet = null
338
339 var write_set: nullable SocketSet = null
340
341 var except_set: nullable SocketSet = null
342
343 init(read: Bool, write: Bool, except: Bool)
344 is old_style_init do
345 if read then read_set = new SocketSet
346 if write then write_set = new SocketSet
347 if except then except_set = new SocketSet
348 end
349
350 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
351 do
352 # FIXME this implementation (see the call to nullable attributes below) and
353 # `NativeSockectObserver::select` is not stable.
354
355 var timeval = new NativeTimeval(seconds, microseconds)
356 return native.select(max.native, read_set.native, write_set.native, except_set.native, timeval) > 0
357 end
358 end
359
360 # Socket over UDP, sends and receive data without the need for a connection
361 class UDPSocket
362 super Socket
363
364 # Last error raised by this socket
365 var error: nullable Error = null
366
367 init do native = new NativeSocket.socket(
368 new NativeSocketAddressFamilies.af_inet,
369 new NativeSocketTypes.sock_dgram,
370 new NativeSocketProtocolFamilies.pf_null)
371
372 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
373 #
374 # On error, sets `error` appropriately.
375 fun bind(address: nullable Text, port: Int)
376 do
377 var addr_in = new NativeSocketAddrIn
378 addr_in.port = port
379 if address != null then
380 # FIXME replace all use of gethostbyname with something not obsolete
381 var hostent = sys.gethostbyname(address.to_cstring)
382 if hostent.address_is_null then
383 error = new IOError.from_h_errno
384 addr_in.free
385 return
386 end
387
388 addr_in.fill_from_hostent hostent
389 else
390 addr_in.family = new NativeSocketAddressFamilies.af_inet
391 addr_in.address_any
392 end
393
394 if native.bind(addr_in) != 0 then error = new IOError.from_errno
395
396 addr_in.free
397 end
398
399 # Receive `length` bytes of data from any sender
400 #
401 # On error, returns an empty string and sets `error` appropriately.
402 fun recv(length: Int): String
403 do
404 var buf = new CString(length)
405 var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
406 if len == -1 then
407 error = new IOError.from_errno
408 return ""
409 end
410 return buf.to_s_with_length(len)
411 end
412
413 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
414 #
415 # On error, returns an empty string and sets `error` appropriately.
416 fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
417 do
418 var src = new NativeSocketAddrIn
419 var buf = new CString(length)
420
421 var len = native.recvfrom(buf, length, 0, src)
422 if len == -1 then
423 error = new IOError.from_errno
424 src.free
425 return ""
426 end
427
428 sender.item = new SocketAddress(src)
429 return buf.to_s_with_length(len)
430 end
431
432 # Send `data` to `dest_address` on `port`
433 #
434 # On error, sets `error` appropriately.
435 fun send_to(dest_address: Text, port: Int, data: Text)
436 do
437 var hostent = sys.gethostbyname(dest_address.to_cstring)
438 if hostent.address_is_null then
439 error = new IOError.from_h_errno
440 return
441 end
442
443 var dest = new NativeSocketAddrIn
444 dest.fill_from_hostent hostent
445 dest.port = port
446 native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
447
448 var buf = data.to_cstring
449 if native.sendto(buf, data.length, 0, dest) == -1 then
450 error = new IOError.from_errno
451 end
452 dest.free
453 end
454
455 # Enable broadcasting for this socket
456 #
457 # On error, sets `error` appropriately.
458 fun enable_broadcast=(value: Bool) do
459 var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
460 if res == -1 then error = new IOError.from_errno
461 end
462
463 # Broadcast `data` on the network on `port`
464 #
465 # On error, sets `error` appropriately.
466 #
467 # Require: setting `enable_broadcast = true`
468 fun broadcast(port: Int, data: Text)
469 do
470 var addr_in = new NativeSocketAddrIn
471 addr_in.port = port
472 addr_in.family = new NativeSocketAddressFamilies.af_inet
473 addr_in.address_broadcast
474
475 var buf = data.to_cstring
476 if native.sendto(buf, data.length, 0, addr_in) == -1 then
477 error = new IOError.from_errno
478 end
479
480 addr_in.free
481 end
482 end
483
484 # Address of a socket in the Internet namespace
485 #
486 # Used in one of the out parameters of `UDPSocket::recv_from`.
487 class SocketAddress
488 super FinalizableOnce
489
490 # FIXME make init private
491
492 private var native: NativeSocketAddrIn
493
494 init
495 do
496 address = native.address.to_s
497 port = native.port
498 end
499
500 # Internet address
501 var address: String is noinit
502
503 # Port of the socket
504 var port: Int is noinit
505
506 redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
507
508 redef fun finalize_once do native.free
509 end
510
511 redef class IOError
512 # Fill a new `IOError` from the message of `errno`
513 init from_errno do init errno.strerror
514
515 # Fill a new `IOError` from the message of `h_errno`
516 #
517 # Used with `gethostbyname`.
518 init from_h_errno do init h_errno.to_s
519 end