9824111758d4ad532b44616458e27b5efb812ccd
[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
22 # Portal for communication between two machines
23 class Socket
24 super BufferedIStream
25 super OStream
26
27 # IPv4 address the socket is connected to
28 # Formatted as xxx.xxx.xxx.xxx
29 var address: String
30
31 # Hostname of the socket connected to self
32 # In C : The real canonical host name (e.g. example.org)
33 var host: nullable String
34
35 # Port open for the socket
36 var port: Int
37
38 # Underlying C socket
39 private var socket: FFSocket
40
41 # Underlying C socket
42 private var addrin: FFSocketAddrIn
43
44 redef var end_reached = false
45
46 # Creates a socket connection to host `thost` on port `port`
47 init client(thost: String, tport: Int)
48 do
49 _buffer = new FlatBuffer
50 _buffer_pos = 0
51 socket = new FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.pf_null )
52 if socket.address_is_null then
53 end_reached = true
54 return
55 end
56 socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
57 var hostname = socket.gethostbyname(thost)
58 addrin = new FFSocketAddrIn.with_hostent(hostname, tport)
59 address = addrin.address
60 host = hostname.h_name
61 port = addrin.port
62 if not end_reached then end_reached = not connect
63 end
64
65 # Creates a server socket on port `tport`, with a connection queue of size `max`
66 init server(tport: Int, max: Int)
67 do
68 _buffer = new FlatBuffer
69 _buffer_pos = 0
70 socket = new FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.pf_null )
71 if socket.address_is_null then
72 end_reached = true
73 return
74 end
75 socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
76 addrin = new FFSocketAddrIn.with(tport, new FFSocketAddressFamilies.af_inet)
77 address = addrin.address
78 port = addrin.port
79 host = null
80 bind
81 listen(max)
82 end
83
84 # Creates a client socket, this is meant to be used by accept only
85 private init primitive_init(h: FFSocketAcceptResult)
86 do
87 _buffer = new FlatBuffer
88 _buffer_pos = 0
89 socket = h.socket
90 addrin = h.addrIn
91 address = addrin.address
92 port = addrin.port
93 host = null
94 end
95
96 # Returns an array containing an enum of the events ready to be read
97 #
98 # event_types : Combination of several event types to watch
99 #
100 # timeout : Time in milliseconds before stopping listening for events on this socket
101 #
102 private fun poll_in(event_types: Array[FFSocketPollValues], timeout: Int): Array[FFSocketPollValues] do
103 if not still_alive then return new Array[FFSocketPollValues]
104 return socket.socket_poll(new PollFD(socket.descriptor, event_types), timeout)
105 end
106
107 # Easier use of poll_in to check for something to read on all channels of any priority
108 #
109 # timeout : Time in milliseconds before stopping to wait for events
110 #
111 fun ready_to_read(timeout: Int): Bool
112 do
113 if _buffer_pos < _buffer.length then return true
114 if eof then return false
115 var events = new Array[FFSocketPollValues]
116 events.push(new FFSocketPollValues.pollin)
117 return poll_in(events, timeout).length != 0
118 end
119
120 # Checks if the socket still is connected
121 #
122 fun connected: Bool
123 do
124 if eof then return false
125 var events = new Array[FFSocketPollValues]
126 events.push(new FFSocketPollValues.pollhup)
127 events.push(new FFSocketPollValues.pollerr)
128 return poll_in(events, 0).length == 0
129 end
130
131 redef fun is_writable do return not end_reached
132
133 # Establishes a connection to socket addrin
134 #
135 # REQUIRES : not self.end_reached
136 private fun connect: Bool
137 do
138 assert not end_reached
139 return socket.connect(addrin) >= 0
140 end
141
142 # If socket.end_reached, nothing will happen
143 redef fun write(msg: Text)
144 do
145 if end_reached then return
146 socket.write(msg.to_s)
147 end
148
149 fun write_ln(msg: Text)
150 do
151 if end_reached then return
152 write(msg.to_s)
153 write("\n")
154 end
155
156 redef fun fill_buffer
157 do
158 _buffer.clear
159 _buffer_pos = 0
160 if not connected then return
161 var read = socket.read
162 if read.length == 0 then
163 close
164 end_reached = true
165 end
166 _buffer.append(read)
167 end
168
169 redef fun close do
170 if end_reached then return
171 if socket.close >= 0 then
172 end_reached = true
173 end
174 end
175
176 # Associates the socket to a local address and port
177 #
178 # Returns : `true` if the socket could be bound, `false` otherwise
179 private fun bind: Bool do
180 if end_reached then return false
181 return socket.bind(addrin) >= 0
182 end
183
184 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
185 #
186 # Returns : `true` if the socket could be set, `false` otherwise
187 private fun listen(size: Int): Bool do
188 if end_reached then return false
189 return socket.listen(size) >= 0
190 end
191
192 # Accepts an incoming connection from a client
193 # This creates a new socket that represents the connection to a client
194 #
195 # Returns : the socket for communication with the client
196 #
197 # REQUIRES : not self.end_reached
198 fun accept: Socket do
199 assert not end_reached
200 return new Socket.primitive_init(socket.accept)
201 end
202
203 end
204
205 class SocketSet
206 var sset: FFSocketSet
207 init do sset = new FFSocketSet end
208 fun set(s: Socket) do sset.set(s.socket) end
209 fun is_set(s: Socket): Bool do return sset.is_set(s.socket) end
210 fun zero do sset.zero end
211 fun clear(s: Socket) do sset.clear(s.socket) end
212 end
213
214 class SocketObserver
215 private var observer: FFSocketObserver
216 var readset: nullable SocketSet = null
217 var writeset: nullable SocketSet = null
218 var exceptset: nullable SocketSet = null
219 init(read :Bool, write :Bool, except: Bool)
220 do
221 if read then readset = new SocketSet
222 if write then writeset = new SocketSet
223 if except then exceptset = new SocketSet
224 observer = new FFSocketObserver
225 end
226 fun select(max: Socket,seconds: Int, microseconds: Int): Bool
227 do
228 var timeval = new FFTimeval(seconds, microseconds)
229 return observer.select(max.socket, readset.sset, writeset.sset, readset.sset, timeval) > 0
230 end
231 end
232