最近在项目中设备需要作为http服务端,这里采用了libevent,来分析一下其源码,先提供示例程序
int main(int argc, char **argv)
{
struct evhttp *http_server = NULL;
short http_port = 8081;
char *http_addr = "0.0.0.0";
event_init(); //事件初始化
http_server = evhttp_start(http_addr,http_port); //启动http服务器
if(http_server == NULL)
{
printf("====line:%d,%s\n",__LINE__,"http server start failed.");
return -1;
}
evhttp_set_timeout(http_server,30);
evhttp_set_cb(http_server,"/me/testpost",http_handler_testpost_msg,NULL);
event_dispatch();
evhttp_free(http_server);
return 0;
}
在源码分析之前,先来了解几个主要的结构体
//这里只列举了一些重要的结构体成员,之后所有的结构体也是这样
struct event_base {
...
const struct eventop *evsel; //添加事件,删除事件,轮询等方法
void *evbase;
int event_count; //事件个数
int event_count_max; //事件最大个数
int event_count_active; //活跃事件个数
int event_count_active_max; //活跃事件最大个数
struct evcallback_list *activequeues; //活跃事件队列,当通过dispatch发现有事件,就会加入到该队列。
...
int nactivequeues; //活跃队列个数
...
struct event_io_map io; //用于存储每一个事件(根据文件描述符),包括一些读写错误等回调函数
...
struct min_heap timeheap; //用于记录每个事件的超时时间,当达到超时时间,该事件被标记为超时事件
...
};
该结构体记录了与事件相关的信息。
struct evhttp {
...
TAILQ_HEAD(boundq, evhttp_bound_socket) sockets; //所有监听的socket的队列头
TAILQ_HEAD(httpcbq, evhttp_cb) callbacks; //struct evhttp_cb的队列头,struct evhttp_cb存放这uri以及回调函数,也就是通过uri来找到对应的回调函数,然后执行http对应的处理。
struct evconq connections; //所有存在的连接
struct timeval timeout; //超时时间,如果超过timeout的时间还没有收完成数据,那么就会超时。
size_t default_max_headers_size; // 默认最大HTTP头长度
ev_uint64_t default_max_body_size; //默认最大HTTP body长度
const char *default_content_type; //默认的content_type
ev_uint16_t allowed_methods; //支持的方法POST GET 等等
void (*gencb)(struct evhttp_request *req, void *); //HTTP通常处理方法,收到一个HTTP包,在没有找到对应的evhttp_cb时,会调用该函数
void *gencbarg; // 对应的参数
struct event_base *base; //对应的event_base
...
};
/* A client or server connection. */
struct evhttp_connection {
/* we use this tailq only if this connection was created for an http
* server */
TAILQ_ENTRY(evhttp_connection) next; //将会被链入道evhttp->connections
evutil_socket_t fd; //当前socket的文件描述符
struct bufferevent *bufev; //对应的buffer信息,真正的读写存储都在这里
char *bind_address; // 服务器地址
ev_uint16_t bind_port; //服务器端口
char *address; //客户端地址
ev_uint16_t port; //客户端端口
size_t max_headers_size; //最大HTTP头长度
ev_uint64_t max_body_size; //最大HTTP body长度
int flags; //一些标志
struct timeval timeout; //超时时间
enum evhttp_connection_state state; //状态机对应的状态,也就是解析HTTP报文的核心部分
struct evhttp *http_server;
TAILQ_HEAD(evcon_requestq, evhttp_request) requests; //http请求的队列头
struct event_base *base;
int ai_family;
};
这个时http 连接状态的信息,也就是说每有一个客户端连接成功,就会创建这么一个evhttp connection结构体。
struct evhttp_request {
#if defined(TAILQ_ENTRY)
TAILQ_ENTRY(evhttp_request) next; //链入到evhttp_connection的request队列中
#else
struct {
struct evhttp_request *tqe_next;
struct evhttp_request **tqe_prev;
} next;
#endif
struct evhttp_connection *evcon;
int flags; //一些标志
struct evkeyvalq *input_headers; //输入的头部信息
struct evkeyvalq *output_headers; //输出的头部信息
char *remote_host; //对端的ip或者域名
ev_uint16_t remote_port; //对端的端口
enum evhttp_request_kind kind; //是请求还是相应
enum evhttp_cmd_type type; //POST GET 等方法
size_t headers_size; // 头部大小
size_t body_size; // body大小
char *uri; // uri
struct evhttp_uri *uri_elems;
char major; //HTTP主版本 1
char minor; //HTTP此版本 如果是HTTP1.0 那么 0
int response_code; //响应结果 如 200 404等
char *response_code_line;
struct evbuffer *input_buffer; //输入的buffer,这里存储着收到的所有数据
ev_int64_t ntoread;
unsigned chunked:1,
userdone:1;
struct evbuffer *output_buffer; //输出buffer,也就是响应
void (*cb)(struct evhttp_request *, void *); //这是在HTTP报文解析完成后的回调函数
void *cb_arg; //回调函数对应的参数
void (*chunk_cb)(struct evhttp_request *, void *);
int (*header_cb)(struct evhttp_request *, void *);
void (*error_cb)(enum evhttp_request_error, void *); // HTTP错误后回调函数。
void (*on_complete_cb)(struct evhttp_request *, void *); //在HTTP响应完成后,调用该回调函数
void *on_complete_cb_arg;
};
每有一个新的HTTP请求,就会有一个request,
根据示例来分析
struct event_base *
event_init(void)
{
struct event_base *base = event_base_new_with_config(NULL);
if (base == NULL) {
event_errx(1, "%s: Unable to construct event_base", __func__);
return NULL;
}
current_base = base;
return (base);
}
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
...
for (i = 0; eventops[i] && !base->evbase; i++) {
if (cfg != NULL) {
/* determine if this backend should be avoided */
if (event_config_is_avoided_method(cfg,
eventops[i]->name))
continue;
if ((eventops[i]->features & cfg->require_features)
!= cfg->require_features)
continue;
}
/* also obey the environment variables */
if (should_check_environment &&
event_is_method_disabled(eventops[i]->name))
continue;
base->evsel = eventops[i]; //实际上是使用epollops
base->evbase = base->evsel->init(base);
}
...
}
主要就是对于event base的初始化,这里面最重要的一点就是选择哪一种事件驱动,libevent默认使用的epoll,并且使用的LT模式
接下来就要启动http server,我们先设想一下这里面的步骤,里面一定会调用到系统调用socket bind 和listen,然后还会调用epoll_ctl来添加事件
struct evhttp *
evhttp_start(const char *address, ev_uint16_t port)
{
struct evhttp *http = NULL;
http = evhttp_new_object(); //evhttp初始化,这里东西都比较简单,无需展开细讲
if (http == NULL)
return (NULL);
if (evhttp_bind_socket(http, address, port) == -1) {
mm_free(http);
return (NULL);
}
return (http);
}
evhttp_bind_socket --->evhttp_bind_socket_with_handle
struct evhttp_bound_socket *
evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port)
{
evutil_socket_t fd;
struct evhttp_bound_socket *bound;
int serrno;
if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1) //创建socket 以及bind端口
return (NULL);
if (listen(fd, 128) == -1) { //监听端口
serrno = EVUTIL_SOCKET_ERROR();
event_sock_warn(fd, "%s: listen", __func__);
evutil_closesocket(fd);
EVUTIL_SET_SOCKET_ERROR(serrno);
return (NULL);
}
bound = evhttp_accept_socket_with_handle(http, fd); //这里主要是绑定事件以及设置回调函数
if (bound != NULL) {
event_debug(("Bound to port %d - Awaiting connections ... ",
port));
return (bound);
}
return (NULL);
}
//bind_socket这里就不细讲了,就是普通的bind一个端口,主要来看看绑定事件
struct evhttp_bound_socket *
evhttp_accept_socket_with_handle(struct evhttp *http, evutil_socket_t fd)
{
struct evhttp_bound_socket *bound;
struct evconnlistener *listener;
const int flags =
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_CLOSE_ON_FREE;
listener = evconnlistener_new(http->base, NULL, NULL, //绑定事件
flags,
0, /* Backlog is '0' because we already said 'listen' */
fd);
if (!listener)
return (NULL);
bound = evhttp_bind_listener(http, listener); //绑定socket,并且设置
//evconnlistener_cb
if (!bound) {
evconnlistener_free(listener);
return (NULL);
}
return (bound);
}
struct evconnlistener *
evconnlistener_new(struct event_base *base,evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,evutil_socket_t fd)
{
...
lev = mm_calloc(1, sizeof(struct evconnlistener_event));
if (!lev)
return NULL;
lev->base.ops = &evconnlistener_event_ops;
lev->base.cb = cb;
lev->base.user_data = ptr;
lev->base.flags = flags;
lev->base.refcnt = 1;
event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,listener_read_cb, lev);//在这里会设置事件的回调函数
if (!(flags & LEV_OPT_DISABLED))
evconnlistener_enable(&lev->base); //绑定事件
}
static int
event_listener_enable(struct evconnlistener *lev)
{
struct evconnlistener_event *lev_e =
EVUTIL_UPCAST(lev, struct evconnlistener_event, base);
return event_add(&lev_e->listener, NULL);
}
绑定事件的流程
event_add ----> evmap_io_add_ ----> event_add_nolock_ ----> evmap_io_add_ ---->evsel->add(...) ---->epoll_apply_one_change--->epoll_ctl
这里有一点,我们刚刚提到了在event_assign里会设置回调函数,这个回调函数其实就是在evmap_io中根据fd找到对应的链表,加入到该链表中,链表中可能会存在好几个事件(因为一个fd对应的事件可以分为读,写,关闭),如果这个文件描述符的读写关闭事件都被绑定,那么按理说链表上就有三个元素,也就会有三个回调函数,根据事件的类型选出对应的回调函数。
这样服务器启动的流程就完成了,接下来就要进行事件的调度。
int event_dispatch(void)
{
return (event_loop(0));
}
int event_base_loop(struct event_base *base, int flags)
{
...
tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p); //找最近的超时时间
} else {
evutil_timerclear(&tv);
}
...
res = evsel->dispatch(base, tv_p); //这里会调用epoll_dispatch,里面会调用epoll_wait,超时时间根据参数tv_p,如果有事件发生,就会evmap_io_active_(base, events[i].data.fd, ev | EV_ET); 也就是说会把原本在evmap_io中对应的事件,链接到base->activequeues[evcb->evcb_pri] 链表中。
timeout_process(base); //判断是否有事件超时,如果有也加入到活跃事件链表中,只不过设置标志位为EV_TIMEOUT;
if (N_ACTIVE_CALLBACKS(base)) {
int n = event_process_active(base); //如果有事件激活
if ((flags & EVLOOP_ONCE)&& N_ACTIVE_CALLBACKS(base) == 0&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
...
}
event_process_active ---->event_process_active_single_queue ----->event_persist_closure
static inline void event_persist_closure(struct event_base *base, struct event *ev)
{
...
if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec) {
...
event_add_nolock_(ev, &run_at, 1); //更新超时时间
}
evcb_callback = ev->ev_callback;
evcb_fd = ev->ev_fd;
evcb_res = ev->ev_res;
evcb_arg = ev->ev_arg;
(evcb_callback)(evcb_fd, evcb_res, evcb_arg); //事件的回调函数
}
这里就到了事件的回调函数,在我们启动服务器的时候,就event_assign了bind事件,里面就有回调函数listener_read_cb,这里来分析一下该函数
首先执行到该函数说明一定有客户端进行连接了
static void listener_read_cb(evutil_socket_t fd, short what, void *p)
{
...
evutil_socket_t new_fd = evutil_accept4_(fd, (struct sockaddr*)&ss, &socklen, lev->accept4_flags); //首先进行accept
...
cb = lev->cb;
...
cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,user_data); //前面在启动服务器的时候,会对lev->cb进行赋值,实际上accept_socket_cb
...
}
accept_socket_cb---->evhttp_get_request
static void
evhttp_get_request(struct evhttp *http, evutil_socket_t fd,
struct sockaddr *sa, ev_socklen_t salen)
{
struct evhttp_connection *evcon;
evcon = evhttp_get_request_connection(http, fd, sa, salen); //申请一个连接,并申请一个bufferevent,这个buffevent负责事件,以及读写。
...
if (evutil_timerisset(&http->timeout))
evhttp_connection_set_timeout_tv(evcon, &http->timeout); //设置超时时间
evcon->http_server = http;
TAILQ_INSERT_TAIL(&http->connections, evcon, next);
if (evhttp_associate_new_request_with_connection(evcon) == -1) //申请一个新的请求
evhttp_connection_free(evcon);
}
event_assign(&bufev->ev_read, bufev->ev_base, fd,
EV_READ|EV_PERSIST|EV_FINALIZE, bufferevent_readcb, bufev);
event_assign(&bufev->ev_write, bufev->ev_base, fd,
EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bufev);
bufferevent_setcb(bev, evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon);
//提供readcb,writecb,errorcb回调函数
对于新的fd,提供两种事件的关联,一个是读一个是写
if (bufferevent_enable(evcon->bufev, EV_READ))
goto err;
if (bufferevent_disable(evcon->bufev, EV_WRITE))
goto err;
//暂时只打开读,关闭写。
也就是说当该fd被激活后,如果是读事件,那么就会调用bufferevent_readcb,如果是写事件,那么就会调用bufferevent_writecb(前提是bufferevent_enable)。
static void bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
{
input = bufev->input;
...
res = evbuffer_read(input, fd, (int)howmuch); //读取报文
...
bufferevent_trigger_nolock_(bufev, EV_READ, 0); --->bufev->readcb(bufev, bufev->cbarg); --->evhttp_read_cb
}
static void evhttp_read_cb(struct bufferevent *bufev, void *arg)
{
switch (evcon->state) {
case EVCON_READING_FIRSTLINE:
evhttp_read_firstline(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
break;
case EVCON_READING_HEADERS:
evhttp_read_header(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
break;
case EVCON_READING_BODY:
evhttp_read_body(evcon, req);
/* note the request may have been freed in
* evhttp_read_body */
break;
case EVCON_READING_TRAILER:
evhttp_read_trailer(evcon, req);
break;
...
}
//这个函数就是一个状态机,用来解析HTTP报文,这里就不进行详解了,在完成后,会调用
(*req->cb)(req, req->cb_arg); ---> evhttp_handle_request --->
if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) {
(*cb->cb)(req, cb->cbarg);
return;
}
这里会根据uri找到对应的http回调函数,调用它,也就是示例中的
evhttp_set_cb(http_server,"/me/testpost",http_handler_testpost_msg,NULL);
然后就是写事件
static void
evhttp_write_cb(struct bufferevent *bufev, void *arg)
{
struct evhttp_connection *evcon = arg;
/* Activate our call back */
if (evcon->cb != NULL)
(*evcon->cb)(evcon, evcon->cb_arg); //(evhttp_send_done)
}
static void
evhttp_send_done(struct evhttp_connection *evcon, void *arg)
{
int need_close;
struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);
TAILQ_REMOVE(&evcon->requests, req, next);
if (req->on_complete_cb != NULL) { //如果存在完成回调函数,调用
req->on_complete_cb(req, req->on_complete_cb_arg);
}
need_close =
(REQ_VERSION_BEFORE(req, 1, 1) && // 是不是keepalive
!evhttp_is_connection_keepalive(req->input_headers)) ||
evhttp_is_request_connection_close(req);
EVUTIL_ASSERT(req->flags & EVHTTP_REQ_OWN_CONNECTION);
evhttp_request_free(req); //释放掉旧的request
if (need_close) { //需要关闭连接的
evhttp_connection_free(evcon); //把连接也释放掉
return;
}
//不需要关闭连接的,那么就重新申请一个请求
if (evhttp_associate_new_request_with_connection(evcon) == -1) {
evhttp_connection_free(evcon);
}
}
这样libevent的http服务器基本就完成了,然后基于这个http服务器,改了一个TCP服务器,其实
很简单只需要把readcb,writecb,errorcb这几个函数重写就好了。
最后附上代码链接(没有经过大量测试,稳定性还需在测试)
https://gitee.com/yu975048635/opensource_yu/tree/master/libevent-2.1.11-tcp