X-Git-Url: http://nitlanguage.org diff --git a/lib/socket/socket.nit b/lib/socket/socket.nit index e295860..681e239 100644 --- a/lib/socket/socket.nit +++ b/lib/socket/socket.nit @@ -17,161 +17,292 @@ # Socket services module socket -import socket_c +private import socket_c +intrude import standard::stream -class Socket - var address: String - var host: nullable String +# 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 - private var socket: FFSocket - 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 + # IPv4 address to which `self` is connected + # + # Formatted as xxx.xxx.xxx.xxx. + var address: String is noinit + + # Is this socket closed? + var closed = false +end + +# Simple communication stream with a remote socket +class TCPStream + super Socket + super BufferedReader + super Writer + super PollableReader + + # Real canonical name of the host to which `self` is connected + var host: String + + private var addrin: NativeSocketAddrIn is noinit + + redef var end_reached = false + + # TODO make init private - init stream_with_host(thost: String, tport: Int) + # Creates a socket connection to host `host` on port `port` + init connect(host: String, port: Int) do - socket = new FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.pf_null ) + _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 - still_alive = false + end_reached = true + closed = true return end - var hostname = socket.gethostbyname(thost) - addrin = new FFSocketAddrIn.with_hostent(hostname, tport) - address = addrin.address - host = hostname.h_name - port = addrin.port - end - - init stream_with_port(tport: Int) - do - 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 + if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then + end_reached = true + closed = true return end - addrin = new FFSocketAddrIn.with(tport, new FFSocketAddressFamilies.af_inet) + var hostname = socket.gethostbyname(host) + addrin = new NativeSocketAddrIn.with_hostent(hostname, port) + address = addrin.address - port = addrin.port - host = null + init(addrin.port, hostname.h_name) + + closed = not internal_connect + end_reached = closed end - init primitive_init(h: FFSocketAcceptResult) + # Creates a client socket, this is meant to be used by accept only + 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) + # 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[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) 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 - 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 + if _buffer_pos < _buffer.length then return true + if end_reached then return false + var events = [new NativeSocketPollValues.pollin] + 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 - var events = new Array[FFSocketPollValues] - events.push(new FFSocketPollValues.pollhup) - events.push(new FFSocketPollValues.pollerr) - return poll_in(events, 0).length == 0 + if closed then return false + var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr] + if pollin(events, 0).length == 0 then + return true + else + closed = 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 internal_connect: Bool + do + assert not closed 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 closed then return + socket.write(msg.to_s) end - fun read: String do - if not still_alive then return "" - return socket.read + fun write_ln(msg: Text) + do + if end_reached then return + write(msg.to_s) + write("\n") end - fun close: Bool do - if not still_alive then return true + 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 closed then return if socket.close >= 0 then - still_alive = false - return true + closed = true + end_reached = true end - return false end - fun bind: Bool do - if not still_alive then return false + # Send the data present in the socket buffer + fun flush + do + if not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or + not socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then + closed = true + end + 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 + if not socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then + closed = true + return + end + addrin = new NativeSocketAddrIn.with_port(port, new NativeSocketAddressFamilies.af_inet) + address = addrin.address + + # Bind it + closed = not bind + end + + # Associates the socket to a local address and port + # + # Returns whether the socket has been be bound. + private fun bind: Bool do 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 fun listen(size: Int): Bool do - if not still_alive then return false return socket.listen(size) >= 0 end - fun accept: Socket do - assert still_alive - return new Socket.primitive_init(socket.accept) + # Accepts an incoming connection from a 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). + # + # 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 + + # Set whether calls to `accept` are blocking + fun blocking=(value: Bool) + do + # We use the opposite from the native version as the native API + # is closer to the C API. In the Nity API, we use a positive version + # of the name. + socket.non_blocking = not value end - fun errno: Int do return socket.errno + # 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 +# A simple set of sockets used by `SocketObserver` class SocketSet - var sset: FFSocketSet - init do sset = new FFSocketSet end - 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 zero do sset.zero end - fun clear(s: Socket) do sset.clear(s.socket) end + private var native = new NativeSocketSet + + init do clear + + # Add `socket` to this set + fun add(socket: Socket) do native.set(socket.socket) + + # Remove `socket` from this set + fun remove(socket: Socket) do native.clear(socket.socket) + + # Does this set has `socket`? + fun has(socket: Socket): Bool do return native.is_set(socket.socket) + + # Clear all sockets from this set + fun clear do native.zero end +# Service class to manage calls to `select` class SocketObserver - private var observer: FFSocketObserver - var readset: nullable SocketSet = null - var writeset: nullable SocketSet = null - var exceptset: nullable SocketSet = null - init(read :Bool, write :Bool, except: Bool) - do - if read then readset = new SocketSet - if write then writeset = new SocketSet - if except then exceptset = new SocketSet - observer = new FFSocketObserver - end - fun select(max: Socket,seconds: Int, microseconds: Int): Bool + private var native = new NativeSocketObserver + + var read_set: nullable SocketSet = null + + var write_set: nullable SocketSet = null + + var except_set: nullable SocketSet = null + + init(read: Bool, write: Bool, except: Bool) + is old_style_init do + if read then read_set = new SocketSet + if write then write_set = new SocketSet + if except then except_set = new SocketSet + end + + fun select(max: Socket, seconds: Int, microseconds: Int): Bool do - var timeval = new FFTimeval(seconds, microseconds) - return observer.select(max.socket, readset.sset, writeset.sset, readset.sset, timeval) > 0 + # FIXME this implementation (see the call to nullable attributes below) and + # `NativeSockectObserver::select` is not stable. + + var timeval = new NativeTimeval(seconds, microseconds) + return native.select(max.socket, read_set.native, write_set.native, except_set.native, timeval) > 0 end end -