A Portable Multithreaded Web Server
Spring 1997, Dr. Dobb's Journal
by Pieter Hintjens and Pascal Antonnaux
#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);
}
/* ----------------------------------------------------------------<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