Ivory Socket Demo Example

At the heart of this example is a listener socket. It uses a built-in method:

         onAccept:            acceptSocket

to accept connections up to a given limit and add them to its connections property. The built-in rule method monitorSocket will establish the socket to listen for connections on the port property.

A rule with an interpreted method is used to monitor the addition of a socket to the listener socket connections list. The action of this is to assign the onReceive property of the newly created socket to a lambda function which displays any received textual input.

Note how the newly accepted socket is customised dynamically. There is no need for a new type or subclass to achieve the desired functionality.

Finally, bytesToString is a built-in function with signature:

bytesToString::(ByteString -> String)

It simply converts a vector of bytes to a null terminated string. In practice, a more sophisticated function would be used to convert non-printable characters appropriately.

-------------------------------------------------------------------------------
--
-- Name:    socketDemo.is
--
-------------------------------------------------------------------------------
--
-- Description:
--
--    Demonstration IvoryScript configuration file
-- 
--    This script defines a simple application to display any textual
--    input received on a listening socket.
--
-------------------------------------------------------------------------------

let acceptedSocketRuleMethod rule::Rule event::Expr _::ADS _::EventPhase =
   case event of {
      AddRefEvent client::Socket listener::Socket #connections ->
         if listener::Ref = rule.listenerSocket then
            client.onReceive := (\socket::Socket ->
                                     show ((bytesToString (socket.receive)) ++ "\n"))
   }
in
   def {
      .socketRule = .Rule [
         method:              monitorSocket];,

      .commsStore =.ADS [
         ruleList:           !RefList[]];

      .commsStore.listenerSocket = .commsStore.Socket [
         port:                81,
         mode:                Listener,
         onAccept:            acceptSocket,
         connectionLimit:     2,
         connections:         !RefList[]];

      .acceptedSocketRule = .Rule [
         listenerSocket:      !.commsStore.listenerSocket,
         method:              acceptedSocketRuleMethod];

      .startADSGroup = !RefList [.commsStore];
      .start = mapProcRefs (raiseEvent (StartEvent 0)) (.startADSGroup)
   };
addRef (.socketRule)          (.commsStore)     #ruleList;
addRef (.acceptedSocketRule)  (.commsStore)     #ruleList;

The following script implements the built-in function onAccept:

module SocketAcceptance where
{
include "prelude.is"
include "socket.is"

!acceptSocket socket::Socket =
   let acceptedSocket::Socket = Socket[mode:     Accepted,
                                       port:      !(socket.port::Int),
                                       onClose:   closeSocket] (host socket)
   in
      if (socket.accept) acceptedSocket then {
         if ¬(hasProperty socket #connectionLimit) |
            length ((socket.connections)::RefList) < (socket.connectionLimit) then {
            trace "Accepting connection";
            addRef acceptedSocket socket #connections
         } else {
            error "acceptSocket: connection limit exceeded";
            destroyObject acceptedSocket
         }
      }
      else {
         error "acceptSocket: accept failed";
         destroyObject acceptedSocket
      }
};

!closeSocket socket::Socket =
   case socket.mode of {
      Accepted ->
         let removeClient ref::Ref =
            case ref of {
               referrer::Socket ->
                  if hasProperty referrer #connections then
                     removeRef socket referrer #connections
            }
         in {
            trace "Closing connection";
            mapProcRefs removeClient (revRefs socket);
            destroyObject socket
         }
   }

home

Last update: 11 October, 2005