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