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