a40cc5ded43f52d7a4d5b8ef15ac87671472fa99
[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 TCP socket, either a `TCPStream` or a `TCPServer`
24 abstract class Socket
25
26 # Underlying C socket
27 private var socket: NativeSocket is noinit
28
29 # Port used by the socket
30 var port: Int
31
32 # IPv4 address to which `self` is connected
33 #
34 # Formatted as xxx.xxx.xxx.xxx.
35 var address: String is noinit
36
37 # Is this socket closed?
38 var closed = false
39 end
40
41 # Simple communication stream with a remote socket
42 class TCPStream
43 super Socket
44 super BufferedReader
45 super Writer
46 super PollableReader
47
48 # Real canonical name of the host to which `self` is connected
49 var host: String
50
51 private var addrin: NativeSocketAddrIn is noinit
52
53 redef var end_reached = false
54
55 # TODO make init private
56
57 # Creates a socket connection to host `host` on port `port`
58 init connect(host: String, port: Int)
59 do
60 _buffer = new NativeString(1024)
61 _buffer_pos = 0
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
65 end_reached = true
66 closed = true
67 return
68 end
69 if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
70 end_reached = true
71 closed = true
72 return
73 end
74
75 var hostname = sys.gethostbyname(host.to_cstring)
76 if hostname.address_is_null then
77 # Error in name lookup
78 var err = sys.h_errno
79 last_error = new IOError(err.to_s)
80
81 closed = true
82 end_reached = true
83
84 return
85 end
86
87 addrin = new NativeSocketAddrIn.with_hostent(hostname, port)
88 address = addrin.address
89 init(addrin.port, hostname.h_name)
90
91 closed = not internal_connect
92 end_reached = closed
93 if closed then
94 # Connection failed
95 last_error = new IOError(errno.strerror)
96 end
97 end
98
99 # Creates a client socket, this is meant to be used by accept only
100 private init server_side(h: SocketAcceptResult)
101 do
102 _buffer = new NativeString(1024)
103 _buffer_pos = 0
104 socket = h.socket
105 addrin = h.addr_in
106 address = addrin.address
107
108 init(addrin.port, address)
109 end
110
111 redef fun poll_in do return ready_to_read(0)
112
113 # Returns an array containing an enum of the events ready to be read
114 #
115 # event_types : Combination of several event types to watch
116 #
117 # timeout : Time in milliseconds before stopping listening for events on this socket
118 private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
119 if end_reached then return new Array[NativeSocketPollValues]
120 return socket.socket_poll(new PollFD(socket.descriptor, event_types), timeout)
121 end
122
123 # Easier use of pollin to check for something to read on all channels of any priority
124 #
125 # timeout : Time in milliseconds before stopping to wait for events
126 fun ready_to_read(timeout: Int): Bool
127 do
128 if _buffer_pos < _buffer_length then return true
129 if end_reached then return false
130 var events = [new NativeSocketPollValues.pollin]
131 return pollin(events, timeout).length != 0
132 end
133
134 # Checks if the socket still is connected
135 fun connected: Bool
136 do
137 if closed then return false
138 var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr]
139 if pollin(events, 0).length == 0 then
140 return true
141 else
142 closed = true
143 return false
144 end
145 end
146
147 redef fun is_writable do return not end_reached
148
149 # Establishes a connection to socket addrin
150 #
151 # REQUIRES : not self.end_reached
152 private fun internal_connect: Bool
153 do
154 assert not closed
155 return socket.connect(addrin) >= 0
156 end
157
158 # If socket.end_reached, nothing will happen
159 redef fun write(msg)
160 do
161 if closed then return
162 socket.write(msg.to_s)
163 end
164
165 redef fun write_byte(value)
166 do
167 if closed then return
168 socket.write_byte value
169 end
170
171 redef fun write_bytes(s) do
172 if closed then return
173 socket.write(s.to_s)
174 end
175
176 fun write_ln(msg: Text)
177 do
178 if end_reached then return
179 write(msg.to_s)
180 write("\n")
181 end
182
183 redef fun fill_buffer
184 do
185 _buffer_length = 0
186 _buffer_pos = 0
187 if not connected then return
188 var read = socket.read
189 if read.length == 0 then
190 close
191 end_reached = true
192 end
193 enlarge(_buffer_length + read.length)
194 read.copy_to_native(_buffer, read.length, 0, 0)
195 _buffer_length = read.length
196 end
197
198 fun enlarge(len: Int) do
199 if _buffer_capacity >= len then return
200 while _buffer_capacity < len do _buffer_capacity = _buffer_capacity * 2 + 2
201 var ns = new NativeString(_buffer_capacity)
202 _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
203 _buffer = ns
204 end
205
206 redef fun close
207 do
208 if closed then return
209 if socket.close >= 0 then
210 closed = true
211 end_reached = true
212 end
213 end
214
215 # Send the data present in the socket buffer
216 fun flush
217 do
218 if not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
219 not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
220 closed = true
221 end
222 end
223 end
224
225 # A socket listening on a given `port` for incomming connections
226 #
227 # Create streams to communicate with clients using `accept`.
228 class TCPServer
229 super Socket
230
231 private var addrin: NativeSocketAddrIn is noinit
232
233 # Create and bind a listening server socket on port `port`
234 init
235 do
236 socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
237 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
238 assert not socket.address_is_null
239 if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
240 closed = true
241 return
242 end
243 addrin = new NativeSocketAddrIn.with_port(port, new NativeSocketAddressFamilies.af_inet)
244 address = addrin.address
245
246 # Bind it
247 closed = not bind
248 end
249
250 # Associates the socket to a local address and port
251 #
252 # Returns whether the socket has been be bound.
253 private fun bind: Bool do
254 return socket.bind(addrin) >= 0
255 end
256
257 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
258 #
259 # Returns `true` if the socket could be set, `false` otherwise
260 fun listen(size: Int): Bool do
261 return socket.listen(size) >= 0
262 end
263
264 # Accepts an incoming connection from a client
265 #
266 # Create and return a new socket to the client. May return null if not
267 # `blocking` and there's no waiting clients, or upon an interruption
268 # (whether `blocking` or not).
269 #
270 # Require: not closed
271 fun accept: nullable TCPStream
272 do
273 assert not closed
274 var native = socket.accept
275 if native == null then return null
276 return new TCPStream.server_side(native)
277 end
278
279 # Set whether calls to `accept` are blocking
280 fun blocking=(value: Bool)
281 do
282 # We use the opposite from the native version as the native API
283 # is closer to the C API. In the Nity API, we use a positive version
284 # of the name.
285 socket.non_blocking = not value
286 end
287
288 # Close this socket
289 fun close
290 do
291 # FIXME unify with `SocketStream::close` when we can use qualified names
292
293 if closed then return
294 if socket.close >= 0 then
295 closed = true
296 end
297 end
298 end
299
300 # A simple set of sockets used by `SocketObserver`
301 class SocketSet
302 private var native = new NativeSocketSet
303
304 init do clear
305
306 # Add `socket` to this set
307 fun add(socket: Socket) do native.set(socket.socket)
308
309 # Remove `socket` from this set
310 fun remove(socket: Socket) do native.clear(socket.socket)
311
312 # Does this set has `socket`?
313 fun has(socket: Socket): Bool do return native.is_set(socket.socket)
314
315 # Clear all sockets from this set
316 fun clear do native.zero
317 end
318
319 # Service class to manage calls to `select`
320 class SocketObserver
321 private var native = new NativeSocketObserver
322
323 var read_set: nullable SocketSet = null
324
325 var write_set: nullable SocketSet = null
326
327 var except_set: nullable SocketSet = null
328
329 init(read: Bool, write: Bool, except: Bool)
330 is old_style_init do
331 if read then read_set = new SocketSet
332 if write then write_set = new SocketSet
333 if except then except_set = new SocketSet
334 end
335
336 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
337 do
338 # FIXME this implementation (see the call to nullable attributes below) and
339 # `NativeSockectObserver::select` is not stable.
340
341 var timeval = new NativeTimeval(seconds, microseconds)
342 return native.select(max.socket, read_set.native, write_set.native, except_set.native, timeval) > 0
343 end
344 end