libevent HTTP server源码详解 以及TCP server 编写

        最近在项目中设备需要作为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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值