A Portable Multithreaded Web Server

Spring 1997, Dr. Dobb's Journal

by Pieter Hintjens and Pascal Antonnaux


Listing One

#include "sfl.h"  /*  SFL library header file */
#include "smtlib.h"  /*  SMT kernel functions */

int
main (int argc, char *argv [])
{
    if (argc > 1) /*  Use port base if specified */
    ip_portbase = atoi (argv [1]);

    smt_init ();  /*  Initialise SMT kernel*/
    smthttp_init (); /*  Initialise HTTP server  */
    smtecho_init (); /* and ECHO server */
    smt_exec_full ();/*  Run until completed  */
    smt_term ();  /*  Shut-down SMT kernel */
    return (EXIT_SUCCESS);
}

Listing Two

/*  ----------------------------------------------------------------<Prolog>-
    Name:       smtecho.c
    Title:      SMT TCP/IP echo agent
    Package:    Libero/SMT Kernel 2.x

    Written:    96/06/15  Pieter Hintjens <ph@imatix.com>
    Revised:    96/09/07  Pieter Hintjens <ph@imatix.com>

    Synopsis:   Handles the TCP/IP echo protocol.  Initialises automatically
                when you call the smtecho_init() function.  Uses smtsock.
                Sends errors to the SMTOPER agent.   Logs connections to the
                file smtecho.log via SMTLOG agent.

    Copyright:  Copyright (c) 1991-1996 iMatix
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version. This program is distributed in the hope that it will be
    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    Public License for more details.  You should have received a copy of the
    GNU General Public License along with this program; if not, write to the
    Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ------------------------------------------------------------------</Prolog>-*/

#include "sfl.h"                        /*  SFL library header file          */
#include "smtlib.h"                     /*  SMT kernel functions             */


/*- Definitions -------------------------------------------------------------*/

#define AGENT_NAME   SMT_ECHO           /*  Our public name                  */

typedef struct {                        /*  Thread context block:            */
    event_t thread_type;                /*    Thread type indicator          */
    SOCKET  handle;                     /*    Handle for i/o                 */
} TCB;


/*- Global variables used in this source file only --------------------------*/

static TCB
    *tcb;                               /*  Address thread contect block     */
static QID
    console,                            /*  Operator console event queue     */
    logq,                               /*  Logging agent event queue        */
    sockq;                              /*  Socket agent event queue         */
static char
    msg_body [LINE_MAX];                /*  Message sent to socket agent     */
static int
    msg_size;                           /*  Size of formatted msg_body       */
static DESCR                            /*  Descriptor for exdr_writed       */
    msg = { LINE_MAX, (byte *) msg_body };

#include "smtecho.d"                    /*  Include dialog data              */

/********************   INITIALIZE AGENT - ENTRY POINT   *********************/

int smtecho_init (void)
{
    AGENT   *agent;                     /*  Handle for our agent             */
    THREAD  *thread;                    /*  Handle to various threads        */
#   include "smtecho.i"                 /*  Include dialog interpreter       */

    /*                      Method name      Event value     Priority        */
    /*  Shutdown event comes from Kernel                                     */
    method_declare (agent, "SHUTDOWN",       shutdown_event, SMT_PRIORITY_MAX);

    /*  Reply events from socket agent                                       */
    method_declare (agent, "SOCK_INPUT_OK",  ok_event,       0);
    method_declare (agent, "SOCK_OUTPUT_OK", ok_event,       0);
    method_declare (agent, "SOCK_READ_OK",   read_ok_event,  0);
    method_declare (agent, "SOCK_WRITE_OK",  write_ok_event, 0);
    method_declare (agent, "SOCK_CLOSED",    closed_event,   0);
    method_declare (agent, "SOCK_ERROR",     error_event,    0);
    method_declare (agent, "SOCK_TIMEOUT",   error_event,    0);

    /*  Private methods used to pass initial thread events                   */
    method_declare (agent, "_MASTER",        master_event,   0);
    method_declare (agent, "_CLIENT",        client_event,   0);

    /*  Ensure that operator console is running, else start it up            */
    smtoper_init ();
    if ((thread = thread_lookup (SMT_OPERATOR, "")) != NULL)
        console = thread-> queue-> qid;
    else
        return (-1);
    /*  Ensure that socket agent is running, else start it up                */
    smtsock_init ();
    if ((thread = thread_lookup (SMT_SOCKET, "")) != NULL)
        sockq = thread-> queue-> qid;
    else
        return (-1);
    /*  Ensure that logging agent is running, and create new thread          */
    smtlog_init ();
    if ((thread = thread_create (SMT_LOGGING, "")) != NULL)
        logq = thread-> queue-> qid;        /*  Get logging queue id         */
    else
        return (-1);
    /*  Create initial thread to manage master port                          */
    if ((thread = thread_create (AGENT_NAME, "")) != NULL)
      {
        SEND (&thread-> queue-> qid, "_MASTER", "");
        ((TCB *) thread-> tcb)-> thread_type = master_event;
        ((TCB *) thread-> tcb)-> handle = 0;
      }
    else
        return (-1);
    /*  Signal okay to caller that we initialised okay                       */
    return (0);
}

/*************************   INITIALIZE THE THREAD   *************************/
MODULE initialise_the_thread (THREAD *thread)
{
    /*  We don't set the_next_event because we expect an argument event      */
    /*  to supply these.                                                     */

    tcb = thread-> tcb;                 /*  Point to thread's context        */
}

/**************************   OPEN AGENT LOG FILE    *************************/
MODULE open_agent_log_file (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    sendfmt (&logq, "OPEN", "smtecho.log");
}

/***************************   OPEN MASTER SOCKET   **************************/
MODULE open_master_socket (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */

    tcb-> handle = passive_TCP (SMT_ECHO_PORT, 5);
    if (tcb-> handle == INVALID_SOCKET)
      {
        sendfmt (&console, "ERROR",
                 "Could not open ECHO port %d/%s", ip_portbase, SMT_ECHO_PORT);
        sendfmt (&console, "ERROR",
                 connect_errlist [connect_error ()]);
        raise_exception (fatal_event);
      }
    else
        sendfmt (&logq, "PUT", "I: opened echo port %s", SMT_ECHO_PORT);
}

/*************************   WAIT FOR SOCKET INPUT   *************************/
MODULE wait_for_socket_input (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */

    msg_size = exdr_writed (&msg, SMT_SOCK_INPUT, 0,
                            tcb-> handle, (qbyte) 0);
    event_send (
        &sockq,                         /*  Send to socket agent             */
        &thread-> queue-> qid,          /*  Queue for reply                  */
        "INPUT",                        /*  Name of event to send            */
        msg_body, msg_size,             /*  Event body and size              */
        NULL, NULL, NULL,               /*  No response events               */
        0);                             /*  No timeout                       */
}

/************************   ACCEPT CLIENT CONNECTION   ***********************/
MODULE accept_client_connection (THREAD *thread)
{
    SOCKET
        slave_socket;                   /*  Connected socket                 */
    THREAD
        *child_thread;                  /*  Handle to child threads          */

    tcb = thread-> tcb;                 /*  Point to thread's context        */

    slave_socket = accept_socket (tcb-> handle);
    if (slave_socket != INVALID_SOCKET)
      {
        child_thread = thread_create (AGENT_NAME, "");
        if (child_thread)
          {
            SEND (&child_thread-> queue-> qid, "_CLIENT", "");
            ((TCB *) child_thread-> tcb)-> handle      = slave_socket;
            ((TCB *) child_thread-> tcb)-> thread_type = client_event;
          }
      }
    else
    if (errno != EAGAIN)
      {
        sendfmt (&console, "ERROR",
                 "Could not accept connection: %s", sockmsg ());
        raise_exception (exception_event);
      }
}

/**********************   READ SOCKET DATA REPEATEDLY   **********************/
MODULE read_socket_data_repeatedly (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */

    /*  We ask the socket agent to read what it can from the socket,         */
    /*  and to return that to us as a message; from 1 to 2048 bytes.         */
    /*  Each time input arrives, we'll get an event.                         */
    msg_size = exdr_writed (&msg, SMT_SOCK_READ, 0,
                            tcb-> handle, 2048, 1, (qbyte) 0);
    event_send (
        &sockq,                         /*  Send to socket agent             */
        &thread-> queue-> qid,          /*  Queue for reply                  */
        "READR",                        /*  Name of event to send            */
        msg_body, msg_size,             /*  Event body and size              */
        NULL, NULL, NULL,               /*  No response events               */
        0);                             /*  No timeout                       */
}

/***************************   WRITE SOCKET DATA   ***************************/
MODULE write_socket_data (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */

    /*  We just need to bounce the read data right back, since it is in      */
    /*  the correct format already (SMT_SOCK_WRITE == SMT_SOCK_READ_OK)      */
    ASSERT (streq (SMT_SOCK_WRITE, SMT_SOCK_READ_OK));
    event_send (
        &sockq,                         /*  Send to socket agent             */
        &thread-> queue-> qid,          /*  Queue for reply                  */
        "WRITE",                        /*  Name of event to send            */
        thread-> event-> body,          /*  Event body                       */
        thread-> event-> body_size,     /*    and size                       */
        NULL, NULL, NULL,               /*  No response events               */
        0);                             /*  No timeout                       */
    event_wait ();                      /*  Wait for reply event             */
}

/**************************   SIGNAL SOCKET ERROR   **************************/
MODULE signal_socket_error (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    sendfmt (&console, "ERROR", "Error on socket: %s", thread-> event-> body);
}

/***************************   CHECK SOCKET TYPE   ***************************/
MODULE check_socket_type (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    the_next_event = tcb-> thread_type;
}

/*************************   CLOSE AGENT LOG FILE    *************************/
MODULE close_agent_log_file (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    sendfmt (&logq, "CLOSE", "");
}

/************************   SHUTDOWN THE APPLICATION   ***********************/
MODULE shutdown_the_application (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    smt_shutdown ();                    /*  Halt the application             */
}

/*************************   TERMINATE THE THREAD   **************************/
MODULE terminate_the_thread (THREAD *thread)
{
    tcb = thread-> tcb;                 /*  Point to thread's context        */
    if (tcb-> handle);
        close_socket (tcb-> handle);
    the_next_event = terminate_event;
}


End Listings