/*
httperf -- a tool for measuring web server performance
Copyright (C) 2000 Hewlett-Packard Company
Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
This file is part of httperf, a web server performance measurment
tool.
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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA
*/
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h> /* grrr, must come after sys/types.h for BSD */
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <httperf.h>
#include <call.h>
#include <core.h>
#include <event.h>
#include <http.h>
#include <conn.h>
#define HASH_TABLE_SIZE 1024 /* can't have more than this many servers */
#define MIN_IP_PORT IPPORT_RESERVED
#define MAX_IP_PORT 65535
#define BITSPERLONG (8*sizeof (u_long))
static int running = 1;
static int iteration;
static u_long max_burst_len;
static fd_set rdfds, wrfds;
static int min_sd = 0x7fffffff, max_sd = 0, alloced_sd_to_conn = 0;
static struct timeval select_timeout;
static struct sockaddr_in myaddr;
Conn **sd_to_conn;
static u_long port_free_map[((MAX_IP_PORT - MIN_IP_PORT + BITSPERLONG)
/ BITSPERLONG)];
static char http10req[] =
" HTTP/1.0\r\nUser-Agent: httperf/"VERSION"\r\nHost: ";
static char http11req[] =
" HTTP/1.1\r\nUser-Agent: httperf/"VERSION"\r\nHost: ";
static char http10req_nohost[] =
" HTTP/1.0\r\nUser-Agent: httperf/"VERSION"\r\n";
static char http11req_nohost[] =
" HTTP/1.1\r\nUser-Agent: httperf/"VERSION"\r\n";
#ifndef SOL_TCP
# define SOL_TCP 6 /* probably ought to do getprotlbyname () */
#endif
#ifdef TIME_SYSCALLS
# define SYSCALL(n,s) \
{ \
Time start, stop; \
do \
{ \
errno = 0; \
start = timer_now_forced (); \
s; /* execute the syscall */ \
stop = timer_now_forced (); \
syscall_time[SC_##n] += stop - start; \
++syscall_count[SC_##n]; \
} \
while (errno == EINTR); \
}
enum Syscalls
{
SC_BIND, SC_CONNECT, SC_READ, SC_SELECT, SC_SOCKET, SC_WRITEV,
SC_SSL_READ, SC_SSL_WRITEV,
SC_NUM_SYSCALLS
};
static const char * const syscall_name[SC_NUM_SYSCALLS] =
{
"bind", "connct", "read", "select", "socket", "writev",
"ssl_read", "ssl_writev"
};
static Time syscall_time[SC_NUM_SYSCALLS];
static u_int syscall_count[SC_NUM_SYSCALLS];
#else
# define SYSCALL(n,s) \
{ \
do \
{ \
errno = 0; \
s; \
} \
while (errno == EINTR); \
}
#endif
struct hash_entry
{
const char *hostname;
int port;
struct sockaddr_in sin;
}
hash_table[HASH_TABLE_SIZE];
static int
hash_code (const char *server, size_t server_len, int port)
{
u_char *cp = (u_char *) server;
u_long h = port;
u_long g;
int ch;
/* Basically the ELF hash algorithm: */
while ((ch = *cp++) != '\0')
{
h = (h << 4) + ch;
if ((g = (h & 0xf0000000)) != 0)
{
h ^= g >> 24;
h &= ~g;
}
}
return h;
}
static struct hash_entry*
hash_enter (const char *server, size_t server_len, int port,
struct sockaddr_in *sin)
{
struct hash_entry *he;
int index = hash_code (server, server_len, port) % HASH_TABLE_SIZE;
while (hash_table[index].hostname)
{
++index;
if (index >= HASH_TABLE_SIZE)
index = 0;
}
he = hash_table + index;
he->hostname = server;
he->port = port;
he->sin = *sin;
return he;
}
static struct sockaddr_in*
hash_lookup (const char *server, size_t server_len, int port)
{
int index, start_index;
index = start_index = hash_code (server, server_len, port) % HASH_TABLE_SIZE;
while (hash_table[index].hostname)
{
if (hash_table[index].port == port
&& strcmp (hash_table[index].hostname, server) == 0)
return &hash_table[index].sin;
++index;
if (index >= HASH_TABLE_SIZE)
index = 0;
if (index == start_index)
break;
}
return 0;
}
static int
lffs (long w)
{
int r;
if (sizeof (w) == sizeof (int))
r = ffs (w);
else
{
r = ffs (w);
#if SIZEOF_LONG > 4
if (r == 0)
{
r = ffs (w >> (8*sizeof (int)));
if (r > 0)
r += 8*sizeof (int);
}
#endif
}
return r;
}
static void
port_put (int port)
{
int i, bit;
port -= MIN_IP_PORT;
i = port / BITSPERLONG;
bit = port % BITSPERLONG;
port_free_map[i] |= (1UL << bit);
}
static int
port_get (void)
{
static u_long mask = ~0UL;
static int previous = 0;
int port, bit, i;
i = previous;
if ((port_free_map[i] & mask) == 0)
{
do
{
++i;
if (i >= NELEMS (port_free_map))
i = 0;
if (i == previous)
{
if (DBG > 0)
fprintf (stderr,
"%s.port_get: Yikes! I'm out of port numbers!\n",
prog_name);
return -1;
}
}
while (port_free_map[i] == 0);
mask = ~0UL;
}
previous = i;
bit = lffs (port_free_map[i] & mask) - 1;
if (bit >= BITSPERLONG - 1)
mask = 0;
else
mask = ~((1UL << (bit + 1)) - 1);
port_free_map[i] &= ~(1UL << bit);
port = bit + i*BITSPERLONG + MIN_IP_PORT;
return port;
}
static void
conn_failure (Conn *s, int err)
{
Any_Type arg;
arg.l = err;
event_signal (EV_CONN_FAILED, (Object *) s, arg);
core_close (s);
}
static void
conn_timeout (Timer *t, Any_Type arg)
{
Conn *s = arg.vp;
Time now;
Call *c;
assert (object_is_conn (s));
s->watchdog = 0;
if (DBG > 0)
{
c = 0;
if (s->sd >= 0)
{
now = timer_now ();
if (FD_ISSET (s->sd, &rdfds)
&& s->recvq && now >= s->recvq->timeout)
c = s->recvq;
else if (FD_ISSET (s->sd, &wrfds)
&& s->sendq && now >= s->sendq->timeout)
c = s->sendq;
}
if (DBG > 0)
{
fprintf (stderr, "connection_timeout");
if (c)
fprintf (stderr, ".%lu", c->id);
fprintf (stderr, ": t=%p, connection=%p\n", t, s);
}
}
arg.l = 0;
event_signal (EV_CONN_TIMEOUT, (Object *) s, arg);
core_close (s);
}
static void
set_active (Conn *s, fd_set *fdset)
{
int sd = s->sd;
Any_Type arg;
Time timeout;
FD_SET (sd, fdset);
if (sd < min_sd)
min_sd = sd;
if (sd >= max_sd)
max_sd = sd;
if (s->watchdog)
return;
timeout = 0.0;
if (s->sendq)
timeout = s->sendq->timeout;
if (s->recvq && (timeout == 0.0 || timeout > s->recvq->timeout))
timeout = s->recvq->timeout;
if (timeout > 0.0)
{
arg.vp = s;
s->watchdog = timer_schedule (conn_timeout, arg,
timeout - timer_now ());
}
}
static void
do_send (Conn *conn)
{
int async_errno, len;
struct iovec *iovp;
int sd = conn->sd;
ssize_t nsent = 0;
Any_Type arg;
Call *call;
while (1)
{
call = conn->sendq;
assert (call);
arg.l = 0;
event_signal (EV_CALL_SEND_RAW_DATA, (Object *) call, arg);
#ifdef HAVE_SSL
if (param.use_ssl)
{
extern ssize_t SSL_writev (SSL *, const struct iovec *, int);
SYSCALL (SSL_WRITEV,
nsent = SSL_writev(conn->ssl,
call->req.iov + call->req.iov_index,
(NELEMS (call->req.iov)
- call->req.iov_index)));
}
else
#endif
{
SYSCALL (WRITEV,
nsent = writev (sd, call->req.iov + call->req.iov_index,
(NELEMS (call->req.iov)
- call->req.iov_index)));
}
if (DBG > 0)
fprintf (stderr, "do_send.%lu: wrote %ld bytes on %p\n", call->id,
(long) nsent, conn);
if (nsent < 0)
{
if (errno == EAGAIN)
return;
len = sizeof (async_errno);
if (getsockopt (sd, SOL_SOCKET, SO_ERROR, &async_errno, &len) == 0
&& async_errno != 0)
errno = async_errno;
if (DBG > 0)
fprintf (stderr, "%s.do_send: writev() failed: %s\n",
prog_name, strerror (errno));
conn_failure (conn, errno);
return;
}
call->req.size += nsent;
iovp = call->req.iov + call->req.iov_index;
while (iovp < call->req.iov + NELEMS (call->req.iov))
{
if (nsent < iovp->iov_len)
{
iovp->iov_len -= nsent;
iovp->iov_base = (caddr_t) ((char *) iovp->iov_base + nsent);
break;
}
else
{
/* we're done with this fragment: */
nsent -= iovp->iov_len;
*iovp = call->req.iov_saved;
++iovp;
call->req.iov_saved = *iovp;
}
}
call->req.iov_index = iovp - call->req.iov;
if (call->req.iov_index < NELEMS (call->req.iov))
{
/* there are more header bytes to write */
call->timeout = param.timeout ? timer_now () + param.timeout : 0.0;
set_active (conn, &wrfds);
return;
}
/* we're done with sending this request */
conn->sendq = call->sendq_next;
if (!conn->sendq)
{
conn->sendq_tail = 0;
FD_CLR (sd, &wrfds);
}
arg.l = 0;
event_signal (EV_CALL_SEND_STOP, (Object *) call, arg);
if (conn->state >= S_CLOSING)
{
call_dec_ref (call);
return;
}
/* get ready to receive matching reply (note that we implicitly
pass on the reference to the call from the sendq to the
recvq): */
call->recvq_next = 0;
if (!conn->recvq)
conn->recvq = conn->recvq_tail = call;
else
{
conn->recvq_tail->recvq_next = call;
conn->recvq_tail = call;
}
call->timeout = param.timeout + param.think_timeout;
if (call->timeout > 0.0)
call->timeout += timer_now ();
set_active (conn, &rdfds);
if (conn->state < S_REPLY_STATUS)
conn->state = S_REPLY_STATUS; /* expecting reply status */
if (!conn->sendq)
return;
arg.l = 0;
event_signal (EV_CALL_SEND_START, (Object *) conn->sendq, arg);
if (conn->state >= S_CLOSING)
return;
}
}
static void
recv_done (Call *call)
{
Conn *conn = call->conn;
Any_Type arg;
conn->recvq = call->recvq_next;
if (!conn->recvq)
{
FD_CLR (conn->sd, &rdfds);
conn->recvq_tail = 0;
}
/* we're done with receiving this request */
arg.l = 0;
event_signal (EV_CALL_RECV_STOP, (Object *) call, arg);
call_dec_ref (call);
}
static void
do_recv (Conn *s)
{
char *cp, buf[8193];
Call *c = s->recvq;
int i, saved_errno;
ssize_t nread = 0;
size_t buf_len;
assert (c);
#ifdef HAVE_SSL
if (param.use_ssl)
{
SYSCALL (SSL_READ,
nread = SSL_read (s->ssl, buf, sizeof (buf) - 1));
}
else
#endif
{
SYSCALL (READ,
nread = read (s->sd, buf, sizeof (buf) - 1));
}
saved_errno = errno;
if (nread <= 0)
{
if (DBG > 0)
{
fprintf (stderr, "do_recv.%lu: received %lu reply bytes on %p\n",
c->id,
(u_long) (c->reply.header_bytes + c->reply.content_bytes),
s);
if (nread < 0)
fprintf (stderr, "%s.do_recv: read() failed: %s\n",
prog_name, strerror (saved_errno));
}
if (nread < 0)
{
if (saved_errno != EAGAIN)
conn_failure (s, saved_errno);
}
else if (s->state != S_REPLY_DATA)
conn_failure (s, ECONNRESET);
else
{
if (s->state < S_CLOSING)
s->state = S_REPLY_DONE;
recv_done (c);
}
return;
}
buf[nread] = '\0'; /* ensure buffer is '\0' terminated */
if (DBG > 3)
{
/* dump received data in hex & ascii: */
fprintf (stderr, "do_recv.%lu: received reply data:\n", c->id);
for (cp = buf; cp < buf + nread; )
{
fprintf (stderr, " %04x:",
(int) (c->reply.header_bytes + c->reply.content_bytes
+ (cp - buf)));
for (i = 0; i < 16 && i < buf + nread - cp; ++i)
fprintf (stderr, " %02x", cp[i] & 0xff);
i *= 3;
while (i++ < 50)
fputc (' ', stderr);
for (i = 0; i < 16 && cp < buf + nread; ++i, ++cp)
fprintf (stderr, "%c", isprint (*cp) ? *cp : '.');
fprintf (stderr, "\n");
}
}
/* process the replies in this buffer: */
buf_len = nread;
cp = buf;
do
{
c = s->recvq;
assert (c);
http_process_reply_bytes (c, &cp, &buf_len);
if (s->state == S_REPLY_DONE)
{
recv_done (c);
if (s->state >= S_CLOSING)
return;
s->state = S_REPLY_STATUS;
}
}
while (buf_len > 0);
if (s->recvq)
set_active (c->conn, &rdfds);
}
struct sockaddr_in*
core_addr_intern (const char *server, size_t server_len, int port)
{
struct sockaddr_in sin;
struct hash_entry *h;
struct hostent *he;
Any_Type arg;
memset (&sin, 0, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (port);
arg.cvp = server;
event_signal (EV_HOSTNAME_LOOKUP_START, 0, arg);
he = gethostbyname (server);
event_signal (EV_HOSTNAME_LOOKUP_STOP, 0, arg);
if (he)
{
if (he->h_addrtype != AF_INET
|| he->h_length != sizeof (sin.sin_addr))
{
fprintf (stderr, "%s: can't deal with addr family %d or size %d\n",
prog_name, he->h_addrtype, he->h_length);
exit (1);
}
memcpy (&sin.sin_addr, he->h_addr_list[0], sizeof (sin.sin_addr));
}
else
{
if (!inet_aton (server, &sin.sin_addr))
{
fprintf (stderr, "%s.core_addr_intern: invalid server address %s\n",
prog_name, server);
exit (1);
}
}
h = hash_enter (server, server_len, port, &sin);
if (!h)
return 0;
return &h->sin;
}
void
core_init (void)
{
struct rlimit rlimit;
memset (&hash_table, 0, sizeof (hash_table));
memset (&rdfds, 0, sizeof (rdfds));
memset (&wrfds, 0, sizeof (wrfds));
memset (&myaddr, 0, sizeof (myaddr));
memset (&port_free_map, 0xff, sizeof (port_free_map));
/* Don't disturb just because a TCP connection closed on us... */
signal (SIGPIPE, SIG_IGN);
#ifdef DONT_POLL
/* This causes select() to take several milliseconds on both
Linux/x86 and HP-UX 10.20. */
select_timeout.tv_sec = (u_long) TIMER_INTERVAL;
select_timeout.tv_usec = (u_long) (TIMER_INTERVAL * 1e6);
#else
/* This causes httperf to become a CPU hog as it polls for
filedescriptors to become readable/writable. This is OK as long
as httperf is the only (interesting) user-level process that
executes on a machine. */
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 0;
#endif
/* boost open file limit to the max: */
if (getrlimit (RLIMIT_NOFILE, &rlimit) < 0)
{
fprintf (stderr, "%s: failed to get number of open file limit: %s",
prog_name, strerror (errno));
exit (1);
}
if (rlimit.rlim_max > FD_SETSIZE)
{
fprintf (stderr, "%s: warning: open file limit > FD_SETSIZE; "
"limiting max. # of open files to FD_SETSIZE\n", prog_name);
rlimit.rlim_max = FD_SETSIZE;
}
rlimit.rlim_cur = rlimit.rlim_max;
if (setrlimit (RLIMIT_NOFILE, &rlimit) < 0)
{
fprintf (stderr, "%s: failed to increase number of open file limit: %s",
prog_name, strerror (errno));
exit (1);
}
if (verbose)
printf ("%s: maximum number of open descriptors = %ld\n",
prog_name, rlimit.rlim_max);
if (param.server)
core_addr_intern (param.server, strlen (param.server), param.port);
}
#ifdef HAVE_SSL
void
core_ssl_connect (Conn *s)
{
Any_Type arg;
int ssl_err;
if (DBG > 2)
fprintf (stderr, "core_ssl_connect(conn=%p)\n", (void *) s);
if (SSL_set_fd (s->ssl, s->sd) == 0)
{
ERR_print_errors_fp (stderr);
exit (-1);
}
ssl_err = SSL_connect (s->ssl);
if (ssl_err < 0)
{
int reason = SSL_get_error(s->ssl, ssl_err);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
if (DBG > 2)
fprintf (stderr, "core_ssl_connect: want to %s more...\n",
(reason == SSL_ERROR_WANT_READ) ? "read" : "write");
if (reason == SSL_ERROR_WANT_READ && !FD_ISSET (s->sd, &rdfds))
{
FD_CLR (s->sd, &wrfds);
set_active (s, &rdfds);
}
else if (reason == SSL_ERROR_WANT_WRITE && !FD_ISSET (s->sd, &wrfds))
{
FD_CLR (s->sd, &rdfds);
set_active (s, &wrfds);
}
return;
}
fprintf (stderr,
"%s: failed to connect to SSL server (err=%d, reason=%d)\n",
prog_name, ssl_err, reason);
ERR_print_errors_fp (stderr);
exit (-1);
}
s->state = S_CONNECTED;
if (DBG > 0)
fprintf (stderr, "core_ssl_connect: SSL is connected!\n");
if (DBG > 1)
{
SSL_CIPHER *ssl_cipher;
ssl_cipher = SSL_get_current_cipher (s->ssl);
if (!ssl_cipher)
fprintf (stderr, "core_ssl_connect: server refused all client cipher "
"suites!\n");
else
fprintf (stderr, "core_ssl_connect: cipher=%s, valid=%d, id=%lu\n",
ssl_cipher->name, ssl_cipher->valid, ssl_cipher->id);
}
arg.l = 0;
event_signal (EV_CONN_CONNECTED, (Object *) s, arg);
}
#endif /* HAVE_SSL */
int
core_connect (Conn *s)
{
int sd, result, len, async_errno;
struct sockaddr_in *sin;
struct linger linger;
int myport, optval;
Any_Type arg;
static int prev_iteration = -1;
static u_long burst_len;
if (iteration == prev_iteration)
++burst_len;
else
{
if (burst_len > max_burst_len)
max_burst_len = burst_len;
burst_len = 1;
prev_iteration = iteration;
}
SYSCALL (SOCKET,
sd = socket (AF_INET, SOCK_STREAM, 0));
if (sd < 0)
{
if (DBG > 0)
fprintf (stderr, "%s.core_connect.socket: %s (max_sd=%d)\n",
prog_name, strerror (errno), max_sd);
goto failure;
}
if (fcntl (sd, F_SETFL, O_NONBLOCK) < 0)
{
fprintf (stderr, "%s.core_connect.fcntl: %s\n",
prog_name, strerror (errno));
goto failure;
}
if (param.close_with_reset)
{
linger.l_onoff = 1;
linger.l_linger = 0;
if (setsockopt (sd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger)) < 0)
{
fprintf (stderr, "%s.core_connect.setsockopt(SO_LINGER): %s\n",
prog_name, strerror (errno));
goto failure;
}
}
/* Disable Nagle algorithm so we don't delay needlessly when
pipelining requests. */
optval = 1;
if (setsockopt (sd, SOL_TCP, TCP_NODELAY, &optval, sizeof (optval)) < 0)
{
fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n",
prog_name, strerror (errno));
goto failure;
}
optval = param.send_buffer_size;
if (setsockopt (sd, SOL_SOCKET, SO_SNDBUF, &optval, sizeof (optval)) < 0)
{
fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n",
prog_name, strerror (errno));
goto failure;
}
optval = param.recv_buffer_size;
if (setsockopt (sd, SOL_SOCKET, SO_RCVBUF, &optval, sizeof (optval)) < 0)
{
fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n",
prog_name, strerror (errno));
goto failure;
}
s->sd = sd;
if (sd >= alloced_sd_to_conn)
{
size_t size, old_size;
old_size = alloced_sd_to_conn * sizeof (sd_to_conn[0]);
alloced_sd_to_conn += 2048;
size = alloced_sd_to_conn * sizeof (sd_to_conn[0]);
if (sd_to_conn)
sd_to_conn = realloc (sd_to_conn, size);
else
sd_to_conn = malloc (size);
if (!sd_to_conn)
{
if (DBG > 0)
fprintf (stderr, "%s.core_connect.realloc: %s\n",
prog_name, strerror (errno));
goto failure;
}
memset ((char *) sd_to_conn + old_size, 0, size - old_size);
}
assert (!sd_to_conn[sd]);
sd_to_conn[sd] = s;
sin = hash_lookup (s->hostname, s->hostname_len, s->port);
if (!sin)
{
if (DBG > 0)
fprintf (stderr, "%s.core_connect: unknown server/port %s:%d\n",
prog_name, s->hostname, s->port);
goto failure;
}
arg.l = 0;
event_signal (EV_CONN_CONNECTING, (Object *) s, arg);
if (s->state >= S_CLOSING)
goto failure;
if (param.hog)
{
while (1)
{
myport = port_get ();
if (myport < 0)
goto failure;
myaddr.sin_port = htons (myport);
SYSCALL (BIND,
result = bind (sd, &myaddr, sizeof (myaddr)));
if (result == 0)
break;
if (errno != EADDRINUSE && errno == EADDRNOTAVAIL)
{
if (DBG > 0)
fprintf (stderr, "%s.core_connect.bind: %s\n",
prog_name, strerror (errno));
goto failure;
}
}
s->myport = myport;
}
SYSCALL (CONNECT,
result = connect (sd, sin, sizeof (*sin)));
if (result == 0)
{
#ifdef HAVE_SSL
if (param.use_ssl)
core_ssl_connect (s);
else
#endif
{
s->state = S_CONNECTED;
arg.l = 0;
event_signal (EV_CONN_CONNECTED, (Object *) s, arg);
}
}
else if (errno == EINPROGRESS)
{
/* The socket becomes writable only after the connection has
been established. Hence we wait for writability to
detect connection establishment. */
s->state = S_CONNECTING;
set_active (s, &wrfds);
if (param.timeout > 0.0)
{
arg.vp = s;
assert (!s->watchdog);
s->watchdog = timer_schedule (conn_timeout, arg, param.timeout);
}
}
else
{
len = sizeof (async_errno);
if (getsockopt (sd, SOL_SOCKET, SO_ERROR, &async_errno, &len) == 0
&& async_errno != 0)
errno = async_errno;
if (DBG > 0)
fprintf (stderr, "%s.core_connect.connect: %s (max_sd=%d)\n",
prog_name, strerror (errno), max_sd);
goto failure;
}
return 0;
failure:
conn_failure (s, errno);
return -1;
}
int
core_send (Conn *conn, Call *call)
{
Any_Type arg;
arg.l = 0;
event_signal (EV_CALL_ISSUE, (Object *) call, arg);
call->conn = conn; /* NO refcounting here (see call.h). */
if (param.no_host_hdr)
{
call->req.iov[IE_HOST].iov_base = (caddr_t) "";
call->req.iov[IE_HOST].iov_len = 0;
}
else if (!call->req.iov[IE_HOST].iov_base)
{
/* Default call's hostname to connection's hostname: */
call->req.iov[IE_HOST].iov_base = (caddr_t) conn->hostname;
call->req.iov[IE_HOST].iov_len = conn->hostname_len;
}
/* NOTE: the protocol version indicates what the _client_ can
understand. If we send HTTP/1.1, it doesn't mean that the server
has to speak HTTP/1.1. In other words, sending an HTTP/1.1
header leaves it up to the server whether it wants to reply with
a 1.0 or 1.1 reply. */
switch (call->req.version)
{
case 0x10000:
if (param.no_host_hdr)
{
call->req.iov[IE_PROTL].iov_base = (caddr_t) http10req_nohost;
call->req.iov[IE_PROTL].iov_len = sizeof (http10req_nohost) - 1;
}
else
{
call->req.iov[IE_PROTL].iov_base = (caddr_t) http10req;
call->req.iov[IE_PROTL].iov_len = sizeof (http10req) - 1;
}
break;
case 0x10001:
if (param.no_host_hdr)
{
call->req.iov[IE_PROTL].iov_base = http11req_nohost;
call->req.iov[IE_PROTL].iov_len = sizeof (http11req_nohost) - 1;
}
else
{
call->req.iov[IE_PROTL].iov_base = http11req;
call->req.iov[IE_PROTL].iov_len = sizeof (http11req) - 1;
}
break;
default:
fprintf (stderr, "%s: unexpected version code %x\n",
prog_name, call->req.version);
exit (1);
}
call->req.iov_index = 0;
call->req.iov_saved = call->req.iov[0];
/* insert call into connection's send queue: */
call_inc_ref (call);
call->sendq_next = 0;
if (!conn->sendq)
{
conn->sendq = conn->sendq_tail = call;
arg.l = 0;
event_signal (EV_CALL_SEND_START, (Object *) call, arg);
if (conn->state >= S_CLOSING)
return -1;
call->timeout = param.timeout ? timer_now () + param.timeout : 0.0;
set_active (conn, &wrfds);
}
else
{
conn->sendq_tail->sendq_next = call;
conn->sendq_tail = call;
}
return 0;
}
void
core_close (Conn *conn)
{
Call *call, *call_next;
Any_Type arg;
int sd;
if (conn->state >= S_CLOSING)
return; /* guard against recursive calls */
conn->state = S_CLOSING;
if (DBG >= 10)
fprintf (stderr, "%s.core_close(conn=%p)\n", prog_name, conn);
if (conn->watchdog)
{
timer_cancel (conn->watchdog);
conn->watchdog = 0;
}
/* first, get rid of all pending calls: */
for (call = conn->sendq; call; call = call_next)
{
call_next = call->sendq_next;
call_dec_ref (call);
}
conn->sendq = 0;
for (call = conn->recvq; call; call = call_next)
{
call_next = call->recvq_next;
call_dec_ref (call);
}
conn->recvq = 0;
sd = conn->sd;
conn->sd = -1;
arg.l = 0;
event_signal (EV_CONN_CLOSE, (Object *) conn, arg);
assert (conn->state == S_CLOSING);
#ifdef HAVE_SSL
if (param.use_ssl)
SSL_shutdown (conn->ssl);
#endif
if (sd >= 0)
{
close (sd);
sd_to_conn[sd] = 0;
FD_CLR (sd, &wrfds);
FD_CLR (sd, &rdfds);
}
if (conn->myport > 0)
port_put (conn->myport);
/* A connection that has been closed is not useful anymore, so we
give up the reference obtained when creating the session. This
normally initiates destruction of the connection. */
conn_dec_ref (conn);
}
void
core_loop (void)
{
int is_readable, is_writable, n, sd, bit, min_i, max_i, i = 0;
fd_set readable, writable;
fd_mask mask;
Any_Type arg;
Conn *conn;
while (running)
{
struct timeval tv = select_timeout;
timer_tick ();
readable = rdfds;
writable = wrfds;
min_i = min_sd / NFDBITS;
max_i = max_sd / NFDBITS;
SYSCALL (SELECT,
n = select (max_sd + 1, &readable, &writable, 0, &tv));
++iteration;
if (n <= 0)
{
if (n < 0)
{
fprintf (stderr, "%s.core_loop: select failed: %s\n",
prog_name, strerror (errno));
exit (1);
}
continue;
}
while (n > 0)
{
/* find the index of the fdmask that has something going on: */
do
{
++i;
if (i > max_i)
i = min_i;
assert (i <= max_i);
mask = readable.fds_bits[i] | writable.fds_bits[i];
}
while (!mask);
bit = 0;
sd = i*NFDBITS + bit;
do
{
if (mask & 1)
{
--n;
is_readable =
(FD_ISSET (sd, &readable) && FD_ISSET (sd, &rdfds));
is_writable =
(FD_ISSET (sd, &writable) && FD_ISSET (sd, &wrfds));
if (is_readable || is_writable)
{
/* only handle sockets that haven't timed out yet */
conn = sd_to_conn[sd];
conn_inc_ref (conn);
if (conn->watchdog)
{
timer_cancel (conn->watchdog);
conn->watchdog = 0;
}
if (conn->state == S_CONNECTING)
{
#ifdef HAVE_SSL
if (param.use_ssl)
core_ssl_connect (conn);
else
#endif
if (is_writable)
{
FD_CLR (sd, &wrfds);
conn->state = S_CONNECTED;
arg.l = 0;
event_signal (EV_CONN_CONNECTED,
(Object *) conn, arg);
}
}
else
{
if (is_writable && conn->sendq)
do_send (conn);
if (is_readable && conn->recvq)
do_recv (conn);
}
conn_dec_ref (conn);
if (n > 0)
timer_tick ();
}
}
mask = ((u_long) mask) >> 1;
++sd;
}
while (mask);
}
}
}
void
core_exit (void)
{
running = 0;
printf ("Maximum connect burst length: %lu\n", max_burst_len);
#ifdef TIME_SYSCALLS
{
u_int count;
Time time;
int i;
printf ("Average syscall execution times:\n");
for (i = 0; i < NELEMS (syscall_name); ++i)
{
count = syscall_count[i];
time = syscall_time[i];
printf ("\t%s:\t%.3f ms/call (%.3fs total, %u calls)\n",
syscall_name[i], count > 0 ? 1e3*time/count : 0, time, count);
}
putchar ('\n');
}
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1