lib/socket: Socket now subclass of BufferedIStream and OStream
[nit.git] / lib / socket / socket.nit
index d5f203a..4a5129e 100644 (file)
@@ -19,34 +19,73 @@ module socket
 
 import socket_c
 
+# Portal for communication between two machines
 class Socket
+       super BufferedIStream
+       super OStream
+
+       # IPv4 address the socket is connected to
+       # Formatted as xxx.xxx.xxx.xxx
        var address: String
+
+       # Hostname of the socket connected to self
+       # In C : The real canonical host name (e.g. example.org)
        var host: nullable String
+
+       # Port open for the socket
        var port: Int
+
+       # Underlying C socket
        private var socket: FFSocket
+
+       # Underlying C socket
        private var addrin: FFSocketAddrIn
 
+       redef var end_reached = false
+
+       # Creates a socket connection to host `thost` on port `port`
        init stream_with_host(thost: String, tport: Int)
        do
+               _buffer = new FlatBuffer
+               _buffer_pos = 0
                socket = new FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.pf_null )
+               if socket.address_is_null then
+                       end_reached = true
+                       return
+               end
+               socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
                var hostname = socket.gethostbyname(thost)
                addrin = new FFSocketAddrIn.with_hostent(hostname, tport)
                address = addrin.address
                host = hostname.h_name
                port = addrin.port
+               if not end_reached then end_reached = not connect
        end
 
+       # Creates a server socket on port `tport`
        init stream_with_port(tport: Int)
        do
+               _buffer = new FlatBuffer
+               _buffer_pos = 0
                socket = new FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.pf_null )
+               if socket.address_is_null then
+                       end_reached = true
+                       return
+               end
+               socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
                addrin = new FFSocketAddrIn.with(tport, new FFSocketAddressFamilies.af_inet)
                address = addrin.address
                port = addrin.port
                host = null
+               bind
+               listen(max)
        end
 
-       init primitive_init(h: FFSocketAcceptResult)
+       # Creates a client socket, this is meant to be used by accept only
+       private init primitive_init(h: FFSocketAcceptResult)
        do
+               _buffer = new FlatBuffer
+               _buffer_pos = 0
                socket = h.socket
                addrin = h.addrIn
                address = addrin.address
@@ -54,14 +93,113 @@ class Socket
                host = null
        end
 
-       fun connect: Bool do return socket.connect(addrin) >= 0
-       fun write(msg: String): Bool do return socket.write(msg) >= 0
-       fun read: String do return socket.read
-       fun close: Bool do return socket.close >= 0
-       fun bind: Bool do return socket.bind(addrin) >= 0
-       fun listen(size: Int): Bool do return socket.listen(size) >= 0
-       fun accept: Socket do return new Socket.primitive_init(socket.accept)
-       fun errno: Int do return socket.errno
+       # Returns an array containing an enum of the events ready to be read
+       #
+       # event_types : Combination of several event types to watch
+       #
+       # timeout : Time in milliseconds before stopping listening for events on this socket
+       #
+       private fun poll_in(event_types: Array[FFSocketPollValues], timeout: Int): Array[FFSocketPollValues] do
+               if not still_alive then return new Array[FFSocketPollValues]
+               return socket.socket_poll(new PollFD(socket.descriptor, event_types), timeout)
+       end
+
+       # Easier use of poll_in to check for something to read on all channels of any priority
+       #
+       # timeout : Time in milliseconds before stopping to wait for events
+       #
+       fun ready_to_read(timeout: Int): Bool
+       do
+               if _buffer_pos < _buffer.length then return true
+               if eof then return false
+               var events = new Array[FFSocketPollValues]
+               events.push(new FFSocketPollValues.pollin)
+               return poll_in(events, timeout).length != 0
+       end
+
+       # Checks if the socket still is connected
+       #
+       fun connected: Bool
+       do
+               if eof then return false
+               var events = new Array[FFSocketPollValues]
+               events.push(new FFSocketPollValues.pollhup)
+               events.push(new FFSocketPollValues.pollerr)
+               return poll_in(events, 0).length == 0
+       end
+
+       redef fun is_writable do return not end_reached
+
+       # Establishes a connection to socket addrin
+       #
+       # REQUIRES : not self.end_reached
+       private fun connect: Bool
+       do
+               assert not end_reached
+               return socket.connect(addrin) >= 0
+       end
+
+       # If socket.end_reached, nothing will happen
+       redef fun write(msg: Text)
+       do
+               if end_reached then return
+               socket.write(msg.to_s)
+       end
+
+       fun write_ln(msg: Text)
+       do
+               if end_reached then return
+               write(msg.to_s)
+               write("\n")
+       end
+
+       redef fun fill_buffer
+       do
+               _buffer.clear
+               _buffer_pos = 0
+               if not connected then return
+               var read = socket.read
+               if read.length == 0 then
+                       close
+                       end_reached = true
+               end
+               _buffer.append(read)
+       end
+
+       redef fun close do
+               if end_reached then return
+               if socket.close >= 0 then
+                       end_reached = true
+               end
+       end
+
+       # Associates the socket to a local address and port
+       #
+       # Returns : `true` if the socket could be bound, `false` otherwise
+       private fun bind: Bool do
+               if end_reached then return false
+               return socket.bind(addrin) >= 0
+       end
+
+       # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
+       #
+       # Returns : `true` if the socket could be set, `false` otherwise
+       private fun listen(size: Int): Bool do
+               if end_reached then return false
+               return socket.listen(size) >= 0
+       end
+
+       # Accepts an incoming connection from a client
+       # This creates a new socket that represents the connection to a client
+       #
+       # Returns : the socket for communication with the client
+       #
+       # REQUIRES : not self.end_reached
+       fun accept: Socket do
+               assert not end_reached
+               return new Socket.primitive_init(socket.accept)
+       end
+
 end
 
 class SocketSet