nghttpd: Add multi threading support
This commit is contained in:
parent
d4ea2418d8
commit
caeeba681f
|
@ -35,6 +35,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ Config::Config()
|
||||||
: data_ptr(nullptr),
|
: data_ptr(nullptr),
|
||||||
output_upper_thres(1024*1024),
|
output_upper_thres(1024*1024),
|
||||||
padding(0),
|
padding(0),
|
||||||
|
num_worker(1),
|
||||||
header_table_size(-1),
|
header_table_size(-1),
|
||||||
port(0),
|
port(0),
|
||||||
verbose(false),
|
verbose(false),
|
||||||
|
@ -92,14 +94,14 @@ public:
|
||||||
Sessions(event_base *evbase, const Config *config, SSL_CTX *ssl_ctx)
|
Sessions(event_base *evbase, const Config *config, SSL_CTX *ssl_ctx)
|
||||||
: evbase_(evbase),
|
: evbase_(evbase),
|
||||||
config_(config),
|
config_(config),
|
||||||
ssl_ctx_(ssl_ctx)
|
ssl_ctx_(ssl_ctx),
|
||||||
|
next_session_id_(1)
|
||||||
{}
|
{}
|
||||||
~Sessions()
|
~Sessions()
|
||||||
{
|
{
|
||||||
for(auto handler : handlers_) {
|
for(auto handler : handlers_) {
|
||||||
delete handler;
|
delete handler;
|
||||||
}
|
}
|
||||||
SSL_CTX_free(ssl_ctx_);
|
|
||||||
}
|
}
|
||||||
void add_handler(Http2Handler *handler)
|
void add_handler(Http2Handler *handler)
|
||||||
{
|
{
|
||||||
|
@ -135,11 +137,43 @@ public:
|
||||||
{
|
{
|
||||||
return evbase_;
|
return evbase_;
|
||||||
}
|
}
|
||||||
|
int64_t get_next_session_id()
|
||||||
|
{
|
||||||
|
auto session_id = next_session_id_;
|
||||||
|
if(next_session_id_ == INT64_MAX) {
|
||||||
|
next_session_id_ = 1;
|
||||||
|
}
|
||||||
|
return session_id;
|
||||||
|
}
|
||||||
|
void accept_connection(int fd)
|
||||||
|
{
|
||||||
|
int val = 1;
|
||||||
|
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
||||||
|
reinterpret_cast<char *>(&val), sizeof(val));
|
||||||
|
SSL *ssl = nullptr;
|
||||||
|
if(ssl_ctx_) {
|
||||||
|
ssl = ssl_session_new(fd);
|
||||||
|
if(!ssl) {
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto handler = util::make_unique<Http2Handler>(this, fd, ssl,
|
||||||
|
get_next_session_id());
|
||||||
|
handler->setup_bev();
|
||||||
|
if(!ssl) {
|
||||||
|
if(handler->on_connect() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_handler(handler.release());
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
std::set<Http2Handler*> handlers_;
|
std::set<Http2Handler*> handlers_;
|
||||||
event_base *evbase_;
|
event_base *evbase_;
|
||||||
const Config *config_;
|
const Config *config_;
|
||||||
SSL_CTX *ssl_ctx_;
|
SSL_CTX *ssl_ctx_;
|
||||||
|
int64_t next_session_id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -971,43 +1005,99 @@ void fill_callback(nghttp2_session_callbacks& callbacks, const Config *config)
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
struct ClientInfo {
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void worker_readcb(bufferevent *bev, void *arg)
|
||||||
|
{
|
||||||
|
auto sessions = static_cast<Sessions*>(arg);
|
||||||
|
auto input = bufferevent_get_input(bev);
|
||||||
|
while(evbuffer_get_length(input) >= sizeof(ClientInfo)) {
|
||||||
|
ClientInfo client;
|
||||||
|
evbuffer_remove(input, &client, sizeof(client));
|
||||||
|
sessions->accept_connection(client.fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void run_worker(int thread_id, int fd, SSL_CTX *ssl_ctx, const Config *config)
|
||||||
|
{
|
||||||
|
auto evbase = event_base_new();
|
||||||
|
auto bev = bufferevent_socket_new(evbase, fd,
|
||||||
|
BEV_OPT_DEFER_CALLBACKS |
|
||||||
|
BEV_OPT_CLOSE_ON_FREE);
|
||||||
|
auto sessions = Sessions(evbase, config, ssl_ctx);
|
||||||
|
|
||||||
|
bufferevent_enable(bev, EV_READ);
|
||||||
|
bufferevent_setcb(bev, worker_readcb, nullptr, nullptr, &sessions);
|
||||||
|
event_base_loop(evbase, 0);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class ListenEventHandler {
|
class ListenEventHandler {
|
||||||
public:
|
public:
|
||||||
ListenEventHandler(Sessions *sessions, int64_t *session_id_seed_ptr)
|
ListenEventHandler(Sessions *sessions, const Config *config)
|
||||||
: sessions_(sessions),
|
: sessions_(sessions),
|
||||||
session_id_seed_ptr_(session_id_seed_ptr)
|
config_(config),
|
||||||
{}
|
next_worker_(0)
|
||||||
void accept_connection(int fd, sockaddr *addr, int addrlen)
|
|
||||||
{
|
{
|
||||||
int rv;
|
int rv;
|
||||||
int val = 1;
|
if(config_->num_worker == 1) {
|
||||||
SSL *ssl = nullptr;
|
return;
|
||||||
rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
}
|
||||||
reinterpret_cast<char *>(&val), sizeof(val));
|
for(size_t i = 0; i < config_->num_worker; ++i) {
|
||||||
|
if(config_->verbose) {
|
||||||
|
std::cerr << "spawning thread #" << i << std::endl;
|
||||||
|
}
|
||||||
|
int socks[2];
|
||||||
|
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
|
||||||
if(rv == -1) {
|
if(rv == -1) {
|
||||||
std::cerr << "Setting option TCP_NODELAY failed: errno="
|
std::cerr << "socketpair() failed: errno=" << errno << std::endl;
|
||||||
<< errno << std::endl;
|
assert(0);
|
||||||
}
|
}
|
||||||
if(sessions_->get_ssl_ctx()) {
|
evutil_make_socket_nonblocking(socks[0]);
|
||||||
ssl = sessions_->ssl_session_new(fd);
|
evutil_make_socket_nonblocking(socks[1]);
|
||||||
if(!ssl) {
|
auto bev = bufferevent_socket_new(sessions_->get_evbase(), socks[0],
|
||||||
|
BEV_OPT_DEFER_CALLBACKS |
|
||||||
|
BEV_OPT_CLOSE_ON_FREE);
|
||||||
|
if(!bev) {
|
||||||
|
std::cerr << "bufferevent_socket_new() failed" << std::endl;
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
workers_.push_back(bev);
|
||||||
|
auto t = std::thread(run_worker, i, socks[1], sessions_->get_ssl_ctx(),
|
||||||
|
config_);
|
||||||
|
t.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void accept_connection(int fd, sockaddr *addr, int addrlen)
|
||||||
|
{
|
||||||
|
if(config_->num_worker == 1) {
|
||||||
|
sessions_->accept_connection(fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Dispatch client to the one of the worker threads, in a round
|
||||||
|
// robin manner.
|
||||||
|
auto client = ClientInfo{fd};
|
||||||
|
bufferevent_write(workers_[next_worker_], &client, sizeof(client));
|
||||||
|
if(next_worker_ == config_->num_worker - 1) {
|
||||||
|
next_worker_ = 0;
|
||||||
|
} else {
|
||||||
|
++next_worker_;
|
||||||
}
|
}
|
||||||
int64_t session_id = ++(*session_id_seed_ptr_);
|
|
||||||
auto handler = util::make_unique<Http2Handler>(sessions_, fd, ssl,
|
|
||||||
session_id);
|
|
||||||
handler->setup_bev();
|
|
||||||
if(!ssl) {
|
|
||||||
if(handler->on_connect() != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sessions_->add_handler(handler.release());
|
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
// In multi threading mode, this includes bufferevent to dispatch
|
||||||
|
// client to the worker threads.
|
||||||
|
std::vector<bufferevent*> workers_;
|
||||||
Sessions *sessions_;
|
Sessions *sessions_;
|
||||||
int64_t *session_id_seed_ptr_;
|
const Config *config_;
|
||||||
|
// In multi threading mode, this points to the next thread that
|
||||||
|
// client will be dispatched.
|
||||||
|
size_t next_worker_;
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpServer::HttpServer(const Config *config)
|
HttpServer::HttpServer(const Config *config)
|
||||||
|
@ -1051,13 +1141,12 @@ void evlistener_errorcb(evconnlistener *listener, void *ptr)
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int start_listen(event_base *evbase, Sessions *sessions,
|
int start_listen(event_base *evbase, Sessions *sessions, const Config *config)
|
||||||
int64_t *session_id_seed_ptr)
|
|
||||||
{
|
{
|
||||||
addrinfo hints;
|
addrinfo hints;
|
||||||
int r;
|
int r;
|
||||||
char service[10];
|
char service[10];
|
||||||
snprintf(service, sizeof(service), "%u", sessions->get_config()->port);
|
snprintf(service, sizeof(service), "%u", config->port);
|
||||||
memset(&hints, 0, sizeof(addrinfo));
|
memset(&hints, 0, sizeof(addrinfo));
|
||||||
hints.ai_family = AF_UNSPEC;
|
hints.ai_family = AF_UNSPEC;
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
@ -1066,6 +1155,8 @@ int start_listen(event_base *evbase, Sessions *sessions,
|
||||||
hints.ai_flags |= AI_ADDRCONFIG;
|
hints.ai_flags |= AI_ADDRCONFIG;
|
||||||
#endif // AI_ADDRCONFIG
|
#endif // AI_ADDRCONFIG
|
||||||
|
|
||||||
|
auto listen_handler = new ListenEventHandler(sessions, config);
|
||||||
|
|
||||||
addrinfo *res, *rp;
|
addrinfo *res, *rp;
|
||||||
r = getaddrinfo(nullptr, service, &hints, &res);
|
r = getaddrinfo(nullptr, service, &hints, &res);
|
||||||
if(r != 0) {
|
if(r != 0) {
|
||||||
|
@ -1095,17 +1186,14 @@ int start_listen(event_base *evbase, Sessions *sessions,
|
||||||
#endif // IPV6_V6ONLY
|
#endif // IPV6_V6ONLY
|
||||||
if(bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
if(bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
||||||
auto evlistener = evconnlistener_new
|
auto evlistener = evconnlistener_new
|
||||||
(evbase,
|
(evbase, evlistener_acceptcb, listen_handler,
|
||||||
evlistener_acceptcb,
|
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, fd);
|
||||||
new ListenEventHandler(sessions, session_id_seed_ptr),
|
|
||||||
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
|
|
||||||
-1, fd);
|
|
||||||
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
||||||
|
|
||||||
if(sessions->get_config()->verbose) {
|
if(config->verbose) {
|
||||||
std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6")
|
std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6")
|
||||||
<< ": listen on port "
|
<< ": listen on port "
|
||||||
<< sessions->get_config()->port << std::endl;
|
<< config->port << std::endl;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1226,13 +1314,11 @@ int HttpServer::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evbase = event_base_new();
|
auto evbase = event_base_new();
|
||||||
int64_t session_id_seed = 0;
|
|
||||||
Sessions sessions(evbase, config_, ssl_ctx);
|
Sessions sessions(evbase, config_, ssl_ctx);
|
||||||
if(start_listen(evbase, &sessions, &session_id_seed) != 0) {
|
if(start_listen(evbase, &sessions, config_) != 0) {
|
||||||
std::cerr << "Could not listen" << std::endl;
|
std::cerr << "Could not listen" << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_base_loop(evbase, 0);
|
event_base_loop(evbase, 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ struct Config {
|
||||||
void *data_ptr;
|
void *data_ptr;
|
||||||
size_t output_upper_thres;
|
size_t output_upper_thres;
|
||||||
size_t padding;
|
size_t padding;
|
||||||
|
size_t num_worker;
|
||||||
ssize_t header_table_size;
|
ssize_t header_table_size;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
|
@ -66,8 +67,6 @@ struct Config {
|
||||||
Config();
|
Config();
|
||||||
};
|
};
|
||||||
|
|
||||||
class Sessions;
|
|
||||||
|
|
||||||
struct Request {
|
struct Request {
|
||||||
Headers headers;
|
Headers headers;
|
||||||
std::pair<std::string, size_t> response_body;
|
std::pair<std::string, size_t> response_body;
|
||||||
|
|
|
@ -67,6 +67,7 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
|
||||||
${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES}
|
${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES}
|
||||||
|
|
||||||
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
||||||
|
ssl.cc ssl.h \
|
||||||
HttpServer.cc HttpServer.h
|
HttpServer.cc HttpServer.h
|
||||||
|
|
||||||
h2load_SOURCES = util.cc util.h http2.cc http2.h h2load.cc h2load.h \
|
h2load_SOURCES = util.cc util.h http2.cc http2.h h2load.cc h2load.h \
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "app_helper.h"
|
#include "app_helper.h"
|
||||||
#include "HttpServer.h"
|
#include "HttpServer.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "ssl.h"
|
||||||
|
|
||||||
namespace nghttp2 {
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
@ -130,6 +131,9 @@ void print_help(std::ostream& out)
|
||||||
<< " root. See --htdocs option.\n"
|
<< " root. See --htdocs option.\n"
|
||||||
<< " -b, --padding=<N> Add at most <N> bytes to a frame payload as\n"
|
<< " -b, --padding=<N> Add at most <N> bytes to a frame payload as\n"
|
||||||
<< " padding. Specify 0 to disable padding.\n"
|
<< " padding. Specify 0 to disable padding.\n"
|
||||||
|
<< " -n, --workers=<CORE>\n"
|
||||||
|
<< " Set the number of worker threads.\n"
|
||||||
|
<< " Default: 1\n"
|
||||||
<< " --version Display version information and exit.\n"
|
<< " --version Display version information and exit.\n"
|
||||||
<< " -h, --help Display this help and exit.\n"
|
<< " -h, --help Display this help and exit.\n"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
@ -151,13 +155,15 @@ int main(int argc, char **argv)
|
||||||
{"header-table-size", required_argument, nullptr, 'c'},
|
{"header-table-size", required_argument, nullptr, 'c'},
|
||||||
{"push", required_argument, nullptr, 'p'},
|
{"push", required_argument, nullptr, 'p'},
|
||||||
{"padding", required_argument, nullptr, 'b'},
|
{"padding", required_argument, nullptr, 'b'},
|
||||||
|
{"workers", required_argument, nullptr, 'n'},
|
||||||
{"no-tls", no_argument, &flag, 1},
|
{"no-tls", no_argument, &flag, 1},
|
||||||
{"color", no_argument, &flag, 2},
|
{"color", no_argument, &flag, 2},
|
||||||
{"version", no_argument, &flag, 3},
|
{"version", no_argument, &flag, 3},
|
||||||
{nullptr, 0, nullptr, 0}
|
{nullptr, 0, nullptr, 0}
|
||||||
};
|
};
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
int c = getopt_long(argc, argv, "DVb:c:d:hp:v", long_options, &option_index);
|
int c = getopt_long(argc, argv, "DVb:c:d:hn:p:v", long_options,
|
||||||
|
&option_index);
|
||||||
char *end;
|
char *end;
|
||||||
if(c == -1) {
|
if(c == -1) {
|
||||||
break;
|
break;
|
||||||
|
@ -175,6 +181,14 @@ int main(int argc, char **argv)
|
||||||
case 'd':
|
case 'd':
|
||||||
config.htdocs = optarg;
|
config.htdocs = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'n':
|
||||||
|
errno = 0;
|
||||||
|
config.num_worker = strtoul(optarg, &end, 10);
|
||||||
|
if(errno == ERANGE || *end != '\0' || config.num_worker == 0) {
|
||||||
|
std::cerr << "-n: Bad option value: " << optarg << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
print_help(std::cout);
|
print_help(std::cout);
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
|
@ -254,6 +268,8 @@ int main(int argc, char **argv)
|
||||||
OpenSSL_add_all_algorithms();
|
OpenSSL_add_all_algorithms();
|
||||||
SSL_load_error_strings();
|
SSL_load_error_strings();
|
||||||
SSL_library_init();
|
SSL_library_init();
|
||||||
|
ssl::LibsslGlobalLock();
|
||||||
|
|
||||||
reset_timer();
|
reset_timer();
|
||||||
|
|
||||||
HttpServer server(&config);
|
HttpServer server(&config);
|
||||||
|
|
Loading…
Reference in New Issue