+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>pthrlib documentation index</title>
+ <style type="text/css"><!--
+ h1 {
+ text-align: center;
+ }
+ pre {
+ background-color: #eeeeff;
+ }
+ code {
+ color: green;
+ font-weight: bold;
+ }
+ --></style>
+ </head>
+
+ <body bgcolor="#ffffff">
+ <h1>pthrlib documentation index</h1>
+
+ <p>
+ <code>pthrlib</code> is a library for writing small, fast
+ and efficient servers in C. It offers a list of advanced
+ features. This library has been used to write a
+ <a href="http://www.annexia.org/freeware/rws/">very
+ tiny and fast web server called rws</a> and a closed
+ source chat server.
+ </p>
+
+ <p>
+ The primary aims of <code>pthrlib</code> are:
+ </p>
+
+ <ul>
+ <li> Be very simple to use.
+ <li> Provide rich functionality for C programmers out of the box.
+ <li> Meticulous attention paid to the number of syscalls
+ issued and the efficiency of those syscalls, so typically
+ a <code>pthrlib</code> server will outperform any other
+ server architecture or language (on non-SMP).
+ <li> Tiny memory footprint for running servers.
+ <li> Cooperative threading reduces the danger and agony
+ of thread programming.
+ <li> Use of <code>c2lib</code> removes many risks of
+ buffer overflows.
+ </ul>
+
+ <h2>Tutorial and programming examples</h2>
+
+ <p>
+ At the heart of <code>pthrlib</code> is a threading
+ library called <q><b>pseudothreads</b></q>. This library
+ is a typical lightweight threading library, written
+ from scratch to be as small and fast as possible (it
+ therefore lacks many of the unnecessary features
+ which complicate other lightweight threading libraries,
+ such as the ability to suspend threads).
+ </p>
+
+ <p>
+ A small <code>pthrlib</code> server will start off
+ with just a single listener thread, listening for
+ new connections on a socket. When connections come
+ in, a new thread is spun off to handle it:
+ </p>
+
+ <table border="1">
+ <tr> <td> listener thread </td> </tr>
+ <tr> <td> processing thread, connected to client #1 </td> </tr>
+ <tr> <td> processing thread, connected to client #2 </td> </tr>
+ <tr> <td> processing thread, connected to client #3 </td> </tr>
+ <tr> <td align="center"> ... </td> </tr>
+ </table>
+
+ <p>
+ More complex <code>pthrlib</code> servers may contain
+ several core threads: for example our closed-source
+ chat server has one extra thread called <code>autoannounce</code>
+ which periodically sends out announcement messages to
+ all clients. They may also use more than one thread
+ per client. Since threads are very lightweight, you
+ should be able to create as many threads as necessary
+ for your application.
+ </p>
+
+ <h3>Simple <q>echo</q> server</h3>
+
+ <p>
+ To help you create a server with a listener thread
+ spinning off threads for each incoming connection,
+ there is a helper function called <code>pthr_server_main_loop(3)</code>.
+ Almost all programs will want to use it, such as the
+ following simple <q>echo</q> program (I have split
+ the program into chunks for readability).
+ </p>
+
+ <p>
+ Standard includes for socket programs, and predeclare
+ static functions:
+ </p>
+
+<pre>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+#include <pool.h>
+
+#include <pthr_pseudothread.h>
+#include <pthr_iolib.h>
+#include <pthr_server.h>
+
+static void start_processor (int sock, void *data);
+static void run (void *);
+</pre>
+
+ <p>
+ Recall from the diagram above that we will start one
+ processing thread for each client. The following structure
+ is used to store the per-thread information about that
+ processing thread:
+ </p>
+
+<pre>
+typedef struct processor_thread
+{
+ pseudothread pth; /* Pseudothread handle. */
+ int sock; /* Socket. */
+} *processor_thread;
+</pre>
+
+ <p>
+ <code>main</code> is very simple, since
+ <code>pthr_server_main_loop</code> does all the hard work
+ of opening up a listening socket, forking into the
+ background, parsing command line arguments and so on.
+ Note that we pass a pointer to our
+ <code>start_processor</code> function.
+ </p>
+
+<pre>
+int
+main (int argc, char *argv[])
+{
+ /* Start up the server. */
+ pthr_server_main_loop (argc, argv, start_processor);
+
+ exit (0);
+}
+</pre>
+
+ <p>
+ Whenever a client makes a new connection to our server,
+ the listener thread is going to call <code>start_processor</code>.
+ This creates allocates the per-thread data structure
+ and starts the new thread. The <code>run</code>
+ function is the actual new processing thread running.
+ </p>
+
+<pre>
+static void
+start_processor (int sock, void *data)
+{
+ pool pool;
+ processor_thread p;
+
+ pool = new_pool ();
+ p = pmalloc (pool, sizeof *p);
+
+ p->sock = sock;
+ p->pth = new_pseudothread (pool, run, p, "processor thread");
+
+ pth_start (p->pth);
+}
+
+static void
+run (void *vp)
+{
+ processor_thread p = (processor_thread) vp;
+ io_handle io;
+ char buffer[256];
+
+ io = io_fdopen (p->sock);
+
+ /* Sit in a loop reading strings and echoing them back. */
+ while (io_fgets (buffer, sizeof buffer, io, 1))
+ io_fputs (buffer, io);
+
+ io_fclose (io);
+
+ pth_exit ();
+}
+</pre>
+
+ <p>
+ Here is a typical run with this program (what I
+ typed is shown in <b>bold text</b>):
+ </p>
+
+<pre>
+$ <b>./eg_echo -p 9000</b>
+$ <b>telnet localhost 9000</b>
+Trying 127.0.0.1...
+Connected to localhost.localnet (127.0.0.1).
+Escape character is '^]'.
+<b>hello</b>
+hello
+<b>goodbye</b>
+goodbye
+<b>^]</b>
+
+telnet> <b>quit</b>
+Connection closed.
+</pre>
+
+ <h3>Simple HTTP server</h3>
+
+ <p>
+ <b>Note:</b> Although it is possible to write complete
+ mini webservers using just <code>pthrlib</code>, it is
+ often more flexible and just as fast to use
+ <a href="http://www.annexia.org/freeware/rws/">rws's</a>
+ shared object scripts. <code>rws</code> provides you
+ with the complete web serving framework. If you don't
+ use <code>rws</code> and you need to, say, serve an
+ image or a static page at some point in your application,
+ then you will need to either link to another web server
+ like Apache, or else write your own static file service
+ code (it can be done -- we did it for the chat server --
+ but it's unnecessary).
+ </p>
+
+ <p>
+ The following code comes from example 1 supplied with
+ <code>pthrlib</code>. You can find the working code
+ in the <code>examples/</code> directory. I have omitted
+ some parts of the code in order to concentrate on the
+ interesting and relevant bits.
+ </p>
+
+ <p>
+ First of all, the <code>main</code> function:
+ </p>
+
+<pre>
+static void start_processor (int sock, void *);
+
+int
+main (int argc, char *argv[])
+{
+ /* Start up the server. */
+ pthr_server_main_loop (argc, argv, start_processor);
+
+ exit (0);
+}
+
+static void
+start_processor (int sock, void *data)
+{
+ (void) new_eg1_echo_processor (sock);
+}
+</pre>
+
+ <p>
+ Again, we are using <code>pthr_server_main_loop</code> to
+ do the hard work. <code>start_processor</code> starts the
+ processor thread. The processor thread's <code>run</code>
+ function has the following outline:
+ </p>
+
+<pre>
+static void
+run (void *vp)
+{
+ eg1_echo_processor p = (eg1_echo_processor) vp;
+ int close = 0;
+ io_handle io;
+
+ io = io_fdopen (p->sock);
+
+ /* Sit in a loop reading HTTP requests. */
+ while (!close)
+ {
+ /* Parse the HTTP request. */
+ : : :
+ : : :
+
+ /* Form the HTTP response. */
+ : : :
+ : : :
+ }
+
+ io_fclose (io);
+
+ pth_exit ();
+}
+</pre>
+
+ <p>
+ The purpose of this loop is to deal with HTTP keepalives,
+ where a client (or perhaps many different clients through a
+ proxy) makes a series of requests over the same TCP connection.
+ For each request, we'll make an iteration of the <code>while</code>
+ loop. Each request is independent of the previous one.
+ </p>
+
+ <p>
+ At the beginning of the thread, the listening thread hands us
+ a socket file descriptor in <code>sock</code>. Doing I/O directly
+ on a file descriptor is inconvenient, and it can't be
+ wrapped up directly in a <code>stdio</code> <code>FILE *</code>
+ because these block, hanging the entire process (and all other
+ threads). <code>iolib</code> is a replacement for <code>stdio</code>
+ which works with pools and doesn't block. <code>io_fdopen</code>
+ wraps up a file descriptor in a full buffered <code>io_handle</code>.
+ </p>
+
+ <p>
+ Now lets look at the step which parses the HTTP request:
+ </p>
+
+<pre>
+ http_request http_request;
+ cgi cgi;
+ pool pool = pth_get_pool (p->pth);
+ : : :
+ : : :
+
+ /* ----- HTTP request ----- */
+ http_request = new_http_request (pool, io);
+ if (http_request == 0) /* Normal end of file. */
+ break;
+
+ cgi = new_cgi (pool, http_request, io);
+ if (cgi == 0) /* XXX Should send an error here. */
+ break;
+</pre>
+
+ <p>
+ The <code>new_http_request</code> function parses the
+ HTTP headers. It does pretty much the equivalent of what
+ Apache does just before it hands off to a normal CGI script.
+ You can think of <code>new_cgi</code> as being somewhat
+ equivalent to Perl's <code>CGI.pm</code>.
+ </p>
+
+ <p>
+ Here's the code which generates the HTTP response:
+ </p>
+
+<pre>
+ http_response http_response;
+ : : :
+ : : :
+
+ http_response = new_http_response (pool, http_request,
+ io,
+ 200, "OK");
+ http_response_send_header (http_response,
+ "Content-Type", "text/plain");
+ close = http_response_end_headers (http_response);
+
+ if (!http_request_is_HEAD (http_request))
+ {
+ io_fprintf (io, "Hello. This is your server.\r\n\r\n");
+ io_fprintf (io, "Your browser sent the following headers:\r\n");
+
+ headers = http_request_get_headers (http_request);
+ for (i = 0; i < vector_size (headers); ++i)
+ {
+ vector_get (headers, i, header);
+ io_fprintf (io, "\t%s: %s\r\n", header.key, header.value);
+ }
+
+ io_fprintf (io, "----- end of headers -----\r\n");
+
+ io_fprintf (io, "The URL was: %s\r\n",
+ http_request_get_url (http_request));
+ io_fprintf (io, "The path component was: %s\r\n",
+ http_request_path (http_request));
+ io_fprintf (io, "The query string was: %s\r\n",
+ http_request_query_string (http_request));
+ io_fprintf (io, "The query arguments were:\r\n");
+
+ params = cgi_params (cgi);
+ for (i = 0; i < vector_size (params); ++i)
+ {
+ vector_get (params, i, name);
+ value = cgi_param (cgi, name);
+ io_fprintf (io, "\t%s=%s\r\n", name, value);
+ }
+
+ io_fprintf (io, "----- end of parameters -----\r\n");
+ }
+</pre>
+
+ <p>
+ <code>new_http_response</code>,
+ <code>http_response_send_header</code> and
+ <code>http_response_end_headers</code> generates the
+ HTTP headers for the response back to the client. We'll
+ see those headers in a minute.
+ Notice that we send back an explicit
+ <code>Content-Type: text/plain</code>
+ header.
+ </p>
+
+ <p>
+ The rest of the code actually generates the page. The
+ simplest way to describe it is to show an actual interaction
+ with the server. What I typed is shown in <b>bold text</b>.
+ </p>
+
+<pre>
+$ <b>./pthr_eg1_echo -p 9000</b>
+$ <b>telnet localhost 9000</b>
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+<b>GET /path/here?abc=123&def=456 HTTP/1.0</b>
+<b>Host: localhost:9000</b>
+
+HTTP/1.1 200 OK
+Content-Type: text/plain
+Server: pthrlib-httpd/3.0.3
+Date: Fri, 30 Aug 2002 17:04:03 GMT
+Connection: close
+
+Hello. This is your server.
+
+Your browser sent the following headers:
+ host: localhost:9000
+----- end of headers -----
+The URL was: /path/here?abc=123&def=456
+The path component was: /path/here
+The query string was: abc=123&def=456
+The query arguments were:
+ abc=123
+ def=456
+----- end of parameters -----
+Connection closed by foreign host.
+</pre>
+
+ <h3>Static file webserver</h3>
+
+ <p>
+ This following code is from example 2. You can find
+ the complete working program in the <code>examples/</code>
+ directory. It's a very minimal webserver which can
+ only serve static files from a single directory. If
+ you start the server up as <code>root</code>, then
+ the server will <code>chroot(2)</code> itself into
+ a configurable directory, and change its user ID to
+ <code>nobody.nobody</code>
+ </p>
+
+ <p>
+ Again the <code>main</code> function uses
+ <code>pthr_server_main_loop</code> for simplicity.
+ However one thing which <code>pthr_server_main_loop</code>
+ can't do (yet) is set up signal handlers, so we have
+ to do those by hand first:
+ </p>
+
+<pre>
+int
+main (int argc, char *argv[])
+{
+ struct sigaction sa;
+
+ /* Intercept signals. */
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = catch_quit_signal;
+ sa.sa_flags = SA_RESTART;
+ sigaction (SIGINT, &sa, 0);
+ sigaction (SIGQUIT, &sa, 0);
+ sigaction (SIGTERM, &sa, 0);
+
+ /* ... but ignore SIGPIPE errors. */
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_RESTART;
+ sigaction (SIGPIPE, &sa, 0);
+
+ /* Start up the server. */
+ pthr_server_chroot (root);
+ pthr_server_username (user);
+ pthr_server_main_loop (argc, argv, start_processor);
+
+ exit (0);
+}
+
+static void
+start_processor (int sock, void *data)
+{
+ (void) new_eg2_server_processor (sock);
+}
+
+static void
+catch_quit_signal (int sig)
+{
+ exit (0);
+}
+</pre>
+
+ <p>
+ Notice that just before we actually call
+ <code>pthr_server_main_loop</code>, we configure
+ the main loop code first by telling it the
+ root directory (where we want to <code>chroot(2)</code>
+ to) and the username (<code>nobody</code>).
+ </p>
+
+ <p>
+ The <code>eg2_server_processor</code> thread
+ structure contains a little more data this time. It
+ contains most of the information about the current
+ request:
+ </p>
+
+<pre>
+struct eg2_server_processor
+{
+ /* Pseudothread handle. */
+ pseudothread pth;
+
+ /* Socket. */
+ int sock;
+
+ /* Pool for memory allocations. */
+ struct pool *pool;
+
+ /* HTTP request. */
+ http_request http_request;
+
+ /* IO handle. */
+ io_handle io;
+};
+</pre>
+
+ <p>
+ The <code>run</code> function has the same basic outline,
+ ie. a <code>while</code> loop to process each request on
+ the same keep-alive connection, and a call to
+ <code>new_http_request</code> to parse the HTTP headers. The
+ outline code is shown in <span style="color: red">red text</span>
+ below. The code to handle the response is shown in
+ black.
+ </p>
+
+<pre>
+<span style="color: red">static void
+run (void *vp)
+{
+ eg2_server_processor p = (eg2_server_processor) vp;
+ int close = 0;
+ const char *path;
+ struct stat statbuf;
+
+ p->io = io_fdopen (p->sock);
+
+ /* Sit in a loop reading HTTP requests. */
+ while (!close)
+ {
+ /* ----- HTTP request ----- */
+ p->http_request = new_http_request (pool, p->io);
+ if (p->http_request == 0) /* Normal end of file. */
+ break;</span>
+
+ /* Get the path and locate the file. */
+ path = http_request_path (p->http_request);
+ if (stat (path, &statbuf) == -1)
+ {
+ close = file_not_found_error (p);
+ continue;
+ }
+
+ /* File or directory? */
+ if (S_ISDIR (statbuf.st_mode))
+ {
+ close = serve_directory (p, path, &statbuf);
+ continue;
+ }
+ else if (S_ISREG (statbuf.st_mode))
+ {
+ close = serve_file (p, path, &statbuf);
+ continue;
+ }
+ else
+ {
+ close = file_not_found_error (p);
+ continue;
+ }
+ <span style="color: red">}
+
+ io_fclose (p->io);
+
+ pth_exit ();
+}</span>
+</pre>
+
+ <p>
+ This is a very simple webserver, so all it does is take the
+ <code>path</code> component of the request, and uses it directly
+ as a filename (note that it relies completely on the
+ <code>chroot(2)</code> environment for security).
+ </p>
+
+ <p>
+ Firstly it calls <code>stat</code> to find out if the filename
+ is a directory or a regular file. If it is neither, or if the
+ file doesn't exist, it calls <code>file_not_found_error</code>
+ which sends back a 404 FILE NOT FOUND error.
+ </p>
+
+ <p>
+ If the file is a regular file, we call <code>serve_file</code>,
+ which is a simple piece of code:
+ </p>
+
+<pre>
+static int
+serve_file (eg2_server_processor p, const char *path,
+ const struct stat *statbuf)
+{
+ http_response http_response;
+ const int n = 4096;
+ char *buffer = alloca (n);
+ int cl, fd, r;
+ char *content_length = pitoa (p->pool, statbuf->st_size);
+
+ fd = open (path, O_RDONLY);
+ if (fd < 0)
+ return file_not_found_error (p);
+
+ http_response = new_http_response (pool, p->http_request, p->io,
+ 200, "OK");
+ http_response_send_headers (http_response,
+ /* Content type. */
+ "Content-Type", "text/plain",
+ "Content-Length", content_length,
+ /* End of headers. */
+ NULL);
+ cl = http_response_end_headers (http_response);
+
+ if (http_request_is_HEAD (p->http_request)) return cl;
+
+ while ((r = read (fd, buffer, n)) > 0)
+ {
+ io_fwrite (buffer, r, 1, p->io);
+ }
+
+ if (r < 0)
+ perror ("read");
+
+ close (fd);
+
+ return cl;
+}
+</pre>
+
+ <p>
+ Firstly we work out the size of the file, using the
+ <code>statbuf.st_size</code> field. The
+ <a href="http://www.annexia.org/freeware/c2lib/">c2lib</a>
+ function <code>pitoa</code> turns this into a string (all
+ headers must be passed as strings). Next we open the
+ file. If this fails, then the file is inaccessible or
+ has just gone, so we return a 404 instead.
+ </p>
+
+ <p>
+ Next we generate our headers:
+ </p>
+
+<pre>
+Content-Type: text/plain
+Content-Length: <i>(size of the file in octets)</i>
+</pre>
+
+ <p>
+ <code>pthrlib</code> will generate other standard
+ headers as well.
+ </p>
+
+ <p>
+ If the request was a <code>HEAD</code> request, then
+ the client only wants to see the headers, so we stop
+ right there. Otherwise we copy the file back to our
+ user.
+ </p>
+
+ <p>
+ Party question: Why is it OK to use <code>read(2)</code>
+ when reading the file, but not OK to use <code>write(2)</code>
+ when writing to the socket? Why will this <i>not</i> cause
+ the whole server process to block (on Linux at least)?
+ </p>
+
+ <p>
+ Serving a directory is more complicated, so we'll take it in
+ steps. Recall that to serve a directory, we actually need
+ to create an HTML page which lists the files, with information
+ about those files and links to the files themselves.
+ </p>
+
+ <p>
+ Firstly if the user requested the directory as:
+ </p>
+
+<pre>
+http://your.hostname/path/to/directory
+</pre>
+
+ <p>
+ then we need to redirect them to:
+ </p>
+
+<pre>
+http://your.hostname/path/to/directory<b>/</b>
+</pre>
+
+ <p>
+ (note the trailing slash). The reason for this is that
+ relative links within our page won't work otherwise. The
+ browser will request <code>/path/to/file</code> instead of
+ <code>/path/to/directory/file</code>. This is actually a
+ bit of webserver arcana which is often forgotten. If you
+ don't believe me, Apache does this too: go look at the source!
+ </p>
+
+<pre>
+static int
+serve_directory (eg2_server_processor p, const char *path,
+ const struct stat *statbuf)
+{
+ http_response http_response;
+ int close;
+ DIR *dir;
+ struct dirent *d;
+
+ /* If the path doesn't end with a "/", then we need to send
+ * a redirect back to the client so it refetches the page
+ * with "/" appended.
+ */
+ if (path[strlen (path)-1] != '/')
+ {
+ char *location = psprintf (p->pool, "%s/", path);
+ return moved_permanently (p, location);
+ }
+</pre>
+
+ <p>
+ <code>moved_permanently</code> sends back a 301 MOVED PERMANENTLY
+ page causing the browser to re-request the new location.
+ </p>
+
+ <p>
+ The next piece of code should be familiar boilerplate. We open
+ the directory, and send back headers. If the request is
+ <code>HEAD</code> we then drop out.
+ </p>
+
+<pre>
+ dir = opendir (path);
+ if (dir == 0)
+ return file_not_found_error (p);
+
+ http_response = new_http_response (pool, p->http_request, p->io,
+ 200, "OK");
+ http_response_send_headers (http_response,
+ /* Content type. */
+ "Content-Type", "text/html",
+ NO_CACHE_HEADERS,
+ /* End of headers. */
+ NULL);
+ close = http_response_end_headers (http_response);
+
+ if (http_request_is_HEAD (p->http_request)) return close;
+</pre>
+
+ <p>
+ The next piece of code is the complicated bit which generates
+ the HTML page listing the files:
+ </p>
+
+<pre>
+ io_fprintf (p->io,
+ "<html><head><title>Directory: %s</title></head>" CRLF
+ "<body bgcolor=\"#ffffff\">" CRLF
+ "<h1>Directory: %s</h1>" CRLF
+ "<table>" CRLF
+ "<tr><td></td><td></td>"
+ "<td><a href=\"..\">Parent directory</a></td></tr>" CRLF,
+ path, path);
+
+ while ((d = readdir (dir)) != 0)
+ {
+ if (d->d_name[0] != '.') /* Ignore hidden files. */
+ {
+ const char *filename;
+ struct stat fstatbuf;
+
+ /* Generate the full pathname to this file. */
+ filename = psprintf (p->pool, "%s/%s", path, d->d_name);
+
+ /* Stat the file to find out what it is. */
+ if (lstat (filename, &fstatbuf) == 0)
+ {
+ const char *type;
+ int size;
+
+ if (S_ISDIR (fstatbuf.st_mode))
+ type = "dir";
+ else if (S_ISREG (fstatbuf.st_mode))
+ type = "file";
+ else if (S_ISLNK (fstatbuf.st_mode))
+ type = "link";
+ else
+ type = "special";
+
+ size = fstatbuf.st_size;
+
+ /* Print the details. */
+ io_fprintf (p->io,
+ "<tr><td>[ %s ]</td><td align=right>%d</td>"
+ "<td><a href=\"%s%s\">%s</a>",
+ type, size,
+ d->d_name,
+ S_ISDIR (fstatbuf.st_mode) ? "/" : "",
+ d->d_name);
+
+ if (S_ISLNK (fstatbuf.st_mode))
+ {
+ char link[NAME_MAX+1];
+ int r;
+
+ r = readlink (filename, link, NAME_MAX);
+ if (r >= 0) link[r] = '\0';
+ else strcpy (link, "unknown");
+
+ io_fprintf (p->io, " -&gt; %s", link);
+ }
+
+ io_fputs ("</td></tr>" CRLF, p->io);
+ }
+ }
+ }
+
+ io_fprintf (p->io,
+ "</table></body></html>" CRLF);
+
+ return close;
+</pre>
+
+ <p>
+ We first send the top of the HTML page, and the beginning
+ of the table (the whole page is one large table, of course).
+ </p>
+
+ <p>
+ Next we loop over the directory entries using <code>readdir(3)</code>
+ to read each one. Ignoring files which start with a dot (.) we
+ <code>lstat(2)</code> each file to find out if it's a directory,
+ file or symbolic link, or some type of special device node.
+ </p>
+
+ <p>
+ Depending on the file type, we generate a different bit
+ of HTML containing a relative link to the file or
+ directory (if it's a directory we need to remember to
+ append a trailing slash to the name to avoid that extra
+ 301 redirect).
+ </p>
+
+ <p>
+ Finally after we reach the end of the directory we finish
+ of the table and the page and return.
+ </p>
+
+ <h2>Further examples</h2>
+
+ <p>
+ That's the end of this <code>pthrlib</code> tutorial, I
+ hope you enjoyed it.
+ </p>
+
+ <p>
+ <code>pthrlib</code> isn't just about writing web servers.
+ You can use it to write all sorts of types of servers,
+ or even clients (it has an FTP client library which I
+ used to load-test <code>Net::FTPServer</code>).
+ </p>
+
+ <p>
+ If, however, you feel like using <code>pthrlib</code> to
+ write a web server, I strongly urge you to use
+ <a href="http://www.annexia.org/freeware/rws/">rws</a>
+ and shared object scripts. These are described in
+ the <a href="http://www.annexia.org/freeware/rws/doc/">rws
+ documentation</a>. (rws uses <code>pthrlib</code>).
+ </p>
+
+ <h2>Links to manual pages</h2>
+
+ <p>
+ (These manual pages are not always up to date. For the
+ latest documentation, always consult the manual pages
+ supplied with the latest <code>pthrlib</code> package!)
+ </p>
+
+ <h3>Pseudothreads</h3>
+
+ <ul>
+ <li> <a href="new_pseudothread.3.html"><code>new_pseudothread(3)</code></a> </li>
+ <li> <a href="pseudothread_count_threads.3.html"><code>pseudothread_count_threads(3)</code></a> </li>
+ <li> <a href="pseudothread_get_stack_size.3.html"><code>pseudothread_get_stack_size(3)</code></a> </li>
+ <li> <a href="pseudothread_get_threads.3.html"><code>pseudothread_get_threads(3)</code></a> </li>
+ <li> <a href="pseudothread_set_stack_size.3.html"><code>pseudothread_set_stack_size(3)</code></a> </li>
+ <li> <a href="pth_accept.3.html"><code>pth_accept(3)</code></a> </li>
+ <li> <a href="pth_catch.3.html"><code>pth_catch(3)</code></a> </li>
+ <li> <a href="pth_connect.3.html"><code>pth_connect(3)</code></a> </li>
+ <li> <a href="pth_die.3.html"><code>pth_die(3)</code></a> </li>
+ <li> <a href="pth_exit.3.html"><code>pth_exit(3)</code></a> </li>
+ <li> <a href="pth_get_data.3.html"><code>pth_get_data(3)</code></a> </li>
+ <li> <a href="pth_get_language.3.html"><code>pth_get_language(3)</code></a> </li>
+ <li> <a href="pth_get_name.3.html"><code>pth_get_name(3)</code></a> </li>
+ <li> <a href="pth_get_PC.3.html"><code>pth_get_PC(3)</code></a> </li>
+ <li> <a href="pth_get_pool.3.html"><code>pth_get_pool(3)</code></a> </li>
+ <li> <a href="pth_get_run.3.html"><code>pth_get_run(3)</code></a> </li>
+ <li> <a href="pth_get_SP.3.html"><code>pth_get_SP(3)</code></a> </li>
+ <li> <a href="pth_get_stack.3.html"><code>pth_get_stack(3)</code></a> </li>
+ <li> <a href="pth_get_stack_size.3.html"><code>pth_get_stack_size(3)</code></a> </li>
+ <li> <a href="pth_get_thread_num.3.html"><code>pth_get_thread_num(3)</code></a> </li>
+ <li> <a href="pth_get_tz.3.html"><code>pth_get_tz(3)</code></a> </li>
+ <li> <a href="pth_millisleep.3.html"><code>pth_millisleep(3)</code></a> </li>
+ <li> <a href="pth_nanosleep.3.html"><code>pth_nanosleep(3)</code></a> </li>
+ <li> <a href="pth_poll.3.html"><code>pth_poll(3)</code></a> </li>
+ <li> <a href="pth_read.3.html"><code>pth_read(3)</code></a> </li>
+ <li> <a href="pth_recv.3.html"><code>pth_recv(3)</code></a> </li>
+ <li> <a href="pth_recvfrom.3.html"><code>pth_recvfrom(3)</code></a> </li>
+ <li> <a href="pth_recvmsg.3.html"><code>pth_recvmsg(3)</code></a> </li>
+ <li> <a href="pth_select.3.html"><code>pth_select(3)</code></a> </li>
+ <li> <a href="pth_send.3.html"><code>pth_send(3)</code></a> </li>
+ <li> <a href="pth_sendmsg.3.html"><code>pth_sendmsg(3)</code></a> </li>
+ <li> <a href="pth_sendto.3.html"><code>pth_sendto(3)</code></a> </li>
+ <li> <a href="pth_set_language.3.html"><code>pth_set_language(3)</code></a> </li>
+ <li> <a href="pth_set_name.3.html"><code>pth_set_name(3)</code></a> </li>
+ <li> <a href="pth_set_tz.3.html"><code>pth_set_tz(3)</code></a> </li>
+ <li> <a href="pth_sleep.3.html"><code>pth_sleep(3)</code></a> </li>
+ <li> <a href="pth_start.3.html"><code>pth_start(3)</code></a> </li>
+ <li> <a href="pth_timeout.3.html"><code>pth_timeout(3)</code></a> </li>
+ <li> <a href="pth_write.3.html"><code>pth_write(3)</code></a> </li>
+ </ul>
+
+ <h3>Server main loop</h3>
+
+ <ul>
+ <li> <a href="pthr_server_main_loop.3.html"><code>pthr_server_main_loop(3)</code></a> </li>
+ <li> <a href="pthr_server_default_port.3.html"><code>pthr_server_default_port(3)</code></a> </li>
+ <li> <a href="pthr_server_port_option_name.3.html"><code>pthr_server_port_option_name(3)</code></a> </li>
+ <li> <a href="pthr_server_disable_syslog.3.html"><code>pthr_server_disable_syslog(3)</code></a> </li>
+ <li> <a href="pthr_server_package_name.3.html"><code>pthr_server_package_name(3)</code></a> </li>
+ <li> <a href="pthr_server_disable_fork.3.html"><code>pthr_server_disable_fork(3)</code></a> </li>
+ <li> <a href="pthr_server_disable_chdir.3.html"><code>pthr_server_disable_chdir(3)</code></a> </li>
+ <li> <a href="pthr_server_disable_close.3.html"><code>pthr_server_disable_close(3)</code></a> </li>
+ <li> <a href="pthr_server_chroot.3.html"><code>pthr_server_chroot(3)</code></a> </li>
+ <li> <a href="pthr_server_username.3.html"><code>pthr_server_username(3)</code></a> </li>
+ <li> <a href="pthr_server_stderr_file.3.html"><code>pthr_server_stderr_file(3)</code></a> </li>
+ <li> <a href="pthr_server_startup_fn.3.html"><code>pthr_server_startup_fn(3)</code></a> </li>
+ </ul>
+
+ <h3>Buffered I/O library</h3>
+
+ <ul>
+ <li> <a href="io_copy.3.html"><code>io_copy(3)</code></a> </li>
+ <li> <a href="io_fclose.3.html"><code>io_fclose(3)</code></a> </li>
+ <li> <a href="io_fdopen.3.html"><code>io_fdopen(3)</code></a> </li>
+ <li> <a href="io_fflush.3.html"><code>io_fflush(3)</code></a> </li>
+ <li> <a href="io_fgetc.3.html"><code>io_fgetc(3)</code></a> </li>
+ <li> <a href="io_fgets.3.html"><code>io_fgets(3)</code></a> </li>
+ <li> <a href="io_fileno.3.html"><code>io_fileno(3)</code></a> </li>
+ <li> <a href="io_fprintf.3.html"><code>io_fprintf(3)</code></a> </li>
+ <li> <a href="io_fputc.3.html"><code>io_fputc(3)</code></a> </li>
+ <li> <a href="io_fputs.3.html"><code>io_fputs(3)</code></a> </li>
+ <li> <a href="io_fread.3.html"><code>io_fread(3)</code></a> </li>
+ <li> <a href="io_fwrite.3.html"><code>io_fwrite(3)</code></a> </li>
+ <li> <a href="io_get_inbufcount.3.html"><code>io_get_inbufcount(3)</code></a> </li>
+ <li> <a href="io_get_outbufcount.3.html"><code>io_get_outbufcount(3)</code></a> </li>
+ <li> <a href="io_pclose.3.html"><code>io_pclose(3)</code></a> </li>
+ <li> <a href="io_popen.3.html"><code>io_popen(3)</code></a> </li>
+ <li> <a href="io_setbufmode.3.html"><code>io_setbufmode(3)</code></a> </li>
+ <li> <a href="io_ungetc.3.html"><code>io_ungetc(3)</code></a> </li>
+ </ul>
+
+ <h3>HTTP server library</h3>
+
+ <ul>
+ <li> <a href="http_get_log_file.3.html"><code>http_get_log_file(3)</code></a> </li>
+ <li> <a href="http_get_servername.3.html"><code>http_get_servername(3)</code></a> </li>
+ <li> <a href="http_request_get_header.3.html"><code>http_request_get_header(3)</code></a> </li>
+ <li> <a href="http_request_get_headers.3.html"><code>http_request_get_headers(3)</code></a> </li>
+ <li> <a href="http_request_is_HEAD.3.html"><code>http_request_is_HEAD(3)</code></a> </li>
+ <li> <a href="http_request_method.3.html"><code>http_request_method(3)</code></a> </li>
+ <li> <a href="http_request_method_string.3.html"><code>http_request_method_string(3)</code></a> </li>
+ <li> <a href="http_request_nr_headers.3.html"><code>http_request_nr_headers(3)</code></a> </li>
+ <li> <a href="http_request_path.3.html"><code>http_request_path(3)</code></a> </li>
+ <li> <a href="http_request_query_string.3.html"><code>http_request_query_string(3)</code></a> </li>
+ <li> <a href="http_request_time.3.html"><code>http_request_time(3)</code></a> </li>
+ <li> <a href="http_request_url.3.html"><code>http_request_url(3)</code></a> </li>
+ <li> <a href="http_request_version.3.html"><code>http_request_version(3)</code></a> </li>
+ <li> <a href="http_response_end_headers.3.html"><code>http_response_end_headers(3)</code></a> </li>
+ <li> <a href="http_response_send_header.3.html"><code>http_response_send_header(3)</code></a> </li>
+ <li> <a href="http_response_send_headers.3.html"><code>http_response_send_headers(3)</code></a> </li>
+ <li> <a href="http_set_log_file.3.html"><code>http_set_log_file(3)</code></a> </li>
+ <li> <a href="http_set_servername.3.html"><code>http_set_servername(3)</code></a> </li>
+ <li> <a href="new_http_request.3.html"><code>new_http_request(3)</code></a> </li>
+ <li> <a href="new_http_response.3.html"><code>new_http_response(3)</code></a> </li>
+ </ul>
+
+ <h3>CGI library</h3>
+
+ <ul>
+ <li> <a href="cgi_erase.3.html"><code>cgi_erase(3)</code></a> </li>
+ <li> <a href="cgi_escape.3.html"><code>cgi_escape(3)</code></a> </li>
+ <li> <a href="cgi_get_post_max.3.html"><code>cgi_get_post_max(3)</code></a> </li>
+ <li> <a href="cgi_param.3.html"><code>cgi_param(3)</code></a> </li>
+ <li> <a href="cgi_param_list.3.html"><code>cgi_param_list(3)</code></a> </li>
+ <li> <a href="cgi_params.3.html"><code>cgi_params(3)</code></a> </li>
+ <li> <a href="cgi_set_post_max.3.html"><code>cgi_set_post_max(3)</code></a> </li>
+ <li> <a href="cgi_unescape.3.html"><code>cgi_unescape(3)</code></a> </li>
+ <li> <a href="copy_cgi.3.html"><code>copy_cgi(3)</code></a> </li>
+ <li> <a href="new_cgi.3.html"><code>new_cgi(3)</code></a> </li>
+ </ul>
+
+ <h3>Thread synchronisation (mutexes, R/W-locks, wait queues)</h3>
+
+ <ul>
+ <li> <a href="mutex_enter.3.html"><code>mutex_enter(3)</code></a> </li>
+ <li> <a href="mutex_leave.3.html"><code>mutex_leave(3)</code></a> </li>
+ <li> <a href="mutex_try_enter.3.html"><code>mutex_try_enter(3)</code></a> </li>
+ <li> <a href="new_mutex.3.html"><code>new_mutex(3)</code></a> </li>
+ <li> <a href="new_rwlock.3.html"><code>new_rwlock(3)</code></a> </li>
+ <li> <a href="new_wait_queue.3.html"><code>new_wait_queue(3)</code></a> </li>
+ <li> <a href="rwlock_enter_read.3.html"><code>rwlock_enter_read(3)</code></a> </li>
+ <li> <a href="rwlock_enter_write.3.html"><code>rwlock_enter_write(3)</code></a> </li>
+ <li> <a href="rwlock_leave.3.html"><code>rwlock_leave(3)</code></a> </li>
+ <li> <a href="rwlock_readers_have_priority.3.html"><code>rwlock_readers_have_priority(3)</code></a> </li>
+ <li> <a href="rwlock_try_enter_read.3.html"><code>rwlock_try_enter_read(3)</code></a> </li>
+ <li> <a href="rwlock_try_enter_write.3.html"><code>rwlock_try_enter_write(3)</code></a> </li>
+ <li> <a href="rwlock_writers_have_priority.3.html"><code>rwlock_writers_have_priority(3)</code></a> </li>
+ <li> <a href="wq_nr_sleepers.3.html"><code>wq_nr_sleepers(3)</code></a> </li>
+ <li> <a href="wq_sleep_on.3.html"><code>wq_sleep_on(3)</code></a> </li>
+ <li> <a href="wq_wake_up.3.html"><code>wq_wake_up(3)</code></a> </li>
+ <li> <a href="wq_wake_up_one.3.html"><code>wq_wake_up_one(3)</code></a> </li>
+ </ul>
+
+ <h3>FTP client library</h3>
+
+ <ul>
+ <li> <a href="ftpc_ascii.3.html"><code>ftpc_ascii(3)</code></a> </li>
+ <li> <a href="ftpc_binary.3.html"><code>ftpc_binary(3)</code></a> </li>
+ <li> <a href="ftpc_cdup.3.html"><code>ftpc_cdup(3)</code></a> </li>
+ <li> <a href="ftpc_cwd.3.html"><code>ftpc_cwd(3)</code></a> </li>
+ <li> <a href="ftpc_delete.3.html"><code>ftpc_delete(3)</code></a> </li>
+ <li> <a href="ftpc_dir.3.html"><code>ftpc_dir(3)</code></a> </li>
+ <li> <a href="ftpc_get.3.html"><code>ftpc_get(3)</code></a> </li>
+ <li> <a href="ftpc_login.3.html"><code>ftpc_login(3)</code></a> </li>
+ <li> <a href="ftpc_ls.3.html"><code>ftpc_ls(3)</code></a> </li>
+ <li> <a href="ftpc_mkdir.3.html"><code>ftpc_mkdir(3)</code></a> </li>
+ <li> <a href="ftpc_put.3.html"><code>ftpc_put(3)</code></a> </li>
+ <li> <a href="ftpc_pwd.3.html"><code>ftpc_pwd(3)</code></a> </li>
+ <li> <a href="ftpc_quit.3.html"><code>ftpc_quit(3)</code></a> </li>
+ <li> <a href="ftpc_quote.3.html"><code>ftpc_quote(3)</code></a> </li>
+ <li> <a href="ftpc_rmdir.3.html"><code>ftpc_rmdir(3)</code></a> </li>
+ <li> <a href="ftpc_set_passive_mode.3.html"><code>ftpc_set_passive_mode(3)</code></a> </li>
+ <li> <a href="ftpc_type.3.html"><code>ftpc_type(3)</code></a> </li>
+ <li> <a href="new_ftpc.3.html"><code>new_ftpc(3)</code></a> </li>
+ </ul>
+
+ <hr>
+ <address><a href="mailto:rich@annexia.org">Richard Jones</a></address>
+<!-- Created: Wed May 1 19:36:16 BST 2002 -->
+<!-- hhmts start -->
+Last modified: Sun Dec 1 14:44:00 GMT 2002
+<!-- hhmts end -->
+ </body>
+</html>