lib/socket: distinguish `TCPServer` from `TCPStream`
authorAlexis Laferrière <alexis.laf@xymus.net>
Mon, 22 Dec 2014 02:42:10 +0000 (21:42 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Tue, 23 Dec 2014 15:57:33 +0000 (10:57 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/socket/socket.nit

index efb6fe9..f0aaa85 100644 (file)
@@ -20,79 +20,73 @@ module socket
 import socket_c
 intrude import standard::stream
 
-# Portal for communication between two machines
-class Socket
-       super BufferedIStream
-       super OStream
-       super PollableIStream
+# A general TCP socket, either a `TCPStream` or a `TCPServer`
+abstract class Socket
+
+       # Underlying C socket
+       private var socket: NativeSocket is noinit
+
+       # Port used by the socket
+       var port: Int
 
-       # IPv4 address the socket is connected to
-       # Formatted as xxx.xxx.xxx.xxx
+       # IPv4 address to which `self` is connected
+       #
+       # Formatted as xxx.xxx.xxx.xxx.
        var address: String is noinit
 
-       # Hostname of the socket connected to self
-       # In C : The real canonical host name (e.g. example.org)
-       var host: nullable String = null
+       # Is this socket closed?
+       var closed = false
+end
 
-       # Port open for the socket
-       var port: Int is noinit
+# Simple communication stream with a remote socket
+class TCPStream
+       super Socket
+       super BufferedIStream
+       super OStream
+       super PollableIStream
 
-       # Underlying C socket
-       private var socket: NativeSocket is noinit
+       # Real canonical name of the host to which `self` is connected
+       var host: String
 
-       # Underlying C socket
        private var addrin: NativeSocketAddrIn is noinit
 
        redef var end_reached = false
 
-       # 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 NativeSocket.socket( new NativeSocketAddressFamilies.af_inet, new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null )
-               if socket.address_is_null then
-                       end_reached = true
-                       return
-               end
-               socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
-               var hostname = socket.gethostbyname(thost)
-               addrin = new NativeSocketAddrIn.with_hostent(hostname, tport)
-               address = addrin.address
-               host = hostname.h_name
-               port = addrin.port
-               if not end_reached then end_reached = not connect
-       end
+       # TODO make init private
 
-       # Creates a server socket on port `tport`, with a connection queue of size `max`
-       init server(tport: Int, max: Int)
+       # Creates a socket connection to host `host` on port `port`
+       init connect(host: String, port: Int)
        do
                _buffer = new FlatBuffer
                _buffer_pos = 0
-               socket = new NativeSocket.socket( new NativeSocketAddressFamilies.af_inet, new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null )
+               socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
+                       new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
                if socket.address_is_null then
                        end_reached = true
+                       closed = true
                        return
                end
                socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
-               addrin = new NativeSocketAddrIn.with(tport, new NativeSocketAddressFamilies.af_inet)
+               var hostname = socket.gethostbyname(host)
+               addrin = new NativeSocketAddrIn.with_hostent(hostname, port)
+
                address = addrin.address
-               port = addrin.port
-               host = null
-               bind
-               listen(max)
+               init(addrin.port, hostname.h_name)
+
+               closed = not internal_connect
+               end_reached = closed
        end
 
        # Creates a client socket, this is meant to be used by accept only
-       private init primitive_init(h: NativeSocketAcceptResult)
+       private init server_side(h: SocketAcceptResult)
        do
                _buffer = new FlatBuffer
                _buffer_pos = 0
                socket = h.socket
-               addrin = h.addrIn
+               addrin = h.addr_in
                address = addrin.address
-               port = addrin.port
-               host = null
+
+               init(addrin.port, address)
        end
 
        redef fun poll_in do return ready_to_read(0)
@@ -102,7 +96,6 @@ class Socket
        # event_types : Combination of several event types to watch
        #
        # timeout : Time in milliseconds before stopping listening for events on this socket
-       #
        private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
                if end_reached then return new Array[NativeSocketPollValues]
                return socket.socket_poll(new PollFD(socket.descriptor, event_types), timeout)
@@ -111,28 +104,23 @@ class Socket
        # 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 _buffer_pos < _buffer.length then return true
                if eof then return false
-               var events = new Array[NativeSocketPollValues]
-               events.push(new NativeSocketPollValues.pollin)
+               var events = [new NativeSocketPollValues.pollin]
                return pollin(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[NativeSocketPollValues]
-               events.push(new NativeSocketPollValues.pollhup)
-               events.push(new NativeSocketPollValues.pollerr)
+               if closed then return false
+               var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr]
                if pollin(events, 0).length == 0 then
                        return true
                else
-                       end_reached = true
+                       closed = true
                        return false
                end
        end
@@ -142,16 +130,16 @@ class Socket
        # Establishes a connection to socket addrin
        #
        # REQUIRES : not self.end_reached
-       private fun connect: Bool
+       private fun internal_connect: Bool
        do
-               assert not end_reached
+               assert not closed
                return socket.connect(addrin) >= 0
        end
 
        # If socket.end_reached, nothing will happen
        redef fun write(msg: Text)
        do
-               if end_reached then return
+               if closed then return
                socket.write(msg.to_s)
        end
 
@@ -175,48 +163,84 @@ class Socket
                _buffer.append(read)
        end
 
-       redef fun close do
-               if end_reached then return
+       redef fun close
+       do
+               if closed then return
                if socket.close >= 0 then
-                       end_reached = true
+                       closed = true
                end
        end
 
+# A socket listening on a given `port` for incomming connections
+#
+# Create streams to communicate with clients using `accept`.
+class TCPServer
+       super Socket
+
+       private var addrin: NativeSocketAddrIn is noinit
+
+       # Create and bind a listening server socket on port `port`
+       init
+       do
+               socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
+                       new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
+               assert not socket.address_is_null
+               socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
+               addrin = new NativeSocketAddrIn.with(port, new NativeSocketAddressFamilies.af_inet)
+               address = addrin.address
+
+               # Bind it
+               closed = not bind
+       end
+
        # Associates the socket to a local address and port
        #
-       # Returns : `true` if the socket could be bound, `false` otherwise
+       # Returns whether the socket has been be bound.
        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
+       # Returns `true` if the socket could be set, `false` otherwise
+       fun listen(size: Int): Bool do
                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
+       # Create and return a new socket to the client. May return null if not
+       # `blocking` and there's no waiting clients, or upon an interruption
+       # (whether `blocking` or not).
        #
-       # REQUIRES : not self.end_reached
-       fun accept: Socket do
-               assert not end_reached
-               return new Socket.primitive_init(socket.accept)
+       # Require: not closed
+       fun accept: nullable TCPStream
+       do
+               assert not closed
+               var native = socket.accept
+               if native == null then return null
+               return new TCPStream.server_side(native)
        end
 
+
+       # Close this socket
+       fun close
+       do
+               # FIXME unify with `SocketStream::close` when we can use qualified names
+
+               if closed then return
+               if socket.close >= 0 then
+                       closed = true
+               end
+       end
 end
 
 class SocketSet
        var sset = new NativeSocketSet
-       fun set(s: Socket) do sset.set(s.socket) end
-       fun is_set(s: Socket): Bool do return sset.is_set(s.socket) end
+       fun set(s: TCPSocket) do sset.set(s.socket) end
+       fun is_set(s: TCPSocket): Bool do return sset.is_set(s.socket) end
        fun zero do sset.zero end
-       fun clear(s: Socket) do sset.clear(s.socket) end
+       fun clear(s: TCPSocket) do sset.clear(s.socket) end
 end
 
 class SocketObserver
@@ -230,8 +254,8 @@ class SocketObserver
                if write then writeset = new SocketSet
                if except then exceptset = new SocketSet
                observer = new NativeSocketObserver
-       end     
-       fun select(max: Socket,seconds: Int, microseconds: Int): Bool
+       end
+       fun select(max: TCPSocket, seconds: Int, microseconds: Int): Bool
        do
                var timeval = new NativeTimeval(seconds, microseconds)
                return observer.select(max.socket, readset.sset, writeset.sset, readset.sset, timeval) > 0