在libevent(六)http server中,作为一个单线程http server,不仅要监听每个连接的到来,还要监听每个连接上的I/O事件。
查看源码可知,在evhttp_bind_socket中设置了accept的回调函数:accept_socket_cb。
/* Listener callback when a connection arrives at a server. */ static void accept_socket_cb(struct evconnlistener *listener, evutil_socket_t nfd, struct sockaddr *peer_sa, int peer_socklen, void *arg) { struct evhttp *http = arg; evhttp_get_request(http, nfd, peer_sa, peer_socklen); }
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); if (evcon == NULL) { event_sock_warn(fd, "%s: cannot get connection on "EV_SOCK_FMT, __func__, EV_SOCK_ARG(fd)); evutil_closesocket(fd); return; } /* the timeout can be used by the server to close idle connections */ if (http->timeout != -1) evhttp_connection_set_timeout(evcon, http->timeout); /* * if we want to accept more than one request on a connection, * we need to know which http server it belongs to. */ evcon->http_server = http; TAILQ_INSERT_TAIL(&http->connections, evcon, next); if (evhttp_associate_new_request_with_connection(evcon) == -1) evhttp_connection_free(evcon); }
两个重要函数: evhttp_get_request_connection、evhttp_associate_new_request_with_connection。
1. evhttp_get_request_connection
/* * Takes a file descriptor to read a request from. * The callback is executed once the whole request has been read. */ static struct evhttp_connection* evhttp_get_request_connection( struct evhttp* http, evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen) { struct evhttp_connection *evcon; char *hostname = NULL, *portname = NULL; name_from_addr(sa, salen, &hostname, &portname); if (hostname == NULL || portname == NULL) { if (hostname) mm_free(hostname); if (portname) mm_free(portname); return (NULL); } event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT" ", __func__, hostname, portname, EV_SOCK_ARG(fd))); /* we need a connection object to put the http request on */ evcon = evhttp_connection_base_new( http->base, NULL, hostname, atoi(portname)); mm_free(hostname); mm_free(portname); if (evcon == NULL) return (NULL); evcon->max_headers_size = http->default_max_headers_size; evcon->max_body_size = http->default_max_body_size; evcon->flags |= EVHTTP_CON_INCOMING; evcon->state = EVCON_READING_FIRSTLINE; evcon->fd = fd; bufferevent_setfd(evcon->bufev, fd); return (evcon); }
注意点:
1. 通过evhttp_connection_base_new设置了bufferevent的readcd: evhttp_read_cb,
writecb: evhttp_write_cb。
2. 调用bufferevent_setfd
bufferevent_setfd代码如下:
int bufferevent_setfd(struct bufferevent *bev, evutil_socket_t fd) { union bufferevent_ctrl_data d; int res = -1; d.fd = fd; BEV_LOCK(bev); if (bev->be_ops->ctrl) res = bev->be_ops->ctrl(bev, BEV_CTRL_SET_FD, &d); BEV_UNLOCK(bev); return res; } static int be_socket_ctrl(struct bufferevent *bev, enum bufferevent_ctrl_op op, union bufferevent_ctrl_data *data) { switch (op) { case BEV_CTRL_SET_FD: be_socket_setfd(bev, data->fd); return 0; case BEV_CTRL_GET_FD: data->fd = event_get_fd(&bev->ev_read); return 0; case BEV_CTRL_GET_UNDERLYING: case BEV_CTRL_CANCEL_ALL: default: return -1; } } static void be_socket_setfd(struct bufferevent *bufev, evutil_socket_t fd) { BEV_LOCK(bufev); EVUTIL_ASSERT(bufev->be_ops == &bufferevent_ops_socket); event_del(&bufev->ev_read); event_del(&bufev->ev_write); event_assign(&bufev->ev_read, bufev->ev_base, fd, EV_READ|EV_PERSIST, bufferevent_readcb, bufev); event_assign(&bufev->ev_write, bufev->ev_base, fd, EV_WRITE|EV_PERSIST, bufferevent_writecb, bufev); if (fd >= 0) bufferevent_enable(bufev, bufev->enabled); BEV_UNLOCK(bufev); }
这里主要设置fd的读事件回调bufferevent_readcb,写事件回调bufferevent_writecb。
(这里的bufferevent_enable可以不用在意,后面会重置。)
2. evhttp_associate_new_request_with_connection
static int evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon) { struct evhttp *http = evcon->http_server; struct evhttp_request *req; if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) return (-1); if ((req->remote_host = mm_strdup(evcon->address)) == NULL) { event_warn("%s: strdup", __func__); evhttp_request_free(req); return (-1); } req->remote_port = evcon->port; req->evcon = evcon; /* the request ends up owning the connection */ req->flags |= EVHTTP_REQ_OWN_CONNECTION; /* We did not present the request to the user user yet, so treat it as * if the user was done with the request. This allows us to free the * request on a persistent connection if the client drops it without * sending a request. */ req->userdone = 1; TAILQ_INSERT_TAIL(&evcon->requests, req, next); req->kind = EVHTTP_REQUEST; evhttp_start_read(evcon); return (0); }
第一步设置evhttp_request的回调函数evhttp_handle_request,第二步调用evhttp_start_read:
/* * Reads data from file descriptor into request structure * Request structure needs to be set up correctly. */ void evhttp_start_read(struct evhttp_connection *evcon) { /* Set up an event to read the headers */ bufferevent_disable(evcon->bufev, EV_WRITE); bufferevent_enable(evcon->bufev, EV_READ); evcon->state = EVCON_READING_FIRSTLINE; /* Reset the bufferevent callbacks */ bufferevent_setcb(evcon->bufev, evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon); /* If there's still data pending, process it next time through the * loop. Don't do it now; that could get recusive. */ if (evbuffer_get_length(bufferevent_get_input(evcon->bufev))) { event_deferred_cb_schedule(get_deferred_queue(evcon), &evcon->read_more_deferred_cb); } }
可以看到,这里将fd的读事件添加到了事件循环中。
最后梳理下读事件调用流程:
1. fd上有读事件发生
2. bufferevent_readcb
3. evhttp_read_cb
4. evhttp_connection_done
5. evhttp_handle_request
6. 调用用户定义的evhttp回调函数
关于数据的流向
当fd上有读事件发生时,首先将fd上的数据读到evhttp_connection的bufferevent中,然后将bufferevent中的数据读到evhttp_request的输入缓冲中。
当我们使用evhttp_send_reply发送数据时,首先将数据写入evhttp_request的输出缓冲中,然后写入evhttp_connection的bufferevent中,最后写入到fd的输出缓冲。