“You cannot teach a man anything, ..” — Galileo
You must go and play with
tcpip_connection_stream and see for yourself. So that is what I did. The result is a module and exemplar called “daemon”, available on my github page. I think it can be used to build network services with Smallworld™ and I’ll show you how.
daemon main loop is as follows:
heavy_thread.new( _proc() _local sock << tcpip_connection_stream.new(port) _loop handle_client_in_separate_thread(sock.get()) _endloop _endproc )
A listening socket can be made in Smallworld™ with
.get() on it waits for
and accepts connections.
.get() returns an accepted socket, which
is of type
tcpip_connection in magik. That connection is best
handled in a new thread, because the listening thread should quickly
return to accept new connections. This logic is in the run method on
my “daemon” exemplar. Client connections are handled as outlined in
the following lines:
heavy_thread.new( _proc(accepted_socket) _loop _local service_name << read_service_to_call_from( accepted_socket.input ) daemon.services[ service_name ].invoke( ... ) _endloop _endproc )
To understand above lines, you need to know how to register services in my network service shell. They are simply put in the global
daemon.services like so (yes indeed, no xml configuration):
daemon.services[:list_themes] << _proc(args) ... _endproc
This means a service can be anything that understands the
invoke() protocol. This includes procs. The following block of code implements a service that lists system themes in the Electricity ACE of the Cambridge Database:
_global list_themes_service<< _proc(args) ## args is a property_list with the keys/values ## :input - socket input stream ## :output - socket output stream ## :socket - the socket itself (use only if necessary) # this service reads _nothing_ from the client _local all_themes << rope.new() _local ace_name << :|Electricity| _local themes << gis_program_manager.ace(ace_name).themes() _for thms _over themes.fast_elements() _loop # themes have this hierarchy thing going on. # we walk the hierarchy to find them all. _for thm _over thms.fast_depth_first() _loop _if thm.is_class_of?( theme ) _then all_themes.add(thm) _endif _endloop _endloop # this service writes to the client: # number of themes (uint32) # for each theme: # number of chars for theme name (uint32) # theme name (num-chars bytes) args[:output].put_unsigned_int( all_themes.size ) _for thm _over all_themes.elements() _loop args[:output].put_unsigned_int(thm.name.size) args[:output].put_ascii_chars(thm.name) _endloop args[:output].flush() _endproc $ # register proc (service) with daemon daemon.services[:list_themes] << list_themes_service $
Before or after registering the service call
daemon.run() to start my network shell. A client for the
list_system_themes service can be found in this gist.
To sum up, each TCP/IP connection is handled in a separate thread and there are no explicitly engineered bounds on the number of threads created. Taking into consideration that the Magik VM on 4.3 is single-threaded, there appears to be no point in having many cuncurrent client connections. In light of the outstanding service provided by one Magik VM I would not place the network shell in a heavy-use multi user environment without a facade, and the use of a proxy that serialises requests seems the architecture of choice.