Basic Binary IPC
This system provides an interface for performing inter process communication using streams. The interface follows a non-blocking pattern which allows applications to communicate either synchronously or asynchronously.
Table of Contents
1 Installation
The prerequisite systems for this extension are
These libraries can be obtained using Quicklisp.
Access to the operating system's networking API is performed using the
foreign function interface provided by CFFI
. The operating systems
that are supported are:
- Apple OSX (Tested with version 10.7.5, 32 bit and 64 bit)
- FreeBSD (Tested with version 9.1, 32 bit and 64 bit)
- Linux (Tested with Debian squeeze, 32 bit and 64 bit)
- Microsoft Windows (Tested with Windows 8, 64 bit MinGW)
The system can be loaded in to the current Lisp environment via ASDF.
(asdf:load-system "basic-binary-ipc")
The tests for the system can be executed via ASDF as well.
(asdf:test-system "basic-binary-ipc")
All tests should succeed.
2 Introduction
The goal of this system is to provide an easy method for establishing a communication channel known as a stream. A stream is a bidirectional, full duplex communication channel with the guarantee that the order in which data is read is the same as the order it was written and that the data written is the same as the data read.
The term stream used in this system is not to be confused with the Common Lisp stream. The two terms are distinct. The main reason for this distinction is that every operation in this system is non-blocking and changes in state are obtained via a polling interface. The benefit of the polling strategy is that it does not require callbacks which allows the library to be used in either synchronous or asynchronous settings.
3 Namespaces
An application must consider the namespace in which two processes are to communicate. A namespace represents the lower level protocol that the stream is constructed on top of. This library can construct streams in the following namespaces
- IPv4 namespace
- The Internet protocol version 4 namespace using the transmission control protocol.
- Local namespace
- Local or Unix domain sockets for communicating between processes located on the same physical device.
Once a namespace is chosen, the functions for that namespace determine how to initiate a connection to a server or start a new server that is capable of accepting incoming connections. Both of these tasks involve creating a specialised socket.
4 Sockets
A socket represents an operating system resource that is needed to
perform communication. Once a socket is no longer required, its
resources must be released using the CLOSE-SOCKET
function.
(defgeneric close-socket (object))
A socket has been closed if the predicate SOCKET-CLOSED-P
is
non NIL
.
Objects implementing the socket protocol inherit from the SOCKET
class.
This library provides protocols for two different types of sockets, the stream server socket and the stream socket.
Lastly, this library adheres to the philosophy that errors should only
be signaled in exceptional circumstances. If an exceptional
circumstance occurs during any operation then an error of type
SOCKET-ERROR
is signaled. Some operations are capable of signaling
other error types.
4.1 Stream Servers
The stream server protocol represents sockets that listen for
connections from clients. Accepting a new connection is performed
using the ACCEPT-CONNECTION
function.
(defgeneric accept-connection (server))
This function returns a new object that implements the stream socket
protocol. The ACCEPT-CONNECTION
function will signal a
NO-CONNECTION-AVAILABLE-ERROR
condition if there is no client
waiting for acknowledgment from the server.
It should be noted that the returned stream socket is dissociated from
the server that created the connection i.e. calling CLOSE-SOCKET
on
a server will not terminate any of its accepted connections.
The predicate CONNECTION-AVAILABLE-P
returns non NIL
if the server
has connections available.
(defgeneric connection-available-p (server))
All objects implementing the stream server protocol inherit from the
STREAM-SERVER
class.
4.2 Stream Sockets
A stream socket represents a stream that is either established or in the process of being established.
There are two ways to create a stream socket. The first is to use a
namespace specific function to initiate a connection to a server. The
second is using the ACCEPT-CONNECTION
function on a server object.
Once a stream socket has been created, it immediately starts to
establish the stream by negotiating with the remote socket. This
negotiation may take a significant amount of time, and in some cases
may fail to complete e.g. a network failure, an overloaded server or
an unreachable host. This period of uncertainty is modelled by the
future connection protocol and is implemented by all instances of the
STREAM-SOCKET
class.
The predicate
(defgeneric determinedp (future-connection))
can be used to determine if the the operating system has finished
trying to negotiate a new stream connection. The result of the
negotiation can be obtained using the predicates CONNECTION-FAILED-P
or CONNECTION-SUCCEEDED-P
.
(defgeneric connection-failed-p (future-connection)) (defgeneric connection-succeeded-p (future-connection))
If the connection is successful, the stream protocol outlined next can be used to send and receive data over the newly created stream.
The function DATA-AVAILABLE-P
can be used to determine if there is
data that can be read immediately from the stream using the function
READ-FROM-STREAM
.
(defgeneric data-available-p (stream)) (defgeneric read-from-stream (stream buffer &key start end peek))
The return value of READ-FROM-STREAM
is the number of bytes written
to BUFFER
. This can be either the number of bytes that are
immediately available for reading or the value (- END START)
. If
PEEK
is T
then READ-FROM-STREAM
obtains data from the stream
without removing it from the stream. i.e. the next call to
READ-FROM-STREAM
will contain exactly the same data.
Note that it is possible for READ-FROM-STREAM
to signal an error
despite DATA-AVAILABLE-P
having returned true! This is the nature of
communication channels where the path connecting the two stream
sockets is governed by a large number of interacting agents.
Writing data to the stream is performed using the function
WRITE-TO-STREAM
.
(defgeneric write-to-stream (stream buffer &key start end))
The return value of WRITE-TO-STREAM
is the number of bytes
written. If no bytes can be written then this function returns
0
.
The predicate READY-TO-WRITE-P
can be used to determine if data can
be written immediately.
(defgeneric ready-to-write-p (stream))
Please be aware that the function WRITE-TO-STREAM
can still fail
even if READY-TO-WRITE-P
returned non NIL
.
Last but not least, the predicate REMOTE-DISCONNECTED-P
can be used
to determine if the connection between the two stream sockets has been
severed.
(defgeneric remote-disconnected-p (stream))
5 IPv4
The IPv4 namespace is the namespace that is the foundation of the Internet. The Transmission Control Protocol (TCP) is the underlying protocol used to establish a stream in the IPv4 namespace. A stream socket in the IPv4 namespace is uniquely defined by a local host address, a local port number, a remote host address and a remote port number.
The function MAKE-IPV4-TCP-SERVER
can be used to create a IPv4
stream server that listens for incoming connections to PORT
on the
host ADDRESS
.
(defun make-ipv4-tcp-server (host-address port &key reuse-socket-address backlog))
The number PORT
must be of type (UNSIGNED-BYTE 16)
and the value
of HOST-ADDRESS
must be a string in dotted-quad format. e.g
127.0.0.1
or one of the constants:
+IPV4-LOOPBACK+
- The address of the localhost IPv4 network interface.
+IPV4-ANY+
- All IPv4 network interfaces for the host.
The value returned from MAKE-IPV4-TCP-SERVER
is an instance of type
IPV4-TCP-SERVER
and adheres to the stream server protocol. The
object returned also implements the following functions
(defgeneric host-address (ipv4-tcp-server)) (defgeneric port (ipv4-tcp-server))
If the PORT
argument to MAKE-IPV4-TCP-SERVER
is zero, than the
operating system will automatically assign a non-zero port to the
server. The assigned port can be retrieved using the PORT
generic
function defined above.
The function CONNECT-TO-IPV4-TCP-SERVER
creates a stream socket that
connects to the TCP/IPv4 server listening on the socket address
defined by HOST-ADDRESS
and PORT
.
(defun connect-to-ipv4-tcp-server (host-address port &key local-host-address local-port))
The arguments LOCAL-HOST-ADDRESS
and LOCAL-PORT
can be used to
specify which local host address and port number should be used to
connect to the server. If these are not specified, then a random port
number and an appropriate host address are chosen.
Stream sockets obtained by using ACCEPT-CONNECTION
or
CONNECT-TO-IPV4-TCP-SERVER
are of type IPV4-TCP-STREAM
. This class
extends the stream socket protocol with the following functions
(defgeneric local-host-address (stream)) (defgeneric local-port (stream)) (defgeneric remote-host-address (stream)) (defgeneric remote-port (stream))
The function CONNECT-TO-IPV4-TCP-SERVER
only accepts host addresses
in dotted quad format (i.e. 127.0.0.1
). The function
RESOLVE-IPV4-ADDRESS
can be used to obtain a host address for a
given domain name.
(defun resolve-ipv4-address (hostname))
If successful, a string containing the host address is returned. If no
host address exists for the given HOSTNAME
than NIL
is
returned. An ERROR
is signalled if RESOLVE-IPV4-ADDRESS
fails for
any other reason.
The reader should be aware that RESOLVE-IPV4-ADDRESS
is a blocking
operation i.e. the current thread will block until the address has
been retrieved.
6 Local
This section outlines how to create a communication channel between two processes running on the same physical machine. Local stream sockets are defined by a filesystem pathname to a server. Unlike IPv4, the Local namespace does not have the ability to determine if two stream sockets refer to the same stream.
The function MAKE-LOCAL-SERVER
creates a server that is capable of
accepting incoming connections on the local namespace.
(defun make-local-server (pathname &key (backlog 5) (delete-on-close t)))
The PATHNAME
argument specifies the filesystem pathname where the
server listens for connections. This pathname must not exist prior to
calling MAKE-LOCAL-SERVER
. A non -NIL
argument for
DELETE-ON-CLOSE
specifies that CLOSE-SOCKET
should delete
PATHNAME
when the server is closed.
The object returned by MAKE-LOCAL-SERVER
is an instance of the class
LOCAL-SERVER
and implements the stream server protocol. It also
implements the function LOCAL-PATHNAME
which returns the PATHNAME
argument to MAKE-LOCAL-SERVER
.
(defgeneric local-pathname (local-socket))
All stream objects returned by ACCEPT-CONNECTION
are instances of
the class LOCAL-STREAM
.
Connections to local servers can be initiated using the function
CONNECT-TO-LOCAL-SERVER
.
(defun connect-to-local-server (pathname &key))
where PATHNAME
is the filesystem pathname of the server. The object
returned is an instance of type LOCAL-STREAM
which implements the stream socket protocol and the LOCAL-PATHNAME
function mentioned
above. If no server exists at PATHNAME
, then a
NO-LOCAL-SERVER-ERRROR
is signalled.
7 Polling
All functions outlined above work directly on the current state of the
socket. The function POLL-SOCKET
allows an application to block
until an object changes state. e.g. data is now available or the
remote host has disconnected.
(defgeneric poll-socket (socket socket-events timeout))
The TIMEOUT
argument specifies how long to wait (in seconds) until a
state change occurs on the socket. A value of :IMMEDIATE
indicates
that POLL-SOCKET
should not wait and return the current state. A
value of :INDEFINITE
means to wait until an event occurs.
The SOCKET-EVENTS
argument tells the POLL-SOCKET
function what
event(s) to wait for. This argument is socket specific and can be
either a symbol or a list of symbols. The symbols accepted correspond
to the predicate functions for each socket object. For example, for
stream server objects, only the symbol CONNECTION-AVAILABLE-P
is
accepted. For stream socket objects, the symbols DETERMINEDP
,
CONNECTION-SUCCEEDED-P
, CONNECTION-FAILED-P
, DATA-AVAILABLE-P
,
READY-TO-WRITE-P
and/or REMOTE-DISCONNECTED-P
are all permitted.
The return value of POLL-SOCKET
is either a SYMBOL
, a list of
SYMBOLS
or NIL
. A symbol is returned only if SOCKET-EVENTS
is a
symbol. A value of NIL
indicates that no events that match the
criteria of SOCKET-EVENTS
has occurred. One should not conclude that
TIMEOUT
seconds has transpired when POLL-SOCKET
returns NIL
. It
is possible for POLL-SOCKETS
to return with a value of NIL
without
timing out.
An extremely useful variant of POLL-SOCKET
is the POLL-SOCKETS
function.
(defun poll-sockets (all-sockets all-sockets-events timeout))
This function acts like the following
(multiplexing-collect (poll-socket socket-1 socket-1-events 10) (poll-socket socket-2 socket-2-events 10) ..)
where the hypothetical function MULTIPLEXING-COLLECT
executes all
POLL-SOCKET
calls simultaneously and stops them all as soon as an
event occurs on any socket. The return value is a list containing the
results of performing POLL-SOCKET
on that socket alone. For example
(destructuring-bind (s1-result s2-result s3-result) (poll-sockets (list s1 s2 s3) (list s1-events s2-events s3-events) 10) ;; do stuff with results )
7.1 Polling many sockets
One draw back of POLL-SOCKETS
is that every call to POLL-SOCKETS
requires traversing N
sockets and their corresponding events. For
server applications this can be problematic as N
is typically large,
N
changes frequently and POLL-SOCKETS
is called repeatedly until
the server stops. In order to handle this situation, the POLLER
protocol is provided.
~POLLER~s represent an operating system resource that monitors many
sockets. Creating a poller object is performed using the function
MAKE-POLLER
.
(defun make-poller ())
Waiting for events to occur with a poller is performed with the
function WAIT-FOR-EVENTS
.
(defgeneric wait-for-events (poller timeout))
The return value of WAIT-FOR-EVENTS
is NIL
if no events occurred
or a list where each item is a list containing two entries, SOCKET
and SOCKET-EVENTS
. The TIMEOUT
argument can be a positive value
representing seconds, or it can be one of :IMMEDIATE
or
:INDEFINITE
. Like POLL-SOCKET
and POLL-SOCKETS
, a return value
of NIL
does not mean that the function timed out.
Adding a socket to be monitored by a POLLER
is performed using the
MONITOR-SOCKET
function.
(defgeneric monitor-socket (poller socket socket-events))
where SOCKET
is the socket to add and SOCKET-EVENTS
contain the
events to wait for.
Changing the set of events to be monitored by the poller is performed
using the (SETF MONITORED-EVENTS)
function.
(defgeneric (setf monitored-events) (socket-events poller socket))
The current set of events being monitored can be accessed using the
MONITORED-EVENTS
function.
(defgeneric monitored-events (poller socket))
Removing a socket from a POLLER
is achieved with the function
UNMONITOR-SOCKET
.
(defgeneric unmonitor-socket (poller socket))
The current set of sockets being monitored can be retrieved using the
function MONITORED-SOCKETS
.
(defgeneric monitored-sockets (poller))
When a poller is no longer required, the function CLOSE-POLLER
must
be called in order to release the operating system resource.
(defgeneric close-poller (poller))
Finally, all objects implementing the above POLLER
protocol inherit
from the POLLER
class.
8 Acknowledgments
Thank you to the people who develop Solarized CSS and Org mode.
Lastly, big thanks to the Lisp community for all their excellent work.