Merge: lib/socket: fix, update style and intro features
authorJean Privat <jean@pryen.org>
Wed, 24 Dec 2014 01:22:16 +0000 (20:22 -0500)
committerJean Privat <jean@pryen.org>
Wed, 24 Dec 2014 01:22:16 +0000 (20:22 -0500)
This is a pretty big update to the `socket` module. It appeared to be broken. Did someone test it since the last modifications to streams and read_line?

* Update and cleanup the style in the first 3 or 4 commits.
* Fix (some of) what was broken.
* Revamp the API with a distinction between listening sockets and stream sockets (making it closer to Ruby and C#). This is the most important commit "lib/socket: distinguish `TCPServer` from `TCPStream`".
* Intro additionnal features used by Mineit: non-blocking `TCPServer` sockets and `TCPStream::flush`.

Still to do:
* Add `Error` support to the API.
* Rename `socket_c` to `native_socket`.
* Fix `SocketObserver` and underlying C implementation.

Pull-Request: #1063
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

lib/mpd.nit
lib/socket/examples/socket_client.nit
lib/socket/examples/socket_server.nit
lib/socket/examples/socket_simple_server.nit [new file with mode: 0644]
lib/socket/socket.nit
lib/socket/socket_c.nit
lib/websocket/examples/websocket_server.nit
lib/websocket/websocket.nit
src/interpreter/debugger_socket.nit
src/nitdbg_client.nit
tests/sav/socket_simple_server.res [new file with mode: 0644]

index 97c38f2..e814b56 100644 (file)
@@ -23,7 +23,7 @@ import socket
 class MPDConnection
 
        # Socket connection to server.
-       var socket: nullable Socket = null
+       var socket: nullable TCPStream = null
 
        # Server hostname.
        var host: String
@@ -40,9 +40,10 @@ class MPDConnection
        # Connect to the MPD server
        fun connect
        do
-               var p: nullable Socket = null
+               var p: nullable TCPStream = null
 
-               p = new Socket.client(host, port)
+               p = new TCPStream.connect(host, port)
+               assert p.connected
 
                sys.nanosleep(0,5000)
 
index b488028..3f38ab4 100644 (file)
@@ -21,18 +21,23 @@ import socket
 
 if args.length < 2 then
        print "Usage : socket_client <host> <port>"
-       return
+       exit 1
 end
 
-var s = new Socket.client(args[0], args[1].to_i)
-print "[HOST ADDRESS] : {s.address}"
-print "[HOST] : {s.host or else "unamed"}"
-print "[PORT] : {s.port}"
-print "Connecting ... {s.connected}"
+var address = args[0]
+var port = args[1].to_i
+
+# Open a conection with the server
+var s = new TCPStream.connect(address, port)
+printn "Connecting to {s.host}:{s.port} at {s.address}... "
+print if s.connected then "Connected" else "Connection failed"
+
 if s.connected then
-       print "Writing ... Hello server !"
-       s.write("Hello server !")
-       print "[Response from server] : {s.read(100)}"
-       print "Closing ..."
+       # Communicate
+       s.write "Hello server!\n"
+       print s.read_line
+       s.write "Bye server!\n"
+       print s.read_line
+
        s.close
 end
index c435320..17bfcab 100644 (file)
@@ -24,27 +24,31 @@ if args.is_empty then
        return
 end
 
-var socket = new Socket.server(args[0].to_i, 1)
+var socket = new TCPServer(args[0].to_i)
+socket.listen 1
 print "[PORT] : {socket.port.to_s}"
 
-var clients = new Array[Socket]
+var clients = new Array[TCPStream]
 var max = socket
 loop
        var fs = new SocketObserver(true, true, true)
-       fs.readset.set(socket)
+       fs.read_set.add socket
 
-       for c in clients do fs.readset.set(c)
+       for c in clients do fs.read_set.add c
 
+       printn "."
        if fs.select(max, 4, 0) == 0 then
                print "Error occured in select {sys.errno.strerror}"
                break
        end
 
-       if fs.readset.is_set(socket) then
+       if fs.read_set.has(socket) then
                var ns = socket.accept
                print "Accepting {ns.address} ... "
-               print "[Message from {ns.address}] : {ns.read(100)}"
-               ns.write("Goodbye client.")
+               print "[Message from {ns.address}] : {ns.read_line}"
+               ns.write "Hello client.\n"
+               print "[Message from {ns.address}] : {ns.read_line}"
+               ns.write "Bye client.\n"
                print "Closing {ns.address} ..."
                ns.close
        end
diff --git a/lib/socket/examples/socket_simple_server.nit b/lib/socket/examples/socket_simple_server.nit
new file mode 100644 (file)
index 0000000..54f9ea3
--- /dev/null
@@ -0,0 +1,50 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Simple server example using a non-blocking `TCPServer`
+module socket_simple_server
+
+import socket
+
+if args.is_empty then
+       print "Usage : socket_simple_server <port>"
+       exit 1
+end
+
+var port = args[0].to_i
+
+# Open the listening socket
+var socket = new TCPServer(port)
+socket.listen 4
+socket.blocking = false
+
+print "Listening on port {socket.port}"
+
+# Loop until a single client connects
+var client: nullable TCPStream = null
+while client == null do
+       printn "."
+       sys.nanosleep(0, 200000)
+
+       client = socket.accept
+end
+print " Connected"
+
+# Communicate
+print client.read_line
+client.write "Hello client!\n"
+print client.read_line
+client.write "Bye client!\n"
+
+client.close
index aad6449..26d9937 100644 (file)
 # Socket services
 module socket
 
-import socket_c
+private 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
 
-       # IPv4 address the socket is connected to
-       # Formatted as xxx.xxx.xxx.xxx
+       # Port used by the socket
+       var port: Int
+
+       # 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: FFSocket is noinit
+       # Real canonical name of the host to which `self` is connected
+       var host: String
 
-       # Underlying C socket
-       private var addrin: FFSocketAddrIn is noinit
+       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 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
+       # 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 FFSocket.socket( new FFSocketAddressFamilies.af_inet, new FFSocketTypes.sock_stream, new FFSocketProtocolFamilies.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 FFSocketOptLevels.socket, new FFSocketOptNames.reuseaddr, 1)
-               addrin = new FFSocketAddrIn.with(tport, new FFSocketAddressFamilies.af_inet)
+               socket.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1)
+               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: FFSocketAcceptResult)
+       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,37 +96,31 @@ 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[FFSocketPollValues], timeout: Int): Array[FFSocketPollValues] do
-               if end_reached 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 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[FFSocketPollValues]
-               events.push(new FFSocketPollValues.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[FFSocketPollValues]
-               events.push(new FFSocketPollValues.pollhup)
-               events.push(new FFSocketPollValues.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,66 +163,136 @@ 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
 
+       # Send the data present in the socket buffer
+       fun flush
+       do
+               socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1)
+               socket.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0)
+       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
 
+       # 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
+
+       # 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 = new FFSocketSet
-       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
-
index 241422c..71867b9 100644 (file)
@@ -28,28 +28,23 @@ in "C Header" `{
        #include <arpa/inet.h>
        #include <netdb.h>
        #include <sys/poll.h>
+`}
 
-       typedef int S_DESCRIPTOR;
-       typedef struct sockaddr_in S_ADDR_IN;
-       typedef struct sockaddr S_ADDR;
-       typedef struct in_addr S_IN_ADDR;
-       typedef struct hostent S_HOSTENT;
-       typedef struct timeval S_TIMEVAL;
-       typedef struct sockaccept_result { S_ADDR_IN addr_in; S_DESCRIPTOR s_desc; } S_ACCEPT_RESULT;
-       typedef fd_set S_FD_SET;
-       typedef socklen_t S_LEN;
+in "C" `{
+       #include <fcntl.h>
+       #include <netinet/tcp.h>
 `}
 
 # Wrapper for the data structure PollFD used for polling on a socket
 class PollFD
 
        # The PollFD object
-       private var poll_struct: FFSocketPollFD
+       private var poll_struct: NativeSocketPollFD
 
        # A collection of the events to be watched
-       var events: Array[FFSocketPollValues]
+       var events: Array[NativeSocketPollValues]
 
-       init(pid: Int, events: Array[FFSocketPollValues])
+       init(pid: Int, events: Array[NativeSocketPollValues])
        do
                assert events.length >= 1
                self.events = events
@@ -60,13 +55,13 @@ class PollFD
                        events_in_one += events[i]
                end
 
-               self.poll_struct = new FFSocketPollFD(pid, events_in_one)
+               self.poll_struct = new NativeSocketPollFD(pid, events_in_one)
        end
 
        # Reads the response and returns an array with the type of events that have been found
-       private fun check_response(response: Int): Array[FFSocketPollValues]
+       private fun check_response(response: Int): Array[NativeSocketPollValues]
        do
-               var resp_array = new Array[FFSocketPollValues]
+               var resp_array = new Array[NativeSocketPollValues]
                for i in events do
                        if c_check_resp(response, i) != 0 then
                                resp_array.push(i)
@@ -76,7 +71,7 @@ class PollFD
        end
 
        # Checks if the poll call has returned true for a particular type of event
-       private fun c_check_resp(response: Int, mask: FFSocketPollValues): Int
+       private fun c_check_resp(response: Int, mask: NativeSocketPollValues): Int
        `{
                return response & mask;
        `}
@@ -84,15 +79,18 @@ class PollFD
 end
 
 # Data structure used by the poll function
-private extern class FFSocketPollFD `{ struct pollfd `}
+private extern class NativeSocketPollFD `{ struct pollfd `}
+
        # File descriptor id
        private fun fd: Int `{ return recv.fd; `}
+
        # List of events to be watched
        private fun events: Int `{ return recv.events; `}
+
        # List of events received by the last poll function
        private fun revents: Int `{  return recv.revents; `}
 
-       new (pid: Int, events: FFSocketPollValues) `{
+       new (pid: Int, events: NativeSocketPollValues) `{
                struct pollfd poll;
                poll.fd = pid;
                poll.events = events;
@@ -101,24 +99,34 @@ private extern class FFSocketPollFD `{ struct pollfd `}
 
 end
 
-extern class FFSocket `{ S_DESCRIPTOR* `}
-       new socket(domain: FFSocketAddressFamilies, socketType: FFSocketTypes, protocol: FFSocketProtocolFamilies) `{
-               S_DESCRIPTOR *d = NULL; d = (S_DESCRIPTOR*) malloc( sizeof(S_DESCRIPTOR) );
+extern class NativeSocket `{ int* `}
+
+       new socket(domain: NativeSocketAddressFamilies, socketType: NativeSocketTypes, protocol: NativeSocketProtocolFamilies) `{
                int ds = socket(domain, socketType, protocol);
                if(ds == -1){
-                       free(d);
                        return NULL;
                }
+               int *d = malloc(sizeof(int));
                memcpy(d, &ds, sizeof(ds));
                return d;
        `}
+
        fun destroy `{ free(recv); `}
-       fun close: Int `{ return close( *recv ); `}
+
+       fun close: Int `{ return close(*recv); `}
+
        fun descriptor: Int `{ return *recv; `}
 
-       fun gethostbyname(n: String): FFSocketHostent import String.to_cstring `{ return gethostbyname(String_to_cstring(n)); `}
-       fun connect(addrIn: FFSocketAddrIn): Int `{ return connect( *recv, (S_ADDR*)addrIn, sizeof(*addrIn) ); `}
-       fun write(buffer: String): Int import String.to_cstring, String.length `{ return write(*recv, (char*)String_to_cstring(buffer), String_length(buffer)); `}
+       fun gethostbyname(n: String): NativeSocketHostent import String.to_cstring `{ return gethostbyname(String_to_cstring(n)); `}
+
+       fun connect(addrIn: NativeSocketAddrIn): Int `{
+               return connect(*recv, (struct sockaddr*)addrIn, sizeof(*addrIn));
+       `}
+
+       fun write(buffer: String): Int
+       import String.to_cstring, String.length `{
+               return write(*recv, (char*)String_to_cstring(buffer), String_length(buffer));
+       `}
 
        fun read: String import NativeString.to_s_with_length `{
                static char c[1024];
@@ -133,31 +141,32 @@ extern class FFSocket `{ S_DESCRIPTOR* `}
        `}
 
        # Sets an option for the socket
-       fun setsockopt(level: FFSocketOptLevels, option_name: FFSocketOptNames, option_value: Int) `{
+       fun setsockopt(level: NativeSocketOptLevels, option_name: NativeSocketOptNames, option_value: Int) `{
                int err = setsockopt(*recv, level, option_name, &option_value, sizeof(int));
                if(err != 0){
-                       perror("Error on setsockopts : ");
+                       perror("Error on setsockopts: ");
                        exit(1);
                }
        `}
 
-       fun bind(addrIn: FFSocketAddrIn): Int `{ return bind(*recv, (S_ADDR*)addrIn, sizeof(*addrIn)); `}
+       fun bind(addrIn: NativeSocketAddrIn): Int `{ return bind(*recv, (struct sockaddr*)addrIn, sizeof(*addrIn)); `}
+
        fun listen(size: Int): Int `{ return listen(*recv, size); `}
 
        # Checks if the buffer is ready for any event specified when creating the pollfd structure
-       fun socket_poll(filedesc: PollFD, timeout: Int): Array[FFSocketPollValues]
+       fun socket_poll(filedesc: PollFD, timeout: Int): Array[NativeSocketPollValues]
        do
-               var result = i_poll(filedesc.poll_struct, timeout)
+               var result = native_poll(filedesc.poll_struct, timeout)
                assert result != -1
                return filedesc.check_response(result)
        end
 
        # Call to the poll function of the C socket
        #
-       # Signature :
+       # Signature:
        # int poll(struct pollfd fds[], nfds_t nfds, int timeout);
        #
-       # Official documentation of the poll function :
+       # Official documentation of the poll function:
        #
        # The poll() function provides applications with a mechanism for multiplexing input/output over a set of file descriptors.
        # For each member of the array pointed to by fds, poll() shall examine the given file descriptor for the event(s) specified in events.
@@ -167,147 +176,208 @@ extern class FFSocket `{ S_DESCRIPTOR* `}
        # It is a pointer to an array with one member for each open file descriptor of interest.
        # The array's members are pollfd structures within which fd specifies an open file descriptor and events and revents are bitmasks constructed by
        # OR'ing a combination of the pollfd flags.
-       private fun i_poll(filedesc: FFSocketPollFD, timeout: Int): Int `{
+       private fun native_poll(filedesc: NativeSocketPollFD, timeout: Int): Int `{
                int poll_return = poll(&filedesc, 1, timeout);
                return poll_return;
        `}
 
-       private fun i_accept(addrIn: FFSocketAddrIn): FFSocket `{
-               S_LEN s = sizeof(S_ADDR);
-               S_DESCRIPTOR *d = NULL;
-               d = malloc(sizeof(S_DESCRIPTOR));
-               *d = accept(*recv,(S_ADDR*)addrIn, &s);
-               return d;
+       private fun native_accept(addr_in: NativeSocketAddrIn): NativeSocket `{
+               socklen_t s = sizeof(struct sockaddr);
+               int socket = accept(*recv, (struct sockaddr*)addr_in, &s);
+               if (socket == -1) return NULL;
+
+               int *ptr = malloc(sizeof(int));
+               *ptr = socket;
+               return ptr;
        `}
-       fun accept: FFSocketAcceptResult
+
+       fun accept: nullable SocketAcceptResult
        do
-               var addrIn = new FFSocketAddrIn
-               var s = i_accept(addrIn)
-               return new FFSocketAcceptResult(s, addrIn)
+               var addrIn = new NativeSocketAddrIn
+               var s = native_accept(addrIn)
+               if s.address_is_null then return null
+               return new SocketAcceptResult(s, addrIn)
        end
-end
 
-extern class FFSocketAcceptResult `{ S_ACCEPT_RESULT* `}
-       new (socket: FFSocket, addrIn: FFSocketAddrIn) `{
-               S_ACCEPT_RESULT *sar = NULL;
-               sar = malloc( sizeof(S_ACCEPT_RESULT) );
-               sar->s_desc = *socket;
-               sar->addr_in = *addrIn;
-               return sar;
+       # Set wether this socket is non blocking
+       fun non_blocking=(value: Bool) `{
+               int flags = fcntl(*recv, F_GETFL, 0);
+               if (flags == -1) flags = 0;
+
+               if (value) {
+                       flags = flags | O_NONBLOCK;
+               } else if (flags & O_NONBLOCK) {
+                       flags = flags - O_NONBLOCK;
+               } else {
+                       return;
+               }
+               fcntl(*recv, F_SETFL, flags);
        `}
-       fun socket: FFSocket `{ return &recv->s_desc; `}
-       fun addrIn: FFSocketAddrIn `{ return &recv->addr_in; `}
-       fun destroy `{ free(recv); `}
 end
 
-extern class FFSocketAddrIn `{ S_ADDR_IN* `}
+# Result of a call to `NativeSocket::accept`
+class SocketAcceptResult
+
+       # Opened socket
+       var socket: NativeSocket
+
+       # Address of the remote client
+       var addr_in: NativeSocketAddrIn
+end
+
+extern class NativeSocketAddrIn `{ struct sockaddr_in* `}
        new `{
-               S_ADDR_IN *sai = NULL;
-               sai = malloc( sizeof(S_ADDR_IN) );
+               struct sockaddr_in *sai = NULL;
+               sai = malloc(sizeof(struct sockaddr_in));
                return sai;
        `}
-       new with(port: Int, family: FFSocketAddressFamilies) `{
-               S_ADDR_IN *sai = NULL;
-               sai = malloc( sizeof(S_ADDR_IN) );
+
+       new with(port: Int, family: NativeSocketAddressFamilies) `{
+               struct sockaddr_in *sai = NULL;
+               sai = malloc(sizeof(struct sockaddr_in));
                sai->sin_family = family;
                sai->sin_port = htons(port);
                sai->sin_addr.s_addr = INADDR_ANY;
                return sai;
        `}
-       new with_hostent(hostent: FFSocketHostent, port: Int) `{
-               S_ADDR_IN *sai = NULL;
-               sai = malloc( sizeof(S_ADDR_IN) );
+
+       new with_hostent(hostent: NativeSocketHostent, port: Int) `{
+               struct sockaddr_in *sai = NULL;
+               sai = malloc(sizeof(struct sockaddr_in));
                sai->sin_family = hostent->h_addrtype;
                sai->sin_port = htons(port);
-               memcpy( (char*)&sai->sin_addr.s_addr, (char*)hostent->h_addr, hostent->h_length );
+               memcpy((char*)&sai->sin_addr.s_addr, (char*)hostent->h_addr, hostent->h_length);
                return sai;
        `}
-       fun address: String import NativeString.to_s `{ return NativeString_to_s( (char*)inet_ntoa(recv->sin_addr) ); `}
-       fun family: FFSocketAddressFamilies `{ return recv->sin_family; `}
+
+       fun address: String import NativeString.to_s `{ return NativeString_to_s((char*)inet_ntoa(recv->sin_addr)); `}
+
+       fun family: NativeSocketAddressFamilies `{ return recv->sin_family; `}
+
        fun port: Int `{ return ntohs(recv->sin_port); `}
+
        fun destroy `{ free(recv); `}
 end
 
-extern class FFSocketHostent `{ S_HOSTENT* `}
-       private fun i_h_aliases(i: Int): String import NativeString.to_s `{ return NativeString_to_s(recv->h_aliases[i]); `}
-       private fun i_h_aliases_reachable(i: Int): Bool `{ return (recv->h_aliases[i] != NULL); `}
+extern class NativeSocketHostent `{ struct hostent* `}
+       private fun native_h_aliases(i: Int): String import NativeString.to_s `{ return NativeString_to_s(recv->h_aliases[i]); `}
+
+       private fun native_h_aliases_reachable(i: Int): Bool `{ return (recv->h_aliases[i] != NULL); `}
+
        fun h_aliases: Array[String]
        do
                var i=0
                var d=new Array[String]
                loop
-                       d.add(i_h_aliases(i))
-                       if i_h_aliases_reachable(i+1) == false then break
+                       d.add(native_h_aliases(i))
+                       if native_h_aliases_reachable(i+1) == false then break
                        i += 1
                end
                return d
        end
-       fun h_addr: String import NativeString.to_s `{ return NativeString_to_s( (char*)inet_ntoa(*(S_IN_ADDR*)recv->h_addr) ); `}
+
+       fun h_addr: String import NativeString.to_s `{ return NativeString_to_s((char*)inet_ntoa(*(struct in_addr*)recv->h_addr)); `}
+
        fun h_addrtype: Int `{ return recv->h_addrtype; `}
+
        fun h_length: Int `{ return recv->h_length; `}
+
        fun h_name: String import NativeString.to_s `{ return NativeString_to_s(recv->h_name); `}
 end
 
-extern class FFTimeval `{ S_TIMEVAL* `}
+extern class NativeTimeval `{ struct timeval* `}
        new (seconds: Int, microseconds: Int) `{
-               S_TIMEVAL* tv = NULL;
-               tv = malloc( sizeof(S_TIMEVAL) );
+               struct timeval* tv = NULL;
+               tv = malloc(sizeof(struct timeval));
                tv->tv_sec = seconds;
                tv->tv_usec = microseconds;
                return tv;
        `}
+
        fun seconds: Int `{ return recv->tv_sec; `}
+
        fun microseconds: Int `{ return recv->tv_usec; `}
-       fun destroy `{ free( recv ); `}
+
+       fun destroy `{ free(recv); `}
 end
 
-extern class FFSocketSet `{ S_FD_SET* `}
+extern class NativeSocketSet `{ fd_set* `}
        new `{
-               S_FD_SET *f = NULL;
-               f = malloc( sizeof(S_FD_SET) );
+               fd_set *f = NULL;
+               f = malloc(sizeof(fd_set));
                return f;
        `}
-       fun set(s: FFSocket) `{ FD_SET( *s, recv ); `}
-       fun is_set(s: FFSocket): Bool `{ return FD_ISSET( *s, recv ); `}
-       fun zero `{ FD_ZERO( recv ); `}
-       fun clear(s: FFSocket) `{ FD_CLR( *s, recv ); `}
-       fun destroy `{ free( recv ); `}
+
+       fun set(s: NativeSocket) `{ FD_SET(*s, recv); `}
+
+       fun is_set(s: NativeSocket): Bool `{ return FD_ISSET(*s, recv); `}
+
+       fun zero `{ FD_ZERO(recv); `}
+
+       fun clear(s: NativeSocket) `{ FD_CLR(*s, recv); `}
+
+       fun destroy `{ free(recv); `}
 end
 
-class FFSocketObserver
-       fun select(max: FFSocket, reads: nullable FFSocketSet, write: nullable FFSocketSet,
-             except: nullable FFSocketSet, timeout: FFTimeval): Int `{
-               S_FD_SET *rds, *wts, *exs = NULL;
-               S_TIMEVAL *tm = NULL;
-               if(reads != NULL) rds = (S_FD_SET*)reads;
-               if(write != NULL) wts = (S_FD_SET*)write;
-               if(except != NULL) exs = (S_FD_SET*)except;
-               if(timeout != NULL) tm = (S_TIMEVAL*)timeout;
+class NativeSocketObserver
+       # FIXME this implementation is broken. `reads`, `write` and `except`
+       # are boxed objects, passing them to a C function is illegal.
+       fun select(max: NativeSocket, reads: nullable NativeSocketSet, write: nullable NativeSocketSet,
+                        except: nullable NativeSocketSet, timeout: NativeTimeval): Int `{
+               fd_set *rds, *wts, *exs = NULL;
+               struct timeval *tm = NULL;
+               if (reads != NULL) rds = (fd_set*)reads;
+               if (write != NULL) wts = (fd_set*)write;
+               if (except != NULL) exs = (fd_set*)except;
+               if (timeout != NULL) tm = (struct timeval*)timeout;
                return select(*max, rds, wts, exs, tm);
        `}
 end
 
-extern class FFSocketTypes `{ int `}
+extern class NativeSocketTypes `{ int `}
        new sock_stream `{ return SOCK_STREAM; `}
        new sock_dgram `{ return SOCK_DGRAM; `}
        new sock_raw `{ return SOCK_RAW; `}
        new sock_seqpacket `{ return SOCK_SEQPACKET; `}
 end
-extern class FFSocketAddressFamilies `{ int `}
+
+extern class NativeSocketAddressFamilies `{ int `}
        new af_null `{ return 0; `}
-       new af_unspec `{ return  AF_UNSPEC; `}          # unspecified
-       new af_unix `{ return  AF_UNIX; `}              # local to host (pipes)
-       new af_local `{ return  AF_LOCAL; `}            # backward compatibility
-       new af_inet `{ return  AF_INET; `}              # internetwork: UDP, TCP, etc.
-       new af_sna `{ return  AF_SNA; `}                # IBM SNA
-       new af_decnet `{ return  AF_DECnet; `}          # DECnet
-       new af_route `{ return  AF_ROUTE; `}            # Internal Routing Protocol
-       new af_ipx `{ return  AF_IPX; `}                # Novell Internet Protocol
-       new af_isdn `{ return  AF_ISDN; `}              # Integrated Services Digital Network
-       new af_inet6 `{ return  AF_INET6; `}            # IPv6
-       new af_max `{ return  AF_MAX; `}
+
+       # Unspecified
+       new af_unspec `{ return AF_UNSPEC; `}
+
+       # Local to host (pipes)
+       new af_unix `{ return AF_UNIX; `}
+
+       # For backward compatibility
+       new af_local `{ return AF_LOCAL; `}
+
+       # Internetwork: UDP, TCP, etc.
+       new af_inet `{ return AF_INET; `}
+
+       # IBM SNA
+       new af_sna `{ return AF_SNA; `}
+
+       # DECnet
+       new af_decnet `{ return AF_DECnet; `}
+
+       # Internal Routing Protocol
+       new af_route `{ return AF_ROUTE; `}
+
+       # Novell Internet Protocol
+       new af_ipx `{ return AF_IPX; `}
+
+       # Integrated Services Digital Network
+       new af_isdn `{ return AF_ISDN; `}
+
+       # IPv6
+       new af_inet6 `{ return AF_INET6; `}
+
+       new af_max `{ return AF_MAX; `}
 end
-extern class FFSocketProtocolFamilies `{ int `}
+
+extern class NativeSocketProtocolFamilies `{ int `}
        new pf_null `{ return 0; `}
        new pf_unspec `{ return PF_UNSPEC; `}
        new pf_local `{ return PF_LOCAL; `}
@@ -322,43 +392,87 @@ extern class FFSocketProtocolFamilies `{ int `}
        new pf_inet6 `{ return PF_INET6; `}
        new pf_max `{ return PF_MAX; `}
 end
+
 # Level on which to set options
-extern class FFSocketOptLevels `{ int `}
+extern class NativeSocketOptLevels `{ int `}
+
        # Dummy for IP (As defined in C)
        new ip `{ return IPPROTO_IP;`}
+
        # Control message protocol
        new icmp `{ return IPPROTO_ICMP;`}
+
        # Use TCP
        new tcp `{ return IPPROTO_TCP; `}
+
        # Socket level options
        new socket `{ return SOL_SOCKET; `}
 end
+
 # Options for socket, use with setsockopt
-extern class FFSocketOptNames `{ int `}
+extern class NativeSocketOptNames `{ int `}
+
        # Enables debugging information
        new debug `{ return SO_DEBUG; `}
+
        # Authorizes the broadcasting of messages
        new broadcast `{ return SO_BROADCAST; `}
+
        # Authorizes the reuse of the local address
        new reuseaddr `{ return SO_REUSEADDR; `}
+
        # Authorizes the use of keep-alive packets in a connection
        new keepalive `{ return SO_KEEPALIVE; `}
+
+       # Disable the Nagle algorithm and send data as soon as possible, in smaller packets
+       new tcp_nodelay `{ return TCP_NODELAY; `}
 end
+
 # Used for the poll function of a socket, mix several Poll values to check for events on more than one type of event
-extern class FFSocketPollValues `{ int `}
-       new pollin `{ return POLLIN; `}           # Data other than high-priority data may be read without blocking.
-       new pollrdnorm `{ return POLLRDNORM; `}   # Normal data may be read without blocking.
-       new pollrdband `{ return POLLRDBAND; `}   # Priority data may be read without blocking.
-       new pollpri `{ return POLLPRI; `}         # High-priority data may be read without blocking.
-       new pollout `{ return POLLOUT; `}         # Normal data may be written without blocking.
-       new pollwrnorm `{ return POLLWRNORM; `}   # Equivalent to POLLOUT
-       new pollwrband `{ return POLLWRBAND; `}   # Priority data may be written.
-       new pollerr `{ return POLLERR; `}         # An error has occurred on the device or stream. This flag is only valid in the revents bitmask; it shall be ignored in the events member.
-       new pollhup `{ return POLLHUP; `}         # The device has been disconnected. This event and POLLOUT are mutually-exclusive; a stream can never be writable if a hangup has occurred. However, this event and POLLIN, POLLRDNORM, POLLRDBAND, or POLLPRI are not mutually-exclusive. This flag is only valid in the revents bitmask; it shall be ignored in the events member.
-       new pollnval `{ return POLLNVAL; `}       # The specified fd value is invalid. This flag is only valid in the revents member; it shall ignored in the events member.
-
-       # Combines two FFSocketPollValues
-       private fun +(other: FFSocketPollValues): FFSocketPollValues `{
+extern class NativeSocketPollValues `{ int `}
+
+       # Data other than high-priority data may be read without blocking.
+       new pollin `{ return POLLIN; `}
+
+       # Normal data may be read without blocking.
+       new pollrdnorm `{ return POLLRDNORM; `}
+
+       # Priority data may be read without blocking.
+       new pollrdband `{ return POLLRDBAND; `}
+
+       # High-priority data may be read without blocking.
+       new pollpri `{ return POLLPRI; `}
+
+       # Normal data may be written without blocking.
+       new pollout `{ return POLLOUT; `}
+
+       # Equivalent to POLLOUT
+       new pollwrnorm `{ return POLLWRNORM; `}
+
+       # Priority data may be written.
+       new pollwrband `{ return POLLWRBAND; `}
+
+       # An error has occurred on the device or stream.
+       #
+       # This flag is only valid in the revents bitmask; it shall be ignored in the events member.
+       new pollerr `{ return POLLERR; `}
+
+       # The device has been disconnected.
+       #
+       # This event and POLLOUT are mutually-exclusive; a stream can never be
+       # writable if a hangup has occurred. However, this event and POLLIN,
+       # POLLRDNORM, POLLRDBAND, or POLLPRI are not mutually-exclusive.
+       #
+       # This flag is only valid in the revents bitmask; it shall be ignored in the events member.
+       new pollhup `{ return POLLHUP; `}
+
+       # The specified fd value is invalid.
+       #
+       # This flag is only valid in the revents member; it shall ignored in the events member.
+       new pollnval `{ return POLLNVAL; `}
+
+       # Combines two NativeSocketPollValues
+       private fun +(other: NativeSocketPollValues): NativeSocketPollValues `{
                return recv | other;
        `}
 end
index aa1124a..089df1b 100644 (file)
@@ -23,13 +23,13 @@ var sock = new WebSocket(8088, 1)
 
 var msg: String
 
-if sock.listener.eof then
+if sock.listener.closed then
        print sys.errno.strerror
 end
 
 sock.accept
 
-while not sock.listener.eof do
+while not sock.listener.closed do
        if not sock.connected then sock.accept
        if sys.stdin.poll_in then
                msg = gets
index 9066391..ebf3f49 100644 (file)
@@ -31,25 +31,28 @@ class WebSocket
        super PollableIStream
 
        # Client connection to the server
-       var client: Socket
+       var client: TCPStream
 
        # Socket listening to connections on a defined port
-       var listener: Socket
+       var listener: TCPServer
 
        # Creates a new Websocket server listening on given port with `max_clients` slots available
        init(port: Int, max_clients: Int)
        do
                _buffer = new FlatBuffer
                _buffer_pos = 0
-               listener = new Socket.server(port, max_clients)
+               listener = new TCPServer(port)
+               listener.listen max_clients
        end
 
        # Accept an incoming connection and initializes the handshake
        fun accept
        do
-               assert not listener.eof
+               assert not listener.closed
 
-               client = listener.accept
+               var client = listener.accept
+               assert client != null
+               self.client = client
 
                var headers = parse_handshake
                var resp = handshake_response(headers)
index b3bac6b..5675a7d 100644 (file)
@@ -90,8 +90,10 @@ redef class ModelBuilder
        fun set_stdstreams
        do
                if self.toolcontext.opt_socket_mode.value then
-                       var sock = new Socket.server(toolcontext.opt_debug_port.value, 1)
+                       var sock = new TCPServer(toolcontext.opt_debug_port.value)
+                       sock.listen 1
                        var ns = sock.accept
+                       assert ns != null
                        sock.close
                        sys.set_io(ns,ns,ns)
                else if self.toolcontext.opt_websocket_mode.value then
@@ -103,7 +105,7 @@ redef class ModelBuilder
 
        fun close_stdstreams
        do
-               if sys.stdin isa WebSocket or sys.stdin isa Socket then
+               if sys.stdin isa WebSocket or sys.stdin isa TCPStream then
                        sys.stdin.close
                        sys.stdout.close
                        sys.stderr.close
index 8983b61..349f53c 100644 (file)
@@ -65,19 +65,19 @@ if toolcontext.opt_debug_port.value < 0 or toolcontext.opt_debug_port.value > 65
        return
 end
 
-var debug: Socket
+var debug: TCPStream
 
 # An IPV4 address does always complies to this form : x.x.x.x
 # Where x is an integer whose value is >=0 and <= 255
 if toolcontext.opt_host_address.value != null then
        if toolcontext.opt_host_address.value.is_valid_ipv4_address then
-               debug = new Socket.client(toolcontext.opt_host_address.value.as(not null), toolcontext.opt_debug_port.value)
+               debug = new TCPStream.connect(toolcontext.opt_host_address.value.as(not null), toolcontext.opt_debug_port.value)
        else
                toolcontext.option_context.usage
                return
        end
 else
-       debug = new Socket.client("127.0.0.1", toolcontext.opt_debug_port.value)
+       debug = new TCPStream.connect("127.0.0.1", toolcontext.opt_debug_port.value)
 end
 
 print "[HOST ADDRESS] : {debug.address}"
diff --git a/tests/sav/socket_simple_server.res b/tests/sav/socket_simple_server.res
new file mode 100644 (file)
index 0000000..1ff31ed
--- /dev/null
@@ -0,0 +1 @@
+Usage : socket_simple_server <port>