Socket library HOWTO
Hans Petter Jansson, hpj@teletopia.no
Release 1, 19981013

This document describes the socket facilities in libflux. It consists of a
general introduction and example programs putting them to use. This is not
intended as a reference, and does not cover all the details on functions
calls and return values.


1.0.0  Introduction

1.1.0  The socket structure

The socket structure contains all the information needed for connections -
peer address and name (cached), incoming and outgoing data buffers, status
fields, and the unix sockets themselves. Unlike Unix sockets, a libflux socket
lifetime is not determined by its connection. That is, the structure can be
re-used as many times as you like, for different connections, or for
re-establishing its previous connection.

Its use is very similar to that of the FILE abstract in standard C. The
socket abstract is called SOCK, and has many of the same I/O features and
functions.

To declare a pointer to a socket structure: SOCK *your_sock;

1.2.0  Socket Operations: Overview

Any program using sockets must start off with a call to sock_init(). This
sets up initial parameters and handlers for some signals.

1.2.1  Creating and Disposing of a Socket

A socket must be created by sock_open() before use. Remember that a socket
is typically either a server socket (listening for incoming connections on a
specified port) or a client socket (bound to any old port on your host). A
call to sock_open() takes care of all the initializing you need - allocating
memory for buffers and the socket structure itself, and binding the socket
to the port specified. It returns a pointer to a SOCK structure, or NULL if
it encountered an error (most likely inability to bind to the desired port).

Disposing of a socket is as simple as a call to sock_close(). This closes a
possibly open connection and frees up all memory otherwise associated with
the socket.

1.2.2  Cryptography

This is not fully worked out yet. Currently, the desired cryptography type
can be set on an opened (or even connected) socket with
sock_set_encryption_in() and sock_set_encryption_out(). This is prone to
change, however, as the more advanced schemes planned involve the
initialization and passing of keys (more parameters, and probably more and
different functions).

The "cryptography" currently implemented is not secure, and is only intended
to foil simple plaintext password snooping.

1.2.3  Client: Common Operations

After initialization, a client would connect the socket to another host. This
is accomplished by the sock_connect() function, which takes a dotted quad or
a fully qualified domain name, and destination port, as arguments. This
function returns TRUE when successful, or FALSE upon failure. In the latter
case, the reason can be read from the socket's status field, or a textual
error message can be generated by sock_status2str().

Whenever a connection is broken, sock_reconnect() can be called to try to
establish a connection to the previously connected host and port. It behaves
more or less like sock_connect(), except the cached host information is
used.

To disconnect (without closing) a socket, sock_disconnect() is called. The
socket may then be re-used in subsequenct calls to sock_connect().

1.2.4  Server: Common Operations

Typically, after opening one or more sockets, a server will want to listen
for incoming connections. sock_listen() does just that. It can listen to
several sockets at once, the last argument being NULL (denoting
end-of-list), for a specified amount of time (or forever). There is also
another version of this call, sock_alisten(), which lets you pass the socket
pointers in an array. The time interval is given in usecs, making it an
effective polling function (e.g. when a handler function needs to tend to
other tasks as well). The return value is a pointer to a spawned socket
(servicing a new incoming connection), or NULL if there was an error (signal)
or no connection within the specified interval.

A spawned socket knows about its parent, so which incoming socket was
connected to can be determined by accessing its sock->parent pointer (this
will probably be moved to a sock_get_parent() function, along with other
typical socket structure data extraction).

Next, the server will want to know about the client's identity. There are
two functions for this, and one call made solely for convenience.
sock_get_remote_ip() returns the remote host's dotted quad address (in ASCII
format), while sock_get_remote_name() looks up and returns its primary fully
qualified name, if available - otherwise, NULL. sock_get_remote_name_or_ip()
tries to look up the name, or, failing that, returns the dotted quad
instead. Names are cached, so repeated calls to these functions won't
produce further NS lookups.

1.2.5  Inter- and Intraprocess Pipes

Pipes are often used in servers, providing communication between a parent
process and subprocesses spawned to handle separate client connections, or
even within the same process. The sockets can be used for this purpose,
with the same structures and data transfer interface as with TCP/IP sockets.

Pipes are created with sock_pipe(). It returns a pointer to a SOCK, just as
would sock_open(). At this time, the pipe is looped back on itself - you can
write to it and read the same data right back.

To add to the excitement, you can now do a fork(). This forks off a child
process, leaving the parent and the child with their respective copies of
the SOCK. Now, the parent calls sock_pipe_parent() on the socket, and the
child does likewise with sock_pipe_child(), readying the sockets for
parent-child communication - data written by the parent will be read by the
child and vice versa. All data operations, including sock_poll() and
sock_wait() apply.

Calls such as sock_connect() or sock_listen() are invalid for SOCKs created
with sock_pipe().

1.2.6  Data transfer, polling and waiting

Data transfer functions are very similar to those used on standard clib
streams. At the core lie sock_read() and sock_write(), handling a specified
number of bytes (8-bit clean) from out of fixed-size arrays.

Other ways of exchanging data are via the sock_putc(), sock_getc(),
sock_puts(), sock_gets(), sock_printf() and sock_scanf() functions. You
should remember the syntax and behaviour from your clib.

It is important that you call sock_flush() whenever you want something sent.
Data may also be flushed automatically when buffers fill up (when data
cannot be flushed quickly enough, buffers can expand to a point, before
blocking the writer). A sock_flush() always blocks.

Polling is accomplished through sock_poll(), operating on a socket and
returning the number of bytes pending, -1 if there was a problem. It returns
immediately, possibly reading data (non-blocking) into the receive buffer.

When you want your process to block until data is received, use sock_wait(),
waiting for a specified number of usecs, or forever. You can wait for
several sockets at once, passing NULL as the final argument. A pointer to
the first socket receiving any data, or the socket with the most data
pending, is returned - NULL if the wait times out before anything happens.

2.0.0  Examples

2.1.0  Server Outline

void handle_request(SOCK *s)
{
  sock_printf(s, "You shall see the Lord of Life and Death.\n"
                 "You shall see Heaven in Hell.\n"
                 "You shall be blinded by light.\n"
                 "You shall see darkness.\n"
                 "God is God.\n");
  sock_flush(s);
  return;
}

int main()
{
  SOCK *s_parent, *s_child;
  int pid_child;

  sock_init();
  s_parent = sock_open(PORT, 0);

  if (s_parent)
  {
    for (;;)
    {
      s_child = sock_listen(0, s_parent, 0);  /* NULL ends list of parents. */
      if (s_child)
      {
        printf("Server: Got request on port %d, from %s.\n",
               s_child->parent->port_bound,
               sock_get_remote_name_or_ip(s_child));

        pid_child = fork();
        if (pid_child) sock_close(s_child, 0);  /* Parent closes child
                                                   sock. */
        else
        {
          sock_close(s_child->parent, 0);  /* Child closes parent sock. */
          handle_request(s_child);
          sock_close(s_child, 0);
          return(0);
        }
      }
    }
  }
  else fprintf(stderr, "Server: Unable to bind to local port.\n");

  return(0);
}

2.2.0  Client Outline

int main()
{
  SOCK *s;
  char *str;

  sock_init();
  s = sock_open(0, 0);

  if (s)
  {
    if (sock_connect(s, "localhost", PORT))
    {
      printf("Client: Connected to %s.\n", sock_get_remote_name_or_ip(s));
      for (; (str = sock_dgets(s)); )
      {
        printf("Client: Got \"%s\".\n", str);
        if (s->status != SOCK_STAT_OK)
          printf("Client: Socket reports \"%s\".\n",
                 sock_status2str(s->status));
        free(str);
      }
    }
    printf("Client: Socket reports \"%s\".\n"
           "Client: Closing connection.\n", sock_status2str(s->status));

    sock_close(s, 0);
  }
  else fprintf(stderr, "Socket error: Unable to create socket.\n");

  return(0);
}

2.3.0  Interprocess Piper Outline

int main()
{
  SOCK *s;
  char *str;
  int t;

  sock_init();
  s = sock_pipe(0);
  if (s)
  {
    sock_puts("Loopback test.", s);
    sock_flush(s);
    str = sock_dgets(s);
    if (str)
    {
      printf("Parent: Got string \"%s\".\n", str);
      free(str);
    }
    else printf("Parent: Loopback test failed.\n");

    t = fork();
    if (t)  /* Parent. */
    {
      sock_pipe_parent(s);
      sock_puts("This is what you want. This is what you get.", s);
      sock_flush(s);
      sock_close(s, 1);
    }
    else  /* Child. */
    {
      sock_pipe_child(s);
      for (; (str = sock_dgets(s)); )
      {
        printf("Child: Got \"%s\".\n", str);
        if (s->status != SOCK_STAT_OK)
          printf("Child: Socket reports \"%s\".\n",
                 sock_status2str(s->status));
        free(str);
      }
      sock_close(s, 0);
      return(0);
    }
  }
  else fprintf(stderr, "Parent: Unable to open pipe socket.\n");

  return(0);
}
