Not everything works in the same way for all operating systems. This documents describes the different levels of support for the various platforms.
Ocamlnet has been primarily developed for POSIX systems. Most libraries should work on all systems, but there are also some more special features that are designed for certain systems only.
On POSIX systems, the
select() system call is not used anymore. As
a general-purpose replacement Ocamlnet favors
poll(). In future
versions of Ocamlnet, there will also be support for the improved
poll() some systems provide, such as
epoll on Linux,
kqueue on BSD, and
/dev/poll on Solaris. Ocamlnet is already
prepared for this change.
The reason for banning
select() are the limited capabilities of this
API. Especially, it cannot handle file descriptors whose numeric
values exceed a system-dependent limit. Although this limit is quite
high (usually 1024) there are applications that need more descriptors.
(Note that it is not sufficient to simply increase the maximum number
of descriptors a process can have - the
select() call is still
restricted in the mentioned sense, and this seems to be unfixable.)
poll() interface does not have this problem.
poll() interface is made accessible from Ocaml in
One of the main difference of the POSIX platforms compared with
Windows is that there is the
fork() system call. Ocamlnet provides
multi-processing support in the
netplex library (see
This makes it easy to manage a set of child processes that are used
as parallel running "workers" (e.g. for accepting network connections).
There is a registry of user functions to be run after a new child
process has been forked off (see
Netsys_posix.register_post_fork_handler). The intention is that the
child can close descriptors it would otherwise share with the parent.
One should note that there is a basic incompatibility between code
fork() to achieve parallelism and multi-threading. The
problem is that the state of synchronization objects like mutexes is
fork(). In the C language, one can try to define
special handlers that run before/after the
fork() to fix up such
objects. There is no such possibility in Ocaml (the mentioned
registry cannot be used for this purpose). One simply should not call
fork() while there is more than one thread. It is of course allowed
to start threads in the sub processes.
For the multi-processing capability of
netplex this means that one
must not create threads in the master process (from which the worker
processes are forked off). There is no such restriction for the
Starting programs as sub processes
There is now extended support for starting subprograms. First, there
Netsys_posix.spawn which provides a comfortable way for starting
programs. The interface is patterned after the
call although the implemention is not using
posix_spawn() yet, but
(In a later version of Ocamlnet it is planned to give the user the
choice of using
posix_spawn() as the underlying system interface.
On some OS,
posix_spawn() is highly optimized and significantly
Second, it is now possible to let Ocamlnet watch for the termination
of child processes:
Netsys_posix.watch_subprocess. This function
arranges that the
SIGCHLD signal is caught, and that the process
termination is reported as a file descriptor event. By means of
careful programming this even works for multi-threaded applications
(where signal handling is notoriously difficult).
POSIX systems usually already provide RPC functionality, as some
deeply-integrated networking protocols like NFS are based on it.
Traditionally, there is a program called
portmapper that acts as
registry of RPC services running on a machine. Ocamlnet can register
portmapper, and it can also look up services there.
On some systems, there is a newer version of
rpcbind is backward-compatible, and also
There is some problem, though, on systems that also provide the XTI
networking API which is a replacement for the socket API. Whereas
TCP/IP can be used with both XTI and sockets, the local network
connections the API's provide are incompatible: A local XTI endpoint
cannot be contacted over the socket API, and a local socket endpoint
(i.e. a Unix domain socket) cannot be contacted over XTI. There is
the helper library
Rpc_xti_client that allows to connect to a local
On Cygwin Ocamlnet should behave like on a POSIX machine.
General remark: The Win32 port is still very experimental, and far from being bug-free! Development progresses very slowly. Please don't expect wonders.
There is now a lot of support for Win32, even for asynchronous network programming. The minimum Windows version is Windows XP SP2. One should also mention that some functions are only supported when the Ocaml program is compiled as multi-threaded program, as sometimes helper threads are needed to emulate a certain behavior. For example, Win32 limits the number of file handles to 64 per thread when one watches them for events. Ocamlnet includes support to overcome this limit by creating helper threads as needed.
As a substitute for Unix Domain sockets, Ocamlnet provides access to
Win32 named pipes. These are, for security reasons, restricted to
local connection (within the same machine). As named pipes exist in a
special file system, one cannot create named pipes in arbitrary
directories (unlike Unix Domain sockets). As a workaround, it is also
supported to create named pipes with random names, and to write these
names into text files. A number of functions that used to expect Unix
Domain socket files as input can now also deal with these text files,
and will automatically map these files to the referenced named pipes
The RPC implementation supports TCP/IP sockets and named pipes, both for clients and for servers. As for POSIX, the clients and servers can be programmed in an asynchronous way.
Netplex is supported, but only for multi-threaded containers. The multi-processing containers are not available. Netplex can manage both TCP connections and named pipes.
The Shell library works, too. As for POSIX, it is possible to create complex pipelines between the started shell commands, and it is possible to read the output of and to provide input to the commands at the same time. All asynchronous command execution features are available! The only limitation is that one can redirect only stdin/stdout/stderr of the started commands, and not arbitrary descriptors as for POSIX (this is a restriction of Win32).
The Equeue library is supported. The GTK- and TclTk-specific extensions do not work, however. The SSL add-on is untested.
The Netshm library is only partially supported: The managed memory object must be file-backed and cannot (yet) live in RAM-only shared memory.
The Nethttpd library is fully supported.
The missing Win32 calls are made available via
accessible objects are:
Netsys_win32, pipe server objects are emulated to simplify this for user code, so that pipe servers look very much like socket servers.
Netsys_win32provides helper threads allowing one to read from an arbitrary file handle in an asynchronous way, even if Win32 does not support that for the type of handle. This is intended for reading from anonymous pipes (as returned by
A central idea here is the concept of "proxy descriptors". Many of the
mentioned objects have a complex inner structure - e.g. a pipe server
can mean an open number of Win32 file handles. However, it is intended
to make these objects look like in POSIX, and that means that there is
only a single descriptor referencing the object. The proxy descriptors
are additional file descriptors Ocamlnet allocates only for the purpose
of referencing these objects. For example, a pipe server is mainly
represented by the type
Netsys_win32.w32_pipe_server, and there are
a lot of functions dealing with such values, e.g.
Netsys_win32.pipe_shutdown_server for shutting a server down.
Of course, a
Netsys_win32.w32_pipe_server is not a file descriptor,
but a complex Ocaml record. The question is now how to pass such an
object to functions that only accept file descriptors as input? The
solution looks simple although the implementation causes a lot of
headache: A special file descriptor is allocated, and there is a global
table mapping these descriptors to the real objects. For instance, you
can get the proxy descriptor of a pipe server by calling
let (fd : Unix.file_descr) = Netsys_win32.pipe_server_descr psrv
for a pipe server object
psrv. You can pass
fd around, and when
a called function requires the pipe server object again, one can
fd back by:
let (psrv : Netsys_win32.w32_pipe_server) = Netsys_win32.lookup_pipe_server fd
Like other descriptors,
fd must be closed after use. The tricky part
of the implementation is that unreferenced proxy descriptors are
detected, and that the entry of the global mapping table is deleted
As mentioned, Ocamlnet supports asynchronous I/O for the supported
Win32 objects. For some objects, Win32 has built-in support in the
form of so-called overlapped I/O. This kind of I/O is, unfortunately,
different than what POSIX provides (
poll-style I/O). The analysis
shows that the main difference is that in Win32 one has to start an
I/O operation in order to asynchronously check whether and when it is
finally possible, whereas POSIX allows one to check the possibility of
I/O before one starts the operation. Ocamlnet hides this difference in
Netsys_win32 wrapper by providing additional buffering. The
price is, however, that the generic
write calls (or
WriteFile in Win32) can no longer be used.
As a substitute, Ocamlnet provides for each kind of object a special
set of read and write operations, e.g.
for named pipes.
For simplicity of user code, there are also generalized read/write
Netsys.gwrite. These work for
all descriptors supported by
Unix.write, plus for
sockets, plus for all of the mentioned Win32-specific objects for
which reading and writing are reasonable I/O operations.
If overlapping I/O is unavailable for a kind of file handle, it is still possible to use the input and output threads in order to do asynchronous I/O for these handles. An example of such a handle type are anonymous pipes. The threads are automatically started and initiate synchronous I/O operations when needed. The completion of the operations is signalled via condition variables to the caller, so that it is possible to provide an asynchronous API to the file handle.
Hints for portable programming
In order to read and write file descriptors, one should first get
the kind of descriptor
let st = Netsys.get_fd_style fd
Now, one can read and write to the file descriptor by calling
Netsys.gwrite (or by some of the provided
variants of these functions). These functions need
st as input,
and internally call the right system function to do the requested
In order to wait for a single descriptor, one can call one of the
Netsys functions doing so, e.g.
These functions block execution until the requested I/O operation
is possible, or until a timeout elapses.
If several descriptors need to be waited for, there is the
portable class type
Netsys_pollset.pollset. One can add several
descriptors to pollsets and wait until I/O operations for one of the
descriptors become possible. There are implementations for both POSIX
selects the best pollset implementation for "normal use".
For porting programs that are still written around
there is an emulation of
select on top of pollsets: