A Portable Multithreaded Web Server
Spring 1997, Dr. Dobb's Journalby 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 <[email protected]> Revised: 96/09/07 Pieter Hintjens <[email protected]> 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