Updates and enhancements to the multi-threaded httpd performance test

tool:

- Use uname(3) to query the OS name to report in the HTTP headers.
  This is probably more useful than hard-coding FreeBSD.

- If no path is specified, create a 1k temporary file and send that
  instead.  Pass a file descriptor into http_serve() rather than using
  a global fd.

- Add more carriage returns to the HTTP headers to be a bit more
  correct.  (Suggested by: andre)

- Read to a buffer rather than a single character to reduce the number
  of recv() system calls pulling in the HTTP request.

- Properly wait for two, not one, \n's on input.
This commit is contained in:
Robert Watson 2005-10-25 18:47:12 +00:00
parent ef74f2c9c5
commit 636edd1f9b

View File

@ -29,6 +29,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/utsname.h>
#include <netinet/in.h>
@ -36,6 +37,7 @@
#include <err.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@ -44,43 +46,65 @@
/*
* Simple, multi-threaded HTTP server. Very dumb.
*
* If a path is specified as an argument, only that file is served. If no
* path is specified, httpd will create one file to send per server thread.
*/
#define THREADS 128
#define BUFFER (48*1024)
#define THREADS 128
#define BUFFER 1024
#define FILESIZE 1024
#define HTTP_OK "HTTP/1.1 200 OK\n"
#define HTTP_SERVER "Server rwatson_httpd/1.0 (FreeBSD)\n"
#define HTTP_CONNECTION "Connection: close\n"
#define HTTP_CONTENT "Content-Type: text/html\n"
#define HTTP_OK "HTTP/1.1 200 OK\n"
#define HTTP_SERVER1 "Server rwatson_httpd/1.0 ("
#define HTTP_SERVER2 ")\n"
#define HTTP_CONNECTION "Connection: close\n"
#define HTTP_CONTENT "Content-Type: text/html\n\n"
struct httpd_thread_state {
pthread_t hts_thread;
int hts_fd;
} hts[THREADS];
static const char *path;
static int listen_sock;
static int data_file;
static int listen_sock;
static struct utsname utsname;
/*
* Given an open client socket, process its request. No notion of timeout.
*/
static int
http_serve(int sock)
http_serve(int sock, int fd)
{
struct iovec header_iovec[4];
struct iovec header_iovec[6];
struct sf_hdtr sf_hdtr;
char buffer[BUFFER];
ssize_t len;
int ncount;
char ch;
int i, ncount;
/* Read until \n\n. Not very smart. */
ncount = 0;
while (1) {
len = recv(sock, &ch, sizeof(ch), 0);
len = recv(sock, buffer, BUFFER, 0);
if (len < 0) {
warn("recv");
return (-1);
}
if (len == 0)
return (-1);
if (ch == '\n')
ncount++;
for (i = 0; i < len; i++) {
switch (buffer[i]) {
case '\n':
ncount++;
break;
case '\r':
break;
default:
ncount = 0;
}
}
if (ncount == 2)
break;
}
@ -89,18 +113,22 @@ http_serve(int sock)
bzero(&header_iovec, sizeof(header_iovec));
header_iovec[0].iov_base = HTTP_OK;
header_iovec[0].iov_len = strlen(HTTP_OK);
header_iovec[1].iov_base = HTTP_SERVER;
header_iovec[1].iov_len = strlen(HTTP_SERVER);
header_iovec[2].iov_base = HTTP_CONNECTION;
header_iovec[2].iov_len = strlen(HTTP_CONNECTION);
header_iovec[3].iov_base = HTTP_CONTENT;
header_iovec[3].iov_len = strlen(HTTP_CONTENT);
header_iovec[1].iov_base = HTTP_SERVER1;
header_iovec[1].iov_len = strlen(HTTP_SERVER1);
header_iovec[2].iov_base = utsname.sysname;
header_iovec[2].iov_len = strlen(utsname.sysname);
header_iovec[3].iov_base = HTTP_SERVER2;
header_iovec[3].iov_len = strlen(HTTP_SERVER2);
header_iovec[4].iov_base = HTTP_CONNECTION;
header_iovec[4].iov_len = strlen(HTTP_CONNECTION);
header_iovec[5].iov_base = HTTP_CONTENT;
header_iovec[5].iov_len = strlen(HTTP_CONTENT);
sf_hdtr.headers = header_iovec;
sf_hdtr.hdr_cnt = 4;
sf_hdtr.hdr_cnt = 6;
sf_hdtr.trailers = NULL;
sf_hdtr.trl_cnt = 0;
if (sendfile(data_file, sock, 0, 0, &sf_hdtr, NULL, 0) < 0)
if (sendfile(fd, sock, 0, 0, &sf_hdtr, NULL, 0) < 0)
warn("sendfile");
return (0);
@ -109,13 +137,16 @@ http_serve(int sock)
static void *
httpd_worker(void *arg)
{
struct httpd_thread_state *htsp;
int sock;
htsp = arg;
while (1) {
sock = accept(listen_sock, NULL, NULL);
if (sock < 0)
continue;
(void)http_serve(sock);
(void)http_serve(sock, htsp->hts_fd);
close(sock);
}
}
@ -123,12 +154,17 @@ httpd_worker(void *arg)
int
main(int argc, char *argv[])
{
pthread_t thread_array[THREADS];
u_char filebuffer[FILESIZE];
char temppath[PATH_MAX];
struct sockaddr_in sin;
ssize_t len;
int i;
if (argc != 3)
errx(-1, "usage: http [port] [path]");
if (argc != 2 && argc != 3)
errx(-1, "usage: http port [path]");
if (uname(&utsname) < 0)
err(-1, "utsname");
listen_sock = socket(PF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
@ -139,10 +175,33 @@ main(int argc, char *argv[])
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[1]));
/*
* If a path is specified, use it. Otherwise, create temporary files
* with some data for each thread.
*/
path = argv[2];
data_file = open(path, O_RDONLY);
if (data_file < 0)
err(-1, "open: %s", path);
if (path != NULL) {
data_file = open(path, O_RDONLY);
if (data_file < 0)
err(-1, "open: %s", path);
for (i = 0; i < THREADS; i++)
hts[i].hts_fd = data_file;
} else {
memset(filebuffer, 'A', FILESIZE - 1);
filebuffer[FILESIZE - 1] = '\n';
for (i = 0; i < THREADS; i++) {
snprintf(temppath, PATH_MAX, "/tmp/httpd.XXXXXXXXXXX");
hts[i].hts_fd = mkstemp(temppath);
if (hts[i].hts_fd < 0)
err(-1, "mkstemp");
(void)unlink(temppath);
len = write(hts[i].hts_fd, filebuffer, FILESIZE);
if (len < 0)
err(-1, "write");
if (len < FILESIZE)
errx(-1, "write: short");
}
}
if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
err(-1, "bind");
@ -151,13 +210,13 @@ main(int argc, char *argv[])
err(-1, "listen");
for (i = 0; i < THREADS; i++) {
if (pthread_create(&thread_array[i], NULL, httpd_worker,
NULL) < 0)
if (pthread_create(&hts[i].hts_thread, NULL, httpd_worker,
&hts[i]) < 0)
err(-1, "pthread_create");
}
for (i = 0; i < THREADS; i++) {
if (pthread_join(thread_array[i], NULL) < 0)
if (pthread_join(hts[i].hts_thread, NULL) < 0)
err(-1, "pthread_join");
}
return (0);