7791159c0be18b01ae899fe42f39d75ac8c8e412
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 standard
::stream
23 # A general TCP socket, either a `TCPStream` or a `TCPServer`
27 private var socket
: NativeSocket is noinit
29 # Port used by the socket
32 # IPv4 address to which `self` is connected
34 # Formatted as xxx.xxx.xxx.xxx.
35 var address
: String is noinit
37 # Is this socket closed?
41 # Simple communication stream with a remote socket
48 # Real canonical name of the host to which `self` is connected
51 private var addrin
: NativeSocketAddrIn is noinit
53 redef var end_reached
= false
55 # TODO make init private
57 # Creates a socket connection to host `host` on port `port`
58 init connect
(host
: String, port
: Int)
60 _buffer
= new NativeString(1024)
62 socket
= new NativeSocket.socket
(new NativeSocketAddressFamilies.af_inet
,
63 new NativeSocketTypes.sock_stream
, new NativeSocketProtocolFamilies.pf_null
)
64 if socket
.address_is_null
then
69 if not socket
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.reuseaddr
, 1) then
75 var hostname
= sys
.gethostbyname
(host
.to_cstring
)
76 if hostname
.address_is_null
then
77 # Error in name lookup
79 last_error
= new IOError(err
.to_s
)
87 addrin
= new NativeSocketAddrIn.with_hostent
(hostname
, port
)
89 address
= addrin
.address
90 init(addrin
.port
, hostname
.h_name
)
92 closed
= not internal_connect
96 # Creates a client socket, this is meant to be used by accept only
97 private init server_side
(h
: SocketAcceptResult)
99 _buffer
= new NativeString(1024)
103 address
= addrin
.address
105 init(addrin
.port
, address
)
108 redef fun poll_in
do return ready_to_read
(0)
110 # Returns an array containing an enum of the events ready to be read
112 # event_types : Combination of several event types to watch
114 # timeout : Time in milliseconds before stopping listening for events on this socket
115 private fun pollin
(event_types
: Array[NativeSocketPollValues], timeout
: Int): Array[NativeSocketPollValues] do
116 if end_reached
then return new Array[NativeSocketPollValues]
117 return socket
.socket_poll
(new PollFD(socket
.descriptor
, event_types
), timeout
)
120 # Easier use of pollin to check for something to read on all channels of any priority
122 # timeout : Time in milliseconds before stopping to wait for events
123 fun ready_to_read
(timeout
: Int): Bool
125 if _buffer_pos
< _buffer_length
then return true
126 if end_reached
then return false
127 var events
= [new NativeSocketPollValues.pollin
]
128 return pollin
(events
, timeout
).length
!= 0
131 # Checks if the socket still is connected
134 if closed
then return false
135 var events
= [new NativeSocketPollValues.pollhup
, new NativeSocketPollValues.pollerr
]
136 if pollin
(events
, 0).length
== 0 then
144 redef fun is_writable
do return not end_reached
146 # Establishes a connection to socket addrin
148 # REQUIRES : not self.end_reached
149 private fun internal_connect
: Bool
152 return socket
.connect
(addrin
) >= 0
155 # If socket.end_reached, nothing will happen
158 if closed
then return
159 socket
.write
(msg
.to_s
)
162 redef fun write_byte
(value
)
164 if closed
then return
165 socket
.write_byte value
168 redef fun write_bytes
(s
) do
169 if closed
then return
173 fun write_ln
(msg
: Text)
175 if end_reached
then return
180 redef fun fill_buffer
184 if not connected
then return
185 var read
= socket
.read
186 if read
.length
== 0 then
190 enlarge
(_buffer_length
+ read
.length
)
191 read
.copy_to_native
(_buffer
, read
.length
, 0, 0)
192 _buffer_length
= read
.length
195 fun enlarge
(len
: Int) do
196 if _buffer_capacity
>= len
then return
197 while _buffer_capacity
< len
do _buffer_capacity
= _buffer_capacity
* 2 + 2
198 var ns
= new NativeString(_buffer_capacity
)
199 _buffer
.copy_to
(ns
, _buffer_length
- _buffer_pos
, _buffer_pos
, 0)
205 if closed
then return
206 if socket
.close
>= 0 then
212 # Send the data present in the socket buffer
215 if not socket
.setsockopt
(new NativeSocketOptLevels.tcp
, new NativeSocketOptNames.tcp_nodelay
, 1) or
216 not socket
.setsockopt
(new NativeSocketOptLevels.tcp
, new NativeSocketOptNames.tcp_nodelay
, 0) then
222 # A socket listening on a given `port` for incomming connections
224 # Create streams to communicate with clients using `accept`.
228 private var addrin
: NativeSocketAddrIn is noinit
230 # Create and bind a listening server socket on port `port`
233 socket
= new NativeSocket.socket
(new NativeSocketAddressFamilies.af_inet
,
234 new NativeSocketTypes.sock_stream
, new NativeSocketProtocolFamilies.pf_null
)
235 assert not socket
.address_is_null
236 if not socket
.setsockopt
(new NativeSocketOptLevels.socket
, new NativeSocketOptNames.reuseaddr
, 1) then
240 addrin
= new NativeSocketAddrIn.with_port
(port
, new NativeSocketAddressFamilies.af_inet
)
241 address
= addrin
.address
247 # Associates the socket to a local address and port
249 # Returns whether the socket has been be bound.
250 private fun bind
: Bool do
251 return socket
.bind
(addrin
) >= 0
254 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
256 # Returns `true` if the socket could be set, `false` otherwise
257 fun listen
(size
: Int): Bool do
258 return socket
.listen
(size
) >= 0
261 # Accepts an incoming connection from a client
263 # Create and return a new socket to the client. May return null if not
264 # `blocking` and there's no waiting clients, or upon an interruption
265 # (whether `blocking` or not).
267 # Require: not closed
268 fun accept
: nullable TCPStream
271 var native
= socket
.accept
272 if native
== null then return null
273 return new TCPStream.server_side
(native
)
276 # Set whether calls to `accept` are blocking
277 fun blocking
=(value
: Bool)
279 # We use the opposite from the native version as the native API
280 # is closer to the C API. In the Nity API, we use a positive version
282 socket
.non_blocking
= not value
288 # FIXME unify with `SocketStream::close` when we can use qualified names
290 if closed
then return
291 if socket
.close
>= 0 then
297 # A simple set of sockets used by `SocketObserver`
299 private var native
= new NativeSocketSet
303 # Add `socket` to this set
304 fun add
(socket
: Socket) do native
.set
(socket
.socket
)
306 # Remove `socket` from this set
307 fun remove
(socket
: Socket) do native
.clear
(socket
.socket
)
309 # Does this set has `socket`?
310 fun has
(socket
: Socket): Bool do return native
.is_set
(socket
.socket
)
312 # Clear all sockets from this set
313 fun clear
do native
.zero
316 # Service class to manage calls to `select`
318 private var native
= new NativeSocketObserver
320 var read_set
: nullable SocketSet = null
322 var write_set
: nullable SocketSet = null
324 var except_set
: nullable SocketSet = null
326 init(read
: Bool, write
: Bool, except
: Bool)
328 if read
then read_set
= new SocketSet
329 if write
then write_set
= new SocketSet
330 if except
then except_set
= new SocketSet
333 fun select
(max
: Socket, seconds
: Int, microseconds
: Int): Bool
335 # FIXME this implementation (see the call to nullable attributes below) and
336 # `NativeSockectObserver::select` is not stable.
338 var timeval
= new NativeTimeval(seconds
, microseconds
)
339 return native
.select
(max
.socket
, read_set
.native
, write_set
.native
, except_set
.native
, timeval
) > 0