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.