1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Matthieu Lucas <lucasmatthieu@gmail.com>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 private import socket_c
21 intrude import core
::stream
23 # A general Socket, either TCP or UDP
27 private var native
: NativeSocket is noinit
29 # Is this socket closed?
32 # Set whether calls to `accept` are blocking
33 fun blocking
=(value
: Bool)
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
38 native
.non_blocking
= not value
42 # A general TCP socket, either a `TCPStream` or a `TCPServer`
43 abstract class TCPSocket
46 # Port used by the socket
49 # IPv4 address to which `self` is connected
51 # Formatted as xxx.xxx.xxx.xxx.
52 var address
: String is noinit
55 # Simple communication stream with a remote socket
62 # Real canonical name of the host to which `self` is connected
65 private var addrin
: NativeSocketAddrIn is noinit
67 redef var end_reached
= false
69 # TODO make init private
71 # Creates a socket connection to host `host` on port `port`
72 init connect
(host
: String, port
: Int)
74 _buffer
= new CString(1024)
76 native
= new NativeSocket.socket
(new NativeSocketAddressFamilies.af_inet
,
77 new NativeSocketTypes.sock_stream
, new NativeSocketProtocolFamilies.pf_unspec
)
78 if native
.address_is_null
then
83 if not native
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.reuseaddr
, 1) then
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
100 addrin
= new NativeSocketAddrIn
101 addrin
.fill_from_hostent hostent
104 address
= addrin
.address
.to_s
105 init(addrin
.port
, hostent
.h_name
.to_s
)
107 closed
= not internal_connect
111 last_error
= new IOError.from_errno
117 # Creates a client socket, this is meant to be used by accept only
118 private init server_side
(h
: SocketAcceptResult)
122 address
= addrin
.address
.to_s
124 init(addrin
.port
, address
)
129 redef fun poll_in
do return ready_to_read
(0)
131 # Returns an array containing an enum of the events ready to be read
133 # event_types : Combination of several event types to watch
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.from_poll_values
(native
.descriptor
, event_types
), timeout
)
141 # Easier use of pollin to check for something to read on all channels of any priority
143 # timeout : Time in milliseconds before stopping to wait for events
144 fun ready_to_read
(timeout
: Int): Bool
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
152 # Is this socket still connected?
155 if closed
then return false
156 if native
.poll_hup_err
== 0 then
164 redef fun is_writable
do return not end_reached
166 # Establishes a connection to socket addrin
168 # REQUIRES : not self.end_reached
169 private fun internal_connect
: Bool
172 return native
.connect
(addrin
) >= 0
175 # If socket.end_reached, nothing will happen
178 if closed
then return
179 native
.write
(msg
.to_cstring
, msg
.length
)
182 redef fun write_byte
(value
)
184 if closed
then return
185 native
.write_byte value
188 redef fun write_bytes_from_cstring
(ns
, len
) do
189 if closed
then return
190 native
.write
(ns
, len
)
193 # Write `msg`, with a trailing `\n`
194 fun write_ln
(msg
: Text)
196 if closed
then return
201 redef fun fill_buffer
203 if not connected
then return
205 var read
= native
.read
(_buffer
, _buffer_capacity
)
211 _buffer_length
= read
215 # Enlarge `_buffer` to at least `len` bytes
216 fun enlarge
(len
: Int) do
217 if _buffer_capacity
>= len
then return
218 _buffer_capacity
= len
220 var ns
= new CString(_buffer_capacity
)
221 _buffer
.copy_to
(ns
, _buffer_length
- _buffer_pos
, _buffer_pos
, 0)
227 if closed
then return
228 if native
.close
>= 0 then
234 # Send the data present in the socket buffer
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
244 # A socket listening on a given `port` for incomming connections
246 # Create streams to communicate with clients using `accept`.
250 private var addrin
: NativeSocketAddrIn is noinit
252 # Create and bind a listening server socket on port `port`
255 native
= new NativeSocket.socket
(new NativeSocketAddressFamilies.af_inet
,
256 new NativeSocketTypes.sock_stream
, new NativeSocketProtocolFamilies.pf_unspec
)
257 assert not native
.address_is_null
258 if not native
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.reuseaddr
, 1) then
263 addrin
= new NativeSocketAddrIn
264 addrin
.family
= new NativeSocketAddressFamilies.af_inet
268 address
= addrin
.address
.to_s
274 # Associates the socket to a local address and port
276 # Returns whether the socket has been be bound.
277 private fun bind
: Bool do
278 return native
.bind
(addrin
) >= 0
281 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
283 # Returns `true` if the socket could be set, `false` otherwise
284 fun listen
(size
: Int): Bool do
285 return native
.listen
(size
) >= 0
288 # Accepts an incoming connection from a client
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).
294 # Require: not closed
295 fun accept
: nullable TCPStream
298 var native
= native
.accept
299 if native
== null then return null
300 return new TCPStream.server_side
(native
)
306 # FIXME unify with `SocketStream::close` when we can use qualified names
308 if closed
then return
309 if native
.close
>= 0 then
315 # A simple set of sockets used by `SocketObserver`
317 private var native
= new NativeSocketSet
321 # Add `socket` to this set
322 fun add
(socket
: Socket) do native
.set
(socket
.native
)
324 # Remove `socket` from this set
325 fun remove
(socket
: Socket) do native
.clear
(socket
.native
)
327 # Does this set has `socket`?
328 fun has
(socket
: Socket): Bool do return native
.is_set
(socket
.native
)
330 # Clear all sockets from this set
331 fun clear
do native
.zero
334 # Service class to manage calls to `select`
336 private var native
= new NativeSocketObserver
338 # Set of file descriptors on which to watch read events
339 var read_set
: nullable SocketSet = null
341 # Set of file descriptors on which to watch write events
342 var write_set
: nullable SocketSet = null
344 # Set of file descriptors on which to watch exception events
345 var except_set
: nullable SocketSet = null
347 # Initialize a socket observer
348 init with_sets
(read
: Bool, write
: Bool, except
: Bool) do
349 if read
then read_set
= new SocketSet
350 if write
then write_set
= new SocketSet
351 if except
then except_set
= new SocketSet
354 # Watch for changes in the states of several sockets.
355 fun select
(max
: Socket, seconds
: Int, microseconds
: Int): Bool
357 # FIXME this implementation (see the call to nullable attributes below) and
358 # `NativeSockectObserver::select` is not stable.
360 var timeval
= new NativeTimeval(seconds
, microseconds
)
361 var rd
= if read_set
!= null then read_set
.as(not null).native
else null
362 var wrt
= if write_set
!= null then write_set
.as(not null).native
else null
363 var expt
= if except_set
!= null then except_set
.as(not null).native
else null
364 return native
.select
(max
.native
, rd
, wrt
, expt
, timeval
) > 0
368 # Socket over UDP, sends and receive data without the need for a connection
372 # Last error raised by this socket
373 var error
: nullable Error = null
375 init do native
= new NativeSocket.socket
(
376 new NativeSocketAddressFamilies.af_inet
,
377 new NativeSocketTypes.sock_dgram
,
378 new NativeSocketProtocolFamilies.pf_unspec
)
380 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
382 # On error, sets `error` appropriately.
383 fun bind
(address
: nullable Text, port
: Int)
385 var addr_in
= new NativeSocketAddrIn
387 if address
!= null then
388 # FIXME replace all use of gethostbyname with something not obsolete
389 var hostent
= sys
.gethostbyname
(address
.to_cstring
)
390 if hostent
.address_is_null
then
391 error
= new IOError.from_h_errno
396 addr_in
.fill_from_hostent hostent
398 addr_in
.family
= new NativeSocketAddressFamilies.af_inet
402 if native
.bind
(addr_in
) != 0 then error
= new IOError.from_errno
407 # Receive `length` bytes of data from any sender
409 # On error, returns an empty string and sets `error` appropriately.
410 fun recv
(length
: Int): String
412 var buf
= new CString(length
)
413 var len
= native
.recvfrom
(buf
, length
, 0, new NativeSocketAddrIn.nul
)
415 error
= new IOError.from_errno
418 return buf
.to_s_unsafe
(len
, copy
=false)
421 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
423 # On error, returns an empty string and sets `error` appropriately.
424 fun recv_from
(length
: Int, sender
: Ref[nullable SocketAddress]): String
426 var src
= new NativeSocketAddrIn
427 var buf
= new CString(length
)
429 var len
= native
.recvfrom
(buf
, length
, 0, src
)
431 error
= new IOError.from_errno
436 sender
.item
= new SocketAddress(src
)
437 return buf
.to_s_unsafe
(len
, copy
=false)
440 # Send `data` to `dest_address` on `port`
442 # On error, sets `error` appropriately.
443 fun send_to
(dest_address
: Text, port
: Int, data
: Text)
445 var hostent
= sys
.gethostbyname
(dest_address
.to_cstring
)
446 if hostent
.address_is_null
then
447 error
= new IOError.from_h_errno
451 var dest
= new NativeSocketAddrIn
452 dest
.fill_from_hostent hostent
454 native
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.broadcast
, 1)
456 var buf
= data
.to_cstring
457 if native
.sendto
(buf
, data
.length
, 0, dest
) == -1 then
458 error
= new IOError.from_errno
463 # Enable broadcasting for this socket
465 # On error, sets `error` appropriately.
466 fun enable_broadcast
=(value
: Bool) do
467 var res
= native
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.broadcast
, value
.to_i
)
468 if res
== -1 then error
= new IOError.from_errno
471 # Broadcast `data` on the network on `port`
473 # On error, sets `error` appropriately.
475 # Require: setting `enable_broadcast = true`
476 fun broadcast
(port
: Int, data
: Text)
478 var addr_in
= new NativeSocketAddrIn
480 addr_in
.family
= new NativeSocketAddressFamilies.af_inet
481 addr_in
.address_broadcast
483 var buf
= data
.to_cstring
484 if native
.sendto
(buf
, data
.length
, 0, addr_in
) == -1 then
485 error
= new IOError.from_errno
492 # Address of a socket in the Internet namespace
494 # Used in one of the out parameters of `UDPSocket::recv_from`.
496 super FinalizableOnce
498 # FIXME make init private
500 private var native
: NativeSocketAddrIn
504 address
= native
.address
.to_s
509 var address
: String is noinit
512 var port
: Int is noinit
514 redef fun ==(o
) do return o
isa SocketAddress and o
.address
== address
and o
.port
== port
516 redef fun finalize_once
do native
.free
520 # Fill a new `IOError` from the message of `errno`
521 init from_errno
do init errno
.strerror
523 # Fill a new `IOError` from the message of `h_errno`
525 # Used with `gethostbyname`.
526 init from_h_errno
do init h_errno
.to_s