<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <link rel="stylesheet" href="style.css" type="text/css"> <meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type"> <link rel="Start" href="index.html"> <link rel="previous" href="Netplex_mbox.html"> <link rel="next" href="Netplex_advanced.html"> <link rel="Up" href="index.html"> <link title="Index of types" rel=Appendix href="index_types.html"> <link title="Index of exceptions" rel=Appendix href="index_exceptions.html"> <link title="Index of values" rel=Appendix href="index_values.html"> <link title="Index of class attributes" rel=Appendix href="index_attributes.html"> <link title="Index of class methods" rel=Appendix href="index_methods.html"> <link title="Index of classes" rel=Appendix href="index_classes.html"> <link title="Index of class types" rel=Appendix href="index_class_types.html"> <link title="Index of modules" rel=Appendix href="index_modules.html"> <link title="Index of module types" rel=Appendix href="index_module_types.html"> <link title="Uq_gtk" rel="Chapter" href="Uq_gtk.html"> <link title="Equeue" rel="Chapter" href="Equeue.html"> <link title="Unixqueue" rel="Chapter" href="Unixqueue.html"> <link title="Unixqueue_pollset" rel="Chapter" href="Unixqueue_pollset.html"> <link title="Unixqueue_select" rel="Chapter" href="Unixqueue_select.html"> <link title="Uq_resolver" rel="Chapter" href="Uq_resolver.html"> <link title="Uq_engines" rel="Chapter" href="Uq_engines.html"> <link title="Uq_socks5" rel="Chapter" href="Uq_socks5.html"> <link title="Uq_io" rel="Chapter" href="Uq_io.html"> <link title="Uq_lwt" rel="Chapter" href="Uq_lwt.html"> <link title="Uq_libevent" rel="Chapter" href="Uq_libevent.html"> <link title="Uq_mt" rel="Chapter" href="Uq_mt.html"> <link title="Equeue_intro" rel="Chapter" href="Equeue_intro.html"> <link title="Equeue_howto" rel="Chapter" href="Equeue_howto.html"> <link title="Uq_ssl" rel="Chapter" href="Uq_ssl.html"> <link title="Https_client" rel="Chapter" href="Https_client.html"> <link title="Uq_tcl" rel="Chapter" href="Uq_tcl.html"> <link title="Netcamlbox" rel="Chapter" href="Netcamlbox.html"> <link title="Netcgi_apache" rel="Chapter" href="Netcgi_apache.html"> <link title="Netcgi_modtpl" rel="Chapter" href="Netcgi_modtpl.html"> <link title="Netcgi_common" rel="Chapter" href="Netcgi_common.html"> <link title="Netcgi" rel="Chapter" href="Netcgi.html"> <link title="Netcgi_ajp" rel="Chapter" href="Netcgi_ajp.html"> <link title="Netcgi_scgi" rel="Chapter" href="Netcgi_scgi.html"> <link title="Netcgi_cgi" rel="Chapter" href="Netcgi_cgi.html"> <link title="Netcgi_fcgi" rel="Chapter" href="Netcgi_fcgi.html"> <link title="Netcgi_dbi" rel="Chapter" href="Netcgi_dbi.html"> <link title="Netcgi1_compat" rel="Chapter" href="Netcgi1_compat.html"> <link title="Netcgi_test" rel="Chapter" href="Netcgi_test.html"> <link title="Netcgi_porting" rel="Chapter" href="Netcgi_porting.html"> <link title="Netcgi_plex" rel="Chapter" href="Netcgi_plex.html"> <link title="Http_client_conncache" rel="Chapter" href="Http_client_conncache.html"> <link title="Http_client" rel="Chapter" href="Http_client.html"> <link title="Telnet_client" rel="Chapter" href="Telnet_client.html"> <link title="Ftp_data_endpoint" rel="Chapter" href="Ftp_data_endpoint.html"> <link title="Ftp_client" rel="Chapter" href="Ftp_client.html"> <link title="Http_fs" rel="Chapter" href="Http_fs.html"> <link title="Ftp_fs" rel="Chapter" href="Ftp_fs.html"> <link title="Netclient_tut" rel="Chapter" href="Netclient_tut.html"> <link title="Netgssapi" rel="Chapter" href="Netgssapi.html"> <link title="Nethttpd_types" rel="Chapter" href="Nethttpd_types.html"> <link title="Nethttpd_kernel" rel="Chapter" href="Nethttpd_kernel.html"> <link title="Nethttpd_reactor" rel="Chapter" href="Nethttpd_reactor.html"> <link title="Nethttpd_engine" rel="Chapter" href="Nethttpd_engine.html"> <link title="Nethttpd_services" rel="Chapter" href="Nethttpd_services.html"> <link title="Nethttpd_plex" rel="Chapter" href="Nethttpd_plex.html"> <link title="Nethttpd_util" rel="Chapter" href="Nethttpd_util.html"> <link title="Nethttpd_intro" rel="Chapter" href="Nethttpd_intro.html"> <link title="Netmech_scram" rel="Chapter" href="Netmech_scram.html"> <link title="Netmech_scram_gssapi" rel="Chapter" href="Netmech_scram_gssapi.html"> <link title="Netmcore" rel="Chapter" href="Netmcore.html"> <link title="Netmcore_camlbox" rel="Chapter" href="Netmcore_camlbox.html"> <link title="Netmcore_mempool" rel="Chapter" href="Netmcore_mempool.html"> <link title="Netmcore_heap" rel="Chapter" href="Netmcore_heap.html"> <link title="Netmcore_ref" rel="Chapter" href="Netmcore_ref.html"> <link title="Netmcore_array" rel="Chapter" href="Netmcore_array.html"> <link title="Netmcore_sem" rel="Chapter" href="Netmcore_sem.html"> <link title="Netmcore_mutex" rel="Chapter" href="Netmcore_mutex.html"> <link title="Netmcore_condition" rel="Chapter" href="Netmcore_condition.html"> <link title="Netmcore_queue" rel="Chapter" href="Netmcore_queue.html"> <link title="Netmcore_buffer" rel="Chapter" href="Netmcore_buffer.html"> <link title="Netmcore_matrix" rel="Chapter" href="Netmcore_matrix.html"> <link title="Netmcore_hashtbl" rel="Chapter" href="Netmcore_hashtbl.html"> <link title="Netmcore_process" rel="Chapter" href="Netmcore_process.html"> <link title="Netmcore_tut" rel="Chapter" href="Netmcore_tut.html"> <link title="Netmcore_basics" rel="Chapter" href="Netmcore_basics.html"> <link title="Netplex_types" rel="Chapter" href="Netplex_types.html"> <link title="Netplex_mp" rel="Chapter" href="Netplex_mp.html"> <link title="Netplex_mt" rel="Chapter" href="Netplex_mt.html"> <link title="Netplex_log" rel="Chapter" href="Netplex_log.html"> <link title="Netplex_controller" rel="Chapter" href="Netplex_controller.html"> <link title="Netplex_container" rel="Chapter" href="Netplex_container.html"> <link title="Netplex_sockserv" rel="Chapter" href="Netplex_sockserv.html"> <link title="Netplex_workload" rel="Chapter" href="Netplex_workload.html"> <link title="Netplex_main" rel="Chapter" href="Netplex_main.html"> <link title="Netplex_config" rel="Chapter" href="Netplex_config.html"> <link title="Netplex_kit" rel="Chapter" href="Netplex_kit.html"> <link title="Rpc_netplex" rel="Chapter" href="Rpc_netplex.html"> <link title="Netplex_cenv" rel="Chapter" href="Netplex_cenv.html"> <link title="Netplex_semaphore" rel="Chapter" href="Netplex_semaphore.html"> <link title="Netplex_sharedvar" rel="Chapter" href="Netplex_sharedvar.html"> <link title="Netplex_mutex" rel="Chapter" href="Netplex_mutex.html"> <link title="Netplex_encap" rel="Chapter" href="Netplex_encap.html"> <link title="Netplex_mbox" rel="Chapter" href="Netplex_mbox.html"> <link title="Netplex_intro" rel="Chapter" href="Netplex_intro.html"> <link title="Netplex_advanced" rel="Chapter" href="Netplex_advanced.html"> <link title="Netplex_admin" rel="Chapter" href="Netplex_admin.html"> <link title="Netshm" rel="Chapter" href="Netshm.html"> <link title="Netshm_data" rel="Chapter" href="Netshm_data.html"> <link title="Netshm_hashtbl" rel="Chapter" href="Netshm_hashtbl.html"> <link title="Netshm_array" rel="Chapter" href="Netshm_array.html"> <link title="Netshm_intro" rel="Chapter" href="Netshm_intro.html"> <link title="Netconversion" rel="Chapter" href="Netconversion.html"> <link title="Netchannels" rel="Chapter" href="Netchannels.html"> <link title="Netstream" rel="Chapter" href="Netstream.html"> <link title="Mimestring" rel="Chapter" href="Mimestring.html"> <link title="Netmime" rel="Chapter" href="Netmime.html"> <link title="Netsendmail" rel="Chapter" href="Netsendmail.html"> <link title="Neturl" rel="Chapter" href="Neturl.html"> <link title="Netaddress" rel="Chapter" href="Netaddress.html"> <link title="Netbuffer" rel="Chapter" href="Netbuffer.html"> <link title="Netdate" rel="Chapter" href="Netdate.html"> <link title="Netencoding" rel="Chapter" href="Netencoding.html"> <link title="Netulex" rel="Chapter" href="Netulex.html"> <link title="Netaccel" rel="Chapter" href="Netaccel.html"> <link title="Netaccel_link" rel="Chapter" href="Netaccel_link.html"> <link title="Nethtml" rel="Chapter" href="Nethtml.html"> <link title="Netstring_str" rel="Chapter" href="Netstring_str.html"> <link title="Netmappings" rel="Chapter" href="Netmappings.html"> <link title="Netaux" rel="Chapter" href="Netaux.html"> <link title="Nethttp" rel="Chapter" href="Nethttp.html"> <link title="Netpagebuffer" rel="Chapter" href="Netpagebuffer.html"> <link title="Netfs" rel="Chapter" href="Netfs.html"> <link title="Netglob" rel="Chapter" href="Netglob.html"> <link title="Netauth" rel="Chapter" href="Netauth.html"> <link title="Netsockaddr" rel="Chapter" href="Netsockaddr.html"> <link title="Netnumber" rel="Chapter" href="Netnumber.html"> <link title="Rtypes" rel="Chapter" href="Rtypes.html"> <link title="Xdr_mstring" rel="Chapter" href="Xdr_mstring.html"> <link title="Xdr" rel="Chapter" href="Xdr.html"> <link title="Netcompression" rel="Chapter" href="Netcompression.html"> <link title="Netunichar" rel="Chapter" href="Netunichar.html"> <link title="Netchannels_tut" rel="Chapter" href="Netchannels_tut.html"> <link title="Netmime_tut" rel="Chapter" href="Netmime_tut.html"> <link title="Netsendmail_tut" rel="Chapter" href="Netsendmail_tut.html"> <link title="Netulex_tut" rel="Chapter" href="Netulex_tut.html"> <link title="Neturl_tut" rel="Chapter" href="Neturl_tut.html"> <link title="Netstring_pcre" rel="Chapter" href="Netstring_pcre.html"> <link title="Netsys" rel="Chapter" href="Netsys.html"> <link title="Netsys_posix" rel="Chapter" href="Netsys_posix.html"> <link title="Netsys_pollset" rel="Chapter" href="Netsys_pollset.html"> <link title="Netlog" rel="Chapter" href="Netlog.html"> <link title="Netexn" rel="Chapter" href="Netexn.html"> <link title="Netsys_win32" rel="Chapter" href="Netsys_win32.html"> <link title="Netsys_pollset_posix" rel="Chapter" href="Netsys_pollset_posix.html"> <link title="Netsys_pollset_win32" rel="Chapter" href="Netsys_pollset_win32.html"> <link title="Netsys_pollset_generic" rel="Chapter" href="Netsys_pollset_generic.html"> <link title="Netsys_signal" rel="Chapter" href="Netsys_signal.html"> <link title="Netsys_oothr" rel="Chapter" href="Netsys_oothr.html"> <link title="Netsys_xdr" rel="Chapter" href="Netsys_xdr.html"> <link title="Netsys_rng" rel="Chapter" href="Netsys_rng.html"> <link title="Netsys_types" rel="Chapter" href="Netsys_types.html"> <link title="Netsys_mem" rel="Chapter" href="Netsys_mem.html"> <link title="Netsys_tmp" rel="Chapter" href="Netsys_tmp.html"> <link title="Netsys_sem" rel="Chapter" href="Netsys_sem.html"> <link title="Netsys_pmanage" rel="Chapter" href="Netsys_pmanage.html"> <link title="Netgzip" rel="Chapter" href="Netgzip.html"> <link title="Netpop" rel="Chapter" href="Netpop.html"> <link title="Rpc_auth_dh" rel="Chapter" href="Rpc_auth_dh.html"> <link title="Rpc_key_service" rel="Chapter" href="Rpc_key_service.html"> <link title="Rpc_time" rel="Chapter" href="Rpc_time.html"> <link title="Rpc_auth_local" rel="Chapter" href="Rpc_auth_local.html"> <link title="Rpc" rel="Chapter" href="Rpc.html"> <link title="Rpc_program" rel="Chapter" href="Rpc_program.html"> <link title="Rpc_util" rel="Chapter" href="Rpc_util.html"> <link title="Rpc_portmapper_aux" rel="Chapter" href="Rpc_portmapper_aux.html"> <link title="Rpc_packer" rel="Chapter" href="Rpc_packer.html"> <link title="Rpc_transport" rel="Chapter" href="Rpc_transport.html"> <link title="Rpc_client" rel="Chapter" href="Rpc_client.html"> <link title="Rpc_simple_client" rel="Chapter" href="Rpc_simple_client.html"> <link title="Rpc_portmapper_clnt" rel="Chapter" href="Rpc_portmapper_clnt.html"> <link title="Rpc_portmapper" rel="Chapter" href="Rpc_portmapper.html"> <link title="Rpc_server" rel="Chapter" href="Rpc_server.html"> <link title="Rpc_auth_sys" rel="Chapter" href="Rpc_auth_sys.html"> <link title="Rpc_auth_gssapi" rel="Chapter" href="Rpc_auth_gssapi.html"> <link title="Rpc_proxy" rel="Chapter" href="Rpc_proxy.html"> <link title="Rpc_intro" rel="Chapter" href="Rpc_intro.html"> <link title="Rpc_mapping_ref" rel="Chapter" href="Rpc_mapping_ref.html"> <link title="Rpc_intro_gss" rel="Chapter" href="Rpc_intro_gss.html"> <link title="Rpc_ssl" rel="Chapter" href="Rpc_ssl.html"> <link title="Rpc_xti_client" rel="Chapter" href="Rpc_xti_client.html"> <link title="Shell_sys" rel="Chapter" href="Shell_sys.html"> <link title="Shell" rel="Chapter" href="Shell.html"> <link title="Shell_uq" rel="Chapter" href="Shell_uq.html"> <link title="Shell_fs" rel="Chapter" href="Shell_fs.html"> <link title="Shell_intro" rel="Chapter" href="Shell_intro.html"> <link title="Netsmtp" rel="Chapter" href="Netsmtp.html"> <link title="Intro" rel="Chapter" href="Intro.html"> <link title="Platform" rel="Chapter" href="Platform.html"> <link title="Foreword" rel="Chapter" href="Foreword.html"> <link title="Ipv6" rel="Chapter" href="Ipv6.html"> <link title="Regexp" rel="Chapter" href="Regexp.html"><link title="Introduction into Netplex" rel="Section" href="#1_IntroductionintoNetplex"> <link title="Netplex RPC systems" rel="Section" href="#rpc_netplex"> <link title="Terminology" rel="Subsection" href="#terminology"> <link title="Example - A Simple Web Server" rel="Subsection" href="#webserver"> <link title="Running This Example" rel="Subsection" href="#runwebserver"> <link title="The Process Model" rel="Subsection" href="#procmod"> <link title="Creating Sockets" rel="Subsection" href="#crsock"> <link title="Services And Processors" rel="Subsection" href="#servproc"> <link title="Defining Custom Processors" rel="Subsection" href="#defproc"> <link title="Workload Management" rel="Subsection" href="#workload"> <link title="Messaging" rel="Subsection" href="#messaging"> <link title="Logging" rel="Subsection" href="#logging"> <link title="Debug logging" rel="Subsection" href="#debug"> <link title="Compatibility with multi-threading" rel="Subsection" href="#mt"> <title>Ocamlnet 3 Reference Manual : Netplex_intro</title> </head> <body> <div class="navbar"><a class="pre" href="Netplex_mbox.html" title="Netplex_mbox">Previous</a> <a class="up" href="index.html" title="Index">Up</a> <a class="post" href="Netplex_advanced.html" title="Netplex_advanced">Next</a> </div> <h1>Netplex_intro</h1> <br> <h1 id="1_IntroductionintoNetplex">Introduction into Netplex</h1> <p> <b>Contents</b> <p> <ul> <li><a href="Netplex_intro.html#terminology"><i>Terminology</i></a></li> <li><a href="Netplex_intro.html#webserver"><i>Example - A Simple Web Server</i></a></li> <li><a href="Netplex_intro.html#runwebserver"><i>Running This Example</i></a></li> <li><a href="Netplex_intro.html#procmod"><i>The Process Model</i></a></li> <li><a href="Netplex_intro.html#crsock"><i>Creating Sockets</i></a></li> <li><a href="Netplex_intro.html#servproc"><i>Services And Processors</i></a></li> <li><a href="Netplex_intro.html#defproc"><i>Defining Custom Processors</i></a></li> <li><a href="Netplex_intro.html#workload"><i>Workload Management</i></a></li> <li><a href="Netplex_intro.html#messaging"><i>Messaging</i></a></li> <li><a href="Netplex_intro.html#logging"><i>Logging</i></a></li> <li><a href="Netplex_intro.html#debug"><i>Debug logging</i></a></li> <li><a href="Netplex_intro.html#mt"><i>Compatibility with multi-threading</i></a></li> <li><a href="Netplex_intro.html#rpc_netplex"><i>Netplex RPC systems</i></a></li> </ul> Netplex is a generic (stream) server framework. This means, Netplex does a lot of things for a network service that are always the same, regardless of the kind of service: <p> <ul> <li>Creation of server sockets</li> <li>Accepting new network connections</li> <li>Organizing multiple threads of execution - either by multiple processes, multiple POSIX threads, or multiplexing</li> <li>Workload management</li> <li>Writing log files</li> <li>Broadcasting messages to all server components</li> <li>Support for a configuration file format</li> </ul> Netplex currently only supports stream sockets (TCP or Unix Domain). <p> Ocamlnet already includes Netplex adapters for Nethttpd (the HTTP daemon), and RPC servers. It is likely that more adapters for other network protocols will follow. <p> Netplex can bundle several network services into a single system of components. For example, you could have an RPC service that can be managed over a web interface provided by Nethttpd. Actually, Netplex focuses on such systems of interconnected components. RPC plays a special role in such systems because this is the network protocol the components use to talk to each other. It is also internally used by Netplex for its administrative tasks. <p> <h2 id="terminology">Terminology</h2> <p> In the Netplex world the following words are preferred to refer to the parts of a Netplex system: <p> <ul> <li>The <b>Netplex controller</b> is the core component of Netplex. The controller opens sockets and manages how new connections are accepted. For every socket, the controller determines which <b>Netplex container</b> will accept the next connection that is tried to be established. Furthermore, the controller manages the startup and shutdown of the Netplex system.</li> </ul> <ul> <li>The <b>Netplex services</b> are the user-defined components of a Netplex system. Every service runs in its own process(es) (if multi-processing is selected) or in its own thread(s) (for POSIX multi-threading). It is up to the user to define what a service is.</li> </ul> <ul> <li>The <b>Netplex protocols</b> are the languages spoken by the services. A protocol is bound to one or more sockets. This means that a service is implemented by a number of protocols. </li> </ul> <ul> <li>The <b>Netplex containers</b> are processes or threads that may execute a certain service. Every container is bound to a specific service. It is possible that only one container is used for a particular service, but one can also configure that containers are dynamically started and stopped as the workload of the system changes.</li> </ul> <h2 id="webserver">Example - A Simple Web Server</h2> <p> In order to create a web server, this main program and the following configuration file are sufficient. (You find an extended example in the "examples/nethttpd" directory of the Ocamlnet tarball.) <p> <pre class="codepre"><code class="code">let main() = (* Create a parser for the standard Netplex command-line arguments: *) let (opt_list, cmdline_cfg) = Netplex_main.args() in (* Parse the command-line arguments: *) Arg.parse opt_list (fun s -> raise (Arg.Bad ("Don't know what to do with: " ^ s))) "usage: netplex [options]"; (* Select multi-processing: *) let parallelizer = Netplex_mp.mp() in (* Start the Netplex system: *) Netplex_main.startup parallelizer Netplex_log.logger_factories Netplex_workload.workload_manager_factories [ Nethttpd_plex.nethttpd_factory() ] cmdline_cfg ;; Sys.set_signal Sys.sigpipe Sys.Signal_ignore; main();; </code></pre> <p> The configuration file: <p> <pre class="codepre"><code class="code">netplex { controller { max_level = "debug"; (* Log level *) logging { type = "stderr"; (* Log to stderr *) } }; service { name = "My HTTP file service"; protocol { (* This section creates the socket *) name = "http"; address { type = "internet"; bind = "0.0.0.0:80"; (* Port 80 on all interfaces *) }; }; processor { (* This section specifies how to process data of the socket *) type = "nethttpd"; host { (* Think of Apache's "virtual hosts" *) pref_name = "localhost"; pref_port = 80; names = "*:0"; (* Which requests are matched here: all *) uri { path = "/"; service { type = "file"; docroot = "/usr"; media_types_file = "/etc/mime.types"; enable_listings = true; } }; }; }; workload_manager { type = "dynamic"; max_jobs_per_thread = 1; (* Everything else is senseless *) min_free_jobs_capacity = 1; max_free_jobs_capacity = 1; max_threads = 20; }; } } </code></pre> <p> As you can see, the main program is extremely simple. Netplex includes support for command-line parsing, and the rest deals with the question which Netplex modules are made accessible for the configuration file. Note that detailed information about the config file is available in the <a href="Netplex_admin.html#main"><i>Netplex Administration Guide</i></a>. <p> Here, we have: <p> <ul> <li>The multi-execution provider is <a href="Netplex_mp.html#VALmp"><code class="code">Netplex_mp.mp</code></a> which implements multi-processing. (Btw, multi-processing is the preferred parallelizing technique in Netplex.) Replace it with <a href="Netplex_mt.html#VALmt"><code class="code">Netplex_mt.mt</code></a> to get multi-threading.</li> <li>The <a href="Netplex_log.html#VALlogger_factories"><code class="code">Netplex_log.logger_factories</code></a> are the list of all predefined logging mechanisms. The configuration file can select one of these mechanisms.</li> <li>The <a href="Netplex_workload.html#VALworkload_manager_factories"><code class="code">Netplex_workload.workload_manager_factories</code></a> are the list of all predefined worload management mechanisms. The configuration file can select one of these mechanisms per service.</li> <li>Finally, we pass <a href="Nethttpd_plex.html#VALnethttpd_factory"><code class="code">Nethttpd_plex.nethttpd_factory</code></a> as the only service processor.</li> </ul> The configuration file consists of nested sections whose extents are denoted by curly braces. The sections are partly defined by Netplex itself (e.g. the controller section and the workload manager section), and partly by the service provider (almost everything inside "processor"). That means that the components of a Netplex system pick "their" part from the configuration file, and that, depending on which components are linked into this system, the config files may look very different. <p> Here, we have: <p> <ul> <li>The <code class="code">controller</code> section sets the log level and the logging method. The latter is done by naming one of the logger factories as logging <code class="code">type</code>. If the factory needs more parameters to create the logger, these can be set inside the <code class="code">logging</code> section.</li> <li>For every <code class="code">service</code> there is a <code class="code">name</code> (can be freely chosen), one or several <code class="code">protocol</code>s, a <code class="code">processor</code>, and a <code class="code">workload_manager</code>. The <code class="code">protocol</code> section declare which protocols are available and to which sockets they are bound. Here, the "http" protocol (name can again be freely chosen) is reachable over TCP port 80 on all network interfaces. By having multiple <code class="code">address</code> sections, one can bind the same protocol to multiple sockets.</li> <li>The <code class="code">processor</code> section specifies the <code class="code">type</code> and optionally a lot of parameters (which may be structured into several sections). By setting <code class="code">type</code> to "nethttpd" we select the <a href="Nethttpd_plex.html#VALnethttpd_factory"><code class="code">Nethttpd_plex.nethttpd_factory</code></a> to create the processor (because "nethttpd" is the default name for this factory). This factory now interprets the other parameters of the <code class="code">processor</code> section. Here, a static HTTP server is defined that uses /usr as document root.</li> <li>Finally, the <code class="code">workload_manager</code> section says how to deal with parallely arriving requests. The <code class="code">type</code> selects the dynamic workload manager which is configured by the other parameters. Roughly said, one container (i.e. process) is created in advance for the next network connection ("pre-fork"), and the upper limit of containers is 20.</li> </ul> <h2 id="runwebserver">Running This Example</h2> <p> If you start this program without any arguments, it will immediately fail because it wants to open <code class="code">/etc/netplex.conf</code> - this is the default name for the configuration file. Use <code class="code">-conf</code> to pass the real name of the above file. <p> Netplex creates a directory for its internal processing, and this is by default <code class="code">/tmp/.netplex</code>. You can change this directory by setting the <code class="code">socket_directory</code> parameter in the <code class="code">controller</code> section. In this directory, you can find: <p> <ul> <li>A directory <code class="code">netplex.controller</code> which refers to the controller component.</li> <li>For every service another directory containing local run-time files. The directory has the same name as the service.</li> </ul> Netplex comes with a generic administration command called <code class="code">netplex-admin</code>. You can use it to send control messages to Netplex systems. For example, <p> <pre class="codepre"><code class="code"> netplex-admin -list </code></pre> <p> outputs the list of services. A more detailed list can be obtained with <p> <pre class="codepre"><code class="code"> netplex-admin -containers </code></pre> <p> The command <p> <pre class="codepre"><code class="code"> netplex-admin -shutdown </code></pre> <p> shuts the system (gracefully) down. It is also possible to broadcast messages to all components: <p> <pre class="codepre"><code class="code"> netplex-admin name arg1 arg2 ... </code></pre> <p> It is up to the components to interpret these messages. <p> <h2 id="procmod">The Process Model</h2> <p> Netplex uses a generalized pre-fork process model. Let me explain this model a bit, as it is important to know it in order to understand Netplex fully. <p> The most trivial form of a multi-process Unix server is the post-fork model. Although it is not used by Netplex, it is the model explained in many books, and it is what many people think a Unix server has to look like. Actually, the post-fork model has lots of limitations, and is not suited for high-performance servers. <p> In the post-fork model, the master process accepts new network connections in an endless loop, and whenever a new connection is established, a sub process (container process) is spawned that processes the network traffic. There is a serious logical limitation, and a performance limitation: <p> <ul> <li>In the post-fork model every container process can only deal with one connection at a time. The reason is that at the time of spawning the container there is only one connection, and one cannot assign the container another connection later.</li> <li>The post-fork model spawns the container processes at a bad moment. Spawning is a very expensive operation, and doing it just after connection establishment is bad because this means that the client has to wait longer for a response. Furthermore, spawning for every connection wastes system resources.</li> </ul> In the pre-fork model, these disadvantages can be avoided. Here, one or several processes are spawned in advance. Furthermore, these processes cannot only manage one connection but any number, and this can happen sequentially (one connection is processed after the other) or in parallel (using multiplexing). <p> This is achieved by letting the containers themselves accept the new connections instead of the master process. In the Unix process model it is possible that server sockets are shared by several processes, and every container is allowed to accept the next connection. However, the containers should cooperate, and avoid that several containers call <code class="code">Unix.accept</code> at the same time (as this leads to performance problems when a container must be able to watch several ports for new connections - a problem we do not discuss here). There are many ways to organize such cooperation, and for simplicity, Netplex implements this by exchanging RPC messages with the master process, the controller. Effectively, the controller has the task of scheduling which of the containers accepts the next arriving network connection. <p> What actually happens is the following. We assume here that we have a number of idle container processes that could accept the next connection. <p> <ul> <li>The controller selects one of the containers as the one that will get the next connection.</li> <li>The selected container watches the ports for incoming connections. Note that this is done in a Unixqueue, so that if there are already connections to be processed, this can be done in a multiplexed way in parallel with watching for new connections.</li> <li>When the next connection arrives, the container accepts it, and invokes the service component to process it.</li> <li>Immediately after connection establishment, the container tells the controller what happened, so the controller can watch out for another container to take over the role of accepting further connections.</li> <li>When the connection is fully processed, another control message is sent to the controller because the controller must know at all times how many connections are being processed by which containers. This is simply an important load parameter.</li> </ul> The details of this mechanism are not very interesting for using it. However, one must know that <p> <ul> <li>connections are accepted by the sub processes and not by the master process,</li> <li>the sub processes can accept as many connections as they want to, either one after the other, or even several at once,</li> <li>the controller schedules tasks and determines which connection is accepted by which container,</li> <li>there is a certain protocol between controller and container, and although the details are hidden from the user, this has consequences for the user interface. In particular, the reason why the <code class="code">when_done</code> function must be called upon connection termination is that a control message must be sent to the controller.</li> </ul> Another implication of the pre-fork model is that one needs workload management. As processes are created in advance, the question arises how many are created, and when the processes are terminated to free resources. Netplex comes with two workload managers: One manager simply creates a fixed number of processes which are never terminated, and the other manager tries to adapt the number to the load by dynamically starting and stopping processes. This is discussed below in detail. <p> <h2 id="crsock">Creating Sockets</h2> <p> The server sockets are always created by the controller at program startup. This is a strict requirement because only this ensures that the created container processes share the same sockets. <p> The sockets are descibed in the <code class="code">protocol</code> section of the configuration file. For an Internet socket this section looks like <p> <pre class="codepre"><code class="code"> protocol { name = "<name>"; address { type = "internet"; bind = "<address>:<port>"; }; }; </code></pre> <p> The <code class="code"><name></code> is only important when there are several protocols in order to distinguish between them. The <code class="code"><address></code> can be: <p> <ul> <li>An IP address of a network interface to bind the socket to this particular interface. Both IPv4 and IPv6 addresses are supported. IPv4 addresses are simply given in "dotted quad" notation (e.g. <code class="code">192.168.76.23</code>), and IPv6 addresses must be enclosed in brackets (e.g. <code class="code">[fe80::250:56ff:fec0:1]</code>).</li> <li>The special IPv4 address <code class="code">0.0.0.0</code> to bind the socket to all IPv4 network interfaces, or the special IPv6 address <code class="code">[::0]</code> to bind it to all IPv6 network interfaces.</li> <li>A resolvable host name which is the same as using the corresponding IP address.</li> </ul> The <code class="code"><port></code> must be the port number or 0 to use an anonymous port. <p> For a local (Unix domain) socket, the <code class="code">protocol</code> section looks like <p> <pre class="codepre"><code class="code"> protocol { name = "<name>"; address { type = "local"; path = "<path>"; }; }; </code></pre> <p> where the <code class="code"><path></code> is the filename of the socket. <p> One can have several <code class="code">address</code> sections to create several sockets for the same protocol. <p> For detailed documentation, see <a href="Netplex_admin.html#protocol"><i>The protocol subsection</i></a>. <p> <h2 id="servproc">Services And Processors</h2> <p> A Netplex system consists of exactly the services that are enumerated in the config file. This means it is not sufficient to build in support for a service into the program, one must also activate it in the config file. This gives the end user of the program a lot of flexibility when running the system: By simply changing the config file one can enable or disable services. It is also possible to run the same program binary several times with different config files. <p> The services are implemented by processors, which are user-defined objects that handle the network connection after it is accepted by the component. The <code class="code">processor</code> section of the service selects the processor by name, and optionally passes configuration parameters to it: <p> <pre class="codepre"><code class="code"> processor { type = "<name>"; ... further parameters allowed ... } </code></pre> <p> The mentioned name of the processor type is used to find the so-called factory for the processor (an object with a <code class="code">create_processor</code> method). All factories must be available at Netplex startup so the library knows which factories exist when the config file is interpreted (the factories are an argument of <a href="Netplex_main.html#VALstartup"><code class="code">Netplex_main.startup</code></a>). <p> Processor objects are somewhat strange in so far as they exist both in the controller and in the container processes. In particular, these objects are created by the controller, and they are duplicated once for all container processes when these are actually created. <p> The processor objects (of type <a href="Netplex_types.processor-c.html"><code class="code">Netplex_types.processor</code></a>) consist of a number of methods. We have already seen one of them, <code class="code">process</code>, which is called in the container process when a new connection is accepted. The other methods are called at other points of interest (see <a href="Netplex_types.processor_hooks-c.html"><code class="code">Netplex_types.processor_hooks</code></a> for more details): <p> <b>Methods called on the controller instance of the processor</b> <p> <ul> <li><code class="code">post_add_hook</code> is immediately called after the addtion of the service to the controller.</li> <li><code class="code">post_rm_hook</code> is immediately called after the removal of the service from the controller.</li> <li><code class="code">pre_start_hook</code> is called just before the next container process is spawned.</li> <li><code class="code">post_finish_hook</code> is called after termination of the container.</li> </ul> <b>Methods called on the container instance of the processor</b> <p> <ul> <li><code class="code">post_start_hook</code> is called just after the container process has been created, but now for the copy of the processor object that lives in the container process. This is a very useful hook method, because one can initialize the container process (e.g. prepare database accesses etc.).</li> <li><code class="code">pre_finish_hook</code> is called just before the container process will (regularly) terminated.</li> <li><code class="code">receive_message</code> is called when a message from another container arrives.</li> <li><code class="code">receive_admin_message</code> is called when a message from the administrator arrives.</li> <li><code class="code">shutdown</code> is called when the shutdown notification arrives. The shutdown will lead to the termination of the process when all network connections managed by Unixqueue are finished. This method must terminate such connections if they have been created in addition to those Netplex manages. The <code class="code">shutdown</code> notification is generated whenever a container needs to be stopped, for example when it has been idle for too long and is probably not needed right now (workload-induced shutdown), or when the whole system is stopped (administrative shutdown).</li> <li><code class="code">system_shutdown</code> is another shutdown-related notification. It is only emitted if the whole Netplex system is going to be stopped. In this case, all containers first receive the <code class="code">system_shutdown</code> notifications, so they can prepare the real shutdown that will happen soon. At the time the <code class="code">system_shutdown</code> is emitted, the whole system is still up and running, and so every action is still possible. Only after all containers have finished their <code class="code">system_shutdown</code> callbacks, the real shutdown begins, i.e. <code class="code">shutdown</code> notifications are sent out.</li> <li><code class="code">global_exception_handler</code> is called for exceptions falling through to the container, and is the last chance to catch them.</li> </ul> Because of the instances in the controller and the containers it is usually a bad idea to store state in the processor object. <p> If multi-threading is used instead of multi-processing, there is only one instance of the processor that is used in the controller and all containers. <p> <h2 id="defproc">Defining Custom Processors</h2> <p> Using predefined processor factories like <a href="Nethttpd_plex.html#VALnethttpd_factory"><code class="code">Nethttpd_plex.nethttpd_factory</code></a> is very easy. Fortunately, it is not very complicated to define a custom adapter that makes an arbitrary network service available as Netplex processor. <p> In principle, you must define a class for the type <a href="Netplex_types.processor-c.html"><code class="code">Netplex_types.processor</code></a> and the corresponding factory implementing the type <a href="Netplex_types.processor_factory-c.html"><code class="code">Netplex_types.processor_factory</code></a>. To do the first, simply inherit from <a href="Netplex_kit.processor_base-c.html"><code class="code">Netplex_kit.processor_base</code></a> and override the methods that should do something instead of nothing. For example, to define a service that outputs the line "Hello world" on the TCP connection, define: <p> <pre class="codepre"><code class="code"> class hello_world_processor : processor = let empty_hooks = new Netplex_kit.empty_processor_hooks() in object(self) inherit Netplex_kit.processor_base empty_hooks method process ~when_done container fd proto_name = Unix.clear_nonblock fd; let ch = Unix.out_channel_of_descr fd in output_string ch "Hello world\n"; close_out ch; when_done() method supported_ptypes = [ `Multi_processing; `Multi_threading ] end </code></pre> <p> The method <code class="code">process</code> is called whenever a new connection is made. The <code class="code">container</code> is the object representing the container where the execution happens (<code class="code">process</code> is always called from the container). In <code class="code">fd</code> the file descriptor is passed that is the (already accepted) connection. In <code class="code">proto_name</code> the protocol name is passed - here it is unused, but it is possible to process the connection in a way that depends on the name of the protocol. <p> Note that the file descriptors created by Netplex are in non-blocking mode. It is, however, possible to switch to blocking mode when this is more appropriate (<code class="code">Unix.clear_nonblock</code>). <p> The argument <code class="code">when_done</code> is very important. It <b>must</b> be called by <code class="code">process</code>! For a synchronous processor like this one it is simply called before <code class="code">process</code> returns to the caller. <p> For an asynchronous processor (i.e. a processor that handles several connections in parallel in the same process/thread), <code class="code">when_done</code> must be called when the connection is fully processed. This may be at any time in the future. <p> The class <code class="code">hello_world_processor</code> can now be turned into a factory: <p> <pre class="codepre"><code class="code">class hello_world_factory : processor_factory = object(self) method name = "hello_world" method create_processor ctrl_cfg cfg_file cfg_addr = new hello_world_processor end </code></pre> <p> As you see, one can simply choose a <code class="code">name</code>. This is the type of the <code class="code">processor</code> section in the configuration file, i.e. you need <p> <pre class="codepre"><code class="code"> ... service { name = "hello world sample"; ... processor { type = "hello_world" }; ... } ... </code></pre> <p> to activate this factory for a certain service definition. Of course, the instantiated <code class="code">hello_world_factory</code> must also be passed to <a href="Netplex_main.html#VALstartup"><code class="code">Netplex_main.startup</code></a> in order to be available at runtime. <p> The <code class="code">create_processor</code> method simply creates an object of your class. The argument <code class="code">ctrl_cfg</code> is the configuration of the controller (e.g. you find there the name of the socket directory). In <code class="code">cfg_file</code> the object is passed that accesses the configuration file as tree of parameters. In <code class="code">cfg_addr</code> the address of the <code class="code">processor</code> section is made available, so you can look for additional configuration parameters. <p> You may wonder why it is necessary to first create <code class="code">empty_hooks</code>. The hook methods are often overridden by the user of processor classes. In order to simplify this, it is common to allow the user to pass a hook object to the processor object: <p> <pre class="codepre"><code class="code"> class hello_world_processor hooks : processor = object(self) inherit Netplex_kit.processor_base hooks method process ~when_done container fd proto_name = ... method supported_ptypes = ... end </code></pre> <p> Now, the user can simply define hooks as in <p> <pre class="codepre"><code class="code">class my_hooks = object(self) inherit Netplex_kit.empty_processor_hooks() method post_start_hook container = ... end </code></pre> <p> and pass such a hook object into the factory. <p> <h2 id="workload">Workload Management</h2> <p> Workload managers decide when to start new containers and when to stop useless ones. The simplest manager is created by the <a href="Netplex_workload.html#VALconstant_workload_manager_factory"><code class="code">Netplex_workload.constant_workload_manager_factory</code></a>. The user simply defines how many containers are to be started. In the config file this is written as <p> <pre class="codepre"><code class="code"> workload_manager { type = "constant"; threads = <n>; } </code></pre> <p> where <code class="code"><n></code> is the number of containers > 0. Often this manager is used to achieve n=1, i.e. to have exactly one container. An example would be a stateful RPC server where it is important that all network connections are handled by the same process. (N.B. n=1 for RPC servers does not enforce that the connections are serialized because Ocamlnet RPC servers can handle multiple connections in parallel, but of course it is enforced that the remote procedures are invoked in a strictly sequential way.) <p> If n>1, it is tried to achieve that all containers get approximately the same load. <p> If processes die unexpectedly, the constant workload manager starts new components until the configured number of processes is again reached. <p> The dynamic workload manager (created by <a href="Netplex_workload.html#VALdynamic_workload_manager_factory"><code class="code">Netplex_workload.dynamic_workload_manager_factory</code></a>) is able to start and stop containers dynamically. There are a few parameters that control the manager. A "thread" is here another word for a started container. A "job" is an established network connection. Using this terms, the task of the workload manager is to decide how many threads are needed to do a varying number of jobs. The parameters now set how many jobs every thread may execute, and how quickly new threads are created or destroyed to adapt the available thread capacity to the current job load. <p> If the service processor can only accept one network connection after the other (like Nethttpd_plex), the only reasonable setting is that there is at most one job per thread. If one configures a higher number in this case, unaccepted network connections will queue up resulting in poor performance. <p> If the service processor can handle several connections in parallel it is possible to allow more than one job per thread. There is no general rule how many jobs per thread are reasonable, one has to experiment to find it out. In this mode of having more than one job per thread, Netplex even allows two service qualities, "normal" and "overload". If possible, Netplex tries to achieve that all containers deliver normal quality, but if the load goes beyond that, it is allowed that containers accept more connections than that. This is called an overload situation. Often it is better to allow overload than to refuse new connections. <p> The dynamic workload manager is enabled by the section <p> <pre class="codepre"><code class="code"> workload_manager { type = "dynamic"; ... parameters, see below ... } </code></pre> <p> The required parameters are: <p> <ul> <li><code class="code">max_threads</code>: How many containers can be created at maximum for this service.</li> <li><code class="code">max_jobs_per_thread</code>: How many jobs every container can execute at maximum. The upper limit for the number of jobs is thus <code class="code">max_threads * max_jobs_per_thread</code>.</li> <li><code class="code">min_free_job_capacity</code>: This parameter controls how quickly new containers are started when the load goes up. It is tried to ensure that there are as many containers so this number of jobs can be additionally performed. This parameter must be at least 1.</li> <li><code class="code">max_free_job_capacity</code>: This parameter controls how quickly containers are stopped when the load goes down. It is tried to ensure that unused containers are stopped so the capacity for additional jobs is not higher than this parameter. This parameter must be greater or equal than <code class="code">min_free_job_capacity</code>.</li> </ul> In order to configure the overload mode: <p> <ul> <li><code class="code">recommended_jobs_per_thread</code>: The number of jobs a container can do with normal service quality. A higher number is considered as overload.</li> </ul> The effect of this parameter is that it is avoided that a container gets more jobs than recommended as long as possible. <p> Another parameter is: <p> <ul> <li><code class="code">inactivity_timeout</code>: If a container idles longer than this number of seconds and is not needed to ensure <code class="code">min_free_job_capacity</code> it is shut down. Defaults to 15 seconds.</li> </ul> More documentation is available in <a href="Netplex_admin.html#workload"><i>Configuration: The workload_manager section</i></a>. <p> <h2 id="messaging">Messaging</h2> <p> There are two kinds of messages one can send to Netplex containers: normal messages come from another Netplex container, and admin messages are sent using the <code class="code">netplex-admin</code> command. <p> Messages have a name and a (possibly empty) list of string parameters. They can be sent to an individual receiver container, or to a number of containers, even to all. The sender does not get an acknowledgment when the messages are delivered. <p> Messages can e.g. be used <p> <ul> <li>to signal that internal state is output to log files in order to debug a special situation</li> <li>to enable or disable special features of the running system</li> <li>to flush caches</li> </ul> and for other comparably simple communication needs. <p> In order to receive a normal message, one must define the <code class="code">receive_message</code> method in the processor object, and to receive an admin message, one must define the <code class="code">receive_admin_message</code> method. <p> A normal message is sent by the container method <code class="code">send_message</code>. The receiver is identified by the service name, i.e. all containers with the passed name get the message. The name may even contain the wildcard <code class="code">*</code> to select the containers by a name pattern. <p> An admin message is sent using the <code class="code">netplex-admin</code> command. <p> There are a few predefined messages understood by all containers. See <a href="Netplex_admin.html#admin"><i>The netplex-admin command</i></a> for a list. <p> In general, messages starting with "netplex." are reserved for Netplex itself. <p> <h2 id="logging">Logging</h2> <p> Log messages can be written in the containers. The messages are first sent to the controller where they are written to stderr, to files, or to any object of the type <a href="Netplex_types.logger-c.html"><code class="code">Netplex_types.logger</code></a>. That the messages are first sent to the controller has a lot of advantages: The messages are implicitly serialized, no locking is needed, and it is easy to support log file rotation. <p> In order to write a log message, one needs the container object. The module <code class="code">Netplex_cenv</code> always knows the container object of the caller, to get it: <p> <pre class="codepre"><code class="code">let cont = Netplex_cenv.self_cont() </code></pre> <p> If you call <code class="code">self_conf</code> outside a container, the exception <a href="Netplex_cenv.html#EXCEPTIONNot_in_container_thread"><code class="code">Netplex_cenv.Not_in_container_thread</code></a> is raised. This is e.g. the case if you call it from the <code class="code">pre_start</code> or <code class="code">post_finish</code> callbacks. <p> Logging is now done by <p> <pre class="codepre"><code class="code">let cont = Netplex_cenv.self_cont() in cont # log level message </code></pre> <p> where <code class="code">level</code> is one of <code class="code">`Debug</code>, <code class="code">`Info</code>, <code class="code">`Notice</code>, <code class="code">`Warning</code>, <code class="code">`Err</code>, <code class="code">`Crit</code>, <code class="code">`Alert</code>, <code class="code">`Emerg</code>, and <code class="code">message</code> is a string. The levels are the same as for syslog. <p> You can also call <a href="Netplex_cenv.html#VALlog"><code class="code">Netplex_cenv.log</code></a> and <a href="Netplex_cenv.html#VALlogf"><code class="code">Netplex_cenv.logf</code></a>, which simply use <code class="code">self_cont</code> to get the container and call its <code class="code">log</code> method to write the message. <p> The config file controls what to do with the log messages. The easiest way is to send all messages to stderr: <p> <pre class="codepre"><code class="code"> controller { max_level = "debug"; (* Log level *) logging { type = "stderr"; (* Log to stderr *) } }; </code></pre> <p> Further types of logging are documented in <a href="Netplex_admin.html#controller"><i>Configuration: The controller section</i></a>. <p> <h2 id="debug">Debug logging</h2> <p> There are various built-in debug logging streams:<ul> <li><a href="Netplex_container.Debug.html#VALenable"><code class="code">Netplex_container.Debug.enable</code></a>: Logs the perspective of the container. Logged events are e.g. when connections are accepted, and when user-defined hook functions are invoked. These messages are quite interesting for debugging user programs.</li> <li><a href="Netplex_controller.Debug.html#VALenable"><code class="code">Netplex_controller.Debug.enable</code></a>: Logs the perspective of the controller. The events are e.g. state changes, when containers are started, and scheduling decisions. This is less interesting to users, but might nevertheless worth activating it.</li> <li><code class="code">Netplex_workload.Debug.enable</code>: Outputs messages from the workload manager.</li> </ul> For these messages, the mechanism of <a href="Netlog.Debug.html"><code class="code">Netlog.Debug</code></a> is used - which has the advantage that messages can also be generated when no Netplex logger is available. <p> <h2 id="mt">Compatibility with multi-threading</h2> <p> As already noted above, it is possible to use Netplex with two concurrency models, namely multi-threading and multi-processing. In the first case, for every container a new thread is started, and in the second case, a new process. <p> You should know that multi-threading in OCaml has some limitations. In particular, it is not possible to exploit more than one CPU core of the system (although the threads are real kernel threads). Because of this, multi-processing is also supported by Netplex. It does not have this deficiency, because every process executes an independent OCaml runtime. <p> Nevertheless, it can be quite useful to just start helper threads, even if the main concurrency model is multi-processing. The question is how to do, and where the traps are. <p> First, one warning ahead: Once you have started threads, it is no longer safe to spawn new processes. This is a general difficulty of the POSIX system API, and cannot be worked around in OCaml programs (to some extent it is possible in C). The immediate consequence is that you must not start threads in the controller process (assumed you have multi-processing). The controller process continuously forks new container processes, and having threads could be deadly for it. For the API exposed by Netplex this means that doing such is not supported at all - all controller-specific APIs assume that they are only called from the same thread. <p> Ok, so you just don't this. However, is it allowed to have additional threads in containers? This is in deed not problematic. Just be sure that you start these threads after the container process has been created, e.g. from <code class="code">post_start_hook</code>. In order to make life simpler, a number of APIs have now been made thread-safe (since Ocamlnet-3.5): <p> <ul> <li>All methods of container objects: <a href="Netplex_types.container-c.html"><code class="code">Netplex_types.container</code></a></li> <li>All functions in <a href="Netplex_cenv.html"><code class="code">Netplex_cenv</code></a></li> <li>All synchronization helpers in <a href="Netplex_semaphore.html"><code class="code">Netplex_semaphore</code></a>, <a href="Netplex_mutex.html"><code class="code">Netplex_mutex</code></a>, and <a href="Netplex_sharedvar.html"><code class="code">Netplex_sharedvar</code></a></li> </ul> This means you can call these methods and functions without any additional synchronization effort. <p> Note that all extra threads are implicitly killed when the container process finishes. If you don't like this and want to terminate them in a more controlled manner, you can do this from the <code class="code">pre_finish_hook</code>. If your main concurrency model is multi-threading, though, no such killing will occur - your helper threads will simply continue running when the container is finished. (Unfortunately, this difference between the two models is unavoidable.) <p> <h1 id="rpc_netplex">Netplex RPC systems</h1> <p> A short description how to build systems of RPC services is given in <a href="Rpc_intro.html#rpc_netplex"><i>Netplex RPC systems</i></a>. <br> </body></html>