libevent 是一个事件触发的网络库,适用于 windows、linux、bsd 、Android 等多种平台,内部使用 select、epoll、kqueue 、完成端口等系统调用管理事件机制。著名分布式缓存软件 memcached 也是 libevent based 。
最近在学习 libevent ,之前基于 libevent 实现了一个 http client ,没有用到 bufferevent 。这次实现了一个 http server ,很简单,只支持 GET 方法,不支持 Range 请求,但完全自己实现,是一个完整可用的示例。这里使用 libevent-2.1.3-alpha 。
我关于 libevent 的其它文章,列在这里供参考:
使用 libevent 实现一个 http server ,有这么几个步骤:
- 监听
- 启动事件循环
- 接受连接
- 解析 http 请求
- 回应客户端
关于监听, libevent 提供了 evconnlistener ,使用起来非常简单,通过一些设置,调用 evconnlistener_new_bind 即可完成一个服务端 socket 的创建,可以参考官方文档Connection Listeners 。下面是启动 server 的代码:
int start_http_server(struct event_base *evbase)
{
int bind_times = 0;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
#ifdef WIN32
sin.sin_addr.S_un.S_addr = inet_addr(g_host);
#else
sin.sin_addr.s_addr = inet_addr(g_host);
#endif
sin.sin_port = htons(g_port);
trybind:
g_listener = evconnlistener_new_bind(
evbase, _accept_connection, 0,
LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_DEFERRED_ACCEPT, -1,
(struct sockaddr*)&sin, sizeof(sin));
if (!g_listener)
{
if(bind_times++ >=3)
{
printf("couldn\'t create listener\n");
return 1;
}
else
{
sin.sin_port = 0;
goto trybind;
}
}
else if(bind_times > 0)
{
socklen_t len = sizeof(sin);
getsockname(evconnlistener_get_fd(g_listener),
(struct sockaddr*)&sin, &len);
g_port = ntohs(sin.sin_port);
}
evconnlistener_set_error_cb(g_listener, _accept_error_cb);
return 0;
}
关于事件循环,event_base_new 可以创建一个 event_base 实例, event_base_loop 可以进入事件循环。下面是 main() 函数中关于事件循环的代码:
g_evbase = event_base_new();
if( 0 == start_http_server(g_evbase) )
{
event_base_loop(g_evbase, EVLOOP_NO_EXIT_ON_EMPTY);
printf("httpserver exit now.\n");
}
else
{
printf("httpserver, start server failed\n");
}
event_base_free(g_evbase);
上面的代码中,启动事件循环时传递了一个标志 EVLOOP_NO_EXIT_ON_EMPTY ,对于服务器程序,这是必须的,否则在没有待处理事件时,事件循环会立即退出。
通过给 evconnlistener 设置一些回调,就可以接受连接、处理错误。下面是相关代码:
static void _accept_connection(struct evconnlistener *listener,
evutil_socket_t fd, struct sockaddr *addr
, int socklen, void * ctx)
{
char address[64];
struct http_connection *conn;
struct sockaddr_in sin;
short port = 0;
/* get address and port*/
memcpy(&sin, addr, sizeof(sin));
sprintf(address, "%s", inet_ntoa(sin.sin_addr));
port = ntohs(sin.sin_port);
#ifdef HTTP_SERVER_DEBUG
printf("httpserver, accept one connection from %s:%d\n", address, port);
#endif
conn = new_http_connection(evconnlistener_get_base(listener),
fd,
addres