lib/socket: Socket now subclass of Pollable interface
[nit.git] / lib / socket / socket.nit
index 52094cc..8c96d17 100644 (file)
@@ -19,24 +19,39 @@ module socket
 
 import socket_c
 
+# Portal for communication between two machines
 class Socket
+       super BufferedIStream
+       super OStream
+       super PollableIStream
+
+       # 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
 
-       # Guard for errors
-       # If the socket could not be created or if the socket was destroyed
-       # before a call needing the socket was made
-       # this flag will be set to false.
-       var still_alive = true # Note : HUGE SUCCESS
+       redef var end_reached = false
 
-       init stream_with_host(thost: String, tport: Int)
+       # Creates a socket connection to host `thost` on port `port`
+       init client(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
-                       still_alive = false
+                       end_reached = true
                        return
                end
                socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
@@ -45,13 +60,17 @@ class Socket
                address = addrin.address
                host = hostname.h_name
                port = addrin.port
+               if not end_reached then end_reached = not connect
        end
 
-       init stream_with_port(tport: Int)
+       # Creates a server socket on port `tport`, with a connection queue of size `max`
+       init server(tport: Int, max: 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
-                       still_alive = false
+                       end_reached = true
                        return
                end
                socket.setsockopt(new FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
@@ -59,10 +78,15 @@ class Socket
                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
@@ -70,79 +94,117 @@ class Socket
                host = null
        end
 
+       redef fun poll_in do return ready_to_read(0)
+
        # 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]
+       private fun pollin(event_types: Array[FFSocketPollValues], timeout: Int): Array[FFSocketPollValues] do
+               if end_reached 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
+       # Easier use of pollin 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 not still_alive then return false
+               if _buffer_pos < _buffer.length then return true
+               if eof then return false
                var events = new Array[FFSocketPollValues]
                events.push(new FFSocketPollValues.pollin)
-               events.push(new FFSocketPollValues.pollrdnorm)
-               events.push(new FFSocketPollValues.pollpri)
-               events.push(new FFSocketPollValues.pollrdband)
-               return poll_in(events, timeout).length != 0
+               return pollin(events, timeout).length != 0
        end
 
        # Checks if the socket still is connected
        #
        fun connected: Bool
        do
-               if not still_alive then return false
+               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
+               if pollin(events, 0).length == 0 then
+                       return true
+               else
+                       end_reached = true
+                       return false
+               end
        end
 
-       fun connect: Bool do
-               assert still_alive
+       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
 
-       fun write(msg: String): Bool do
-               if not still_alive then return false
-               return socket.write(msg) >= 0
+       # 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
 
-       fun read: String do
-               if not still_alive then return ""
-               return socket.read
+       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
 
-       fun close: Bool do
-               if not still_alive then return true
+       redef fun close do
+               if end_reached then return
                if socket.close >= 0 then
-                       still_alive = false
-                       return true
+                       end_reached = true
                end
-               return false
        end
 
-       fun bind: Bool do
-               if not still_alive then return false
+       # 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
 
-       fun listen(size: Int): Bool do
-               if not still_alive then return false
+       # 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 still_alive
+               assert not end_reached
                return new Socket.primitive_init(socket.accept)
        end