7791159c0be18b01ae899fe42f39d75ac8c8e412
[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
89 address = addrin.address
90 init(addrin.port, hostname.h_name)
91
92 closed = not internal_connect
93 end_reached = closed
94 end
95
96 # Creates a client socket, this is meant to be used by accept only
97 private init server_side(h: SocketAcceptResult)
98 do
99 _buffer = new NativeString(1024)
100 _buffer_pos = 0
101 socket = h.socket
102 addrin = h.addr_in
103 address = addrin.address
104
105 init(addrin.port, address)
106 end
107
108 redef fun poll_in do return ready_to_read(0)
109
110 # Returns an array containing an enum of the events ready to be read
111 #
112 # event_types : Combination of several event types to watch
113 #
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)
118 end
119
120 # Easier use of pollin to check for something to read on all channels of any priority
121 #
122 # timeout : Time in milliseconds before stopping to wait for events
123 fun ready_to_read(timeout: Int): Bool
124 do
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
129 end
130
131 # Checks if the socket still is connected
132 fun connected: Bool
133 do
134 if closed then return false
135 var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr]
136 if pollin(events, 0).length == 0 then
137 return true
138 else
139 closed = true
140 return false
141 end
142 end
143
144 redef fun is_writable do return not end_reached
145
146 # Establishes a connection to socket addrin
147 #
148 # REQUIRES : not self.end_reached
149 private fun internal_connect: Bool
150 do
151 assert not closed
152 return socket.connect(addrin) >= 0
153 end
154
155 # If socket.end_reached, nothing will happen
156 redef fun write(msg)
157 do
158 if closed then return
159 socket.write(msg.to_s)
160 end
161
162 redef fun write_byte(value)
163 do
164 if closed then return
165 socket.write_byte value
166 end
167
168 redef fun write_bytes(s) do
169 if closed then return
170 socket.write(s.to_s)
171 end
172
173 fun write_ln(msg: Text)
174 do
175 if end_reached then return
176 write(msg.to_s)
177 write("\n")
178 end
179
180 redef fun fill_buffer
181 do
182 _buffer_length = 0
183 _buffer_pos = 0
184 if not connected then return
185 var read = socket.read
186 if read.length == 0 then
187 close
188 end_reached = true
189 end
190 enlarge(_buffer_length + read.length)
191 read.copy_to_native(_buffer, read.length, 0, 0)
192 _buffer_length = read.length
193 end
194
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)
200 _buffer = ns
201 end
202
203 redef fun close
204 do
205 if closed then return
206 if socket.close >= 0 then
207 closed = true
208 end_reached = true
209 end
210 end
211
212 # Send the data present in the socket buffer
213 fun flush
214 do
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
217 closed = true
218 end
219 end
220 end
221
222 # A socket listening on a given `port` for incomming connections
223 #
224 # Create streams to communicate with clients using `accept`.
225 class TCPServer
226 super Socket
227
228 private var addrin: NativeSocketAddrIn is noinit
229
230 # Create and bind a listening server socket on port `port`
231 init
232 do
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
237 closed = true
238 return
239 end
240 addrin = new NativeSocketAddrIn.with_port(port, new NativeSocketAddressFamilies.af_inet)
241 address = addrin.address
242
243 # Bind it
244 closed = not bind
245 end
246
247 # Associates the socket to a local address and port
248 #
249 # Returns whether the socket has been be bound.
250 private fun bind: Bool do
251 return socket.bind(addrin) >= 0
252 end
253
254 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
255 #
256 # Returns `true` if the socket could be set, `false` otherwise
257 fun listen(size: Int): Bool do
258 return socket.listen(size) >= 0
259 end
260
261 # Accepts an incoming connection from a client
262 #
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).
266 #
267 # Require: not closed
268 fun accept: nullable TCPStream
269 do
270 assert not closed
271 var native = socket.accept
272 if native == null then return null
273 return new TCPStream.server_side(native)
274 end
275
276 # Set whether calls to `accept` are blocking
277 fun blocking=(value: Bool)
278 do
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
281 # of the name.
282 socket.non_blocking = not value
283 end
284
285 # Close this socket
286 fun close
287 do
288 # FIXME unify with `SocketStream::close` when we can use qualified names
289
290 if closed then return
291 if socket.close >= 0 then
292 closed = true
293 end
294 end
295 end
296
297 # A simple set of sockets used by `SocketObserver`
298 class SocketSet
299 private var native = new NativeSocketSet
300
301 init do clear
302
303 # Add `socket` to this set
304 fun add(socket: Socket) do native.set(socket.socket)
305
306 # Remove `socket` from this set
307 fun remove(socket: Socket) do native.clear(socket.socket)
308
309 # Does this set has `socket`?
310 fun has(socket: Socket): Bool do return native.is_set(socket.socket)
311
312 # Clear all sockets from this set
313 fun clear do native.zero
314 end
315
316 # Service class to manage calls to `select`
317 class SocketObserver
318 private var native = new NativeSocketObserver
319
320 var read_set: nullable SocketSet = null
321
322 var write_set: nullable SocketSet = null
323
324 var except_set: nullable SocketSet = null
325
326 init(read: Bool, write: Bool, except: Bool)
327 is old_style_init do
328 if read then read_set = new SocketSet
329 if write then write_set = new SocketSet
330 if except then except_set = new SocketSet
331 end
332
333 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
334 do
335 # FIXME this implementation (see the call to nullable attributes below) and
336 # `NativeSockectObserver::select` is not stable.
337
338 var timeval = new NativeTimeval(seconds, microseconds)
339 return native.select(max.socket, read_set.native, write_set.native, except_set.native, timeval) > 0
340 end
341 end