首先声明,libevent的http模块是为单线程设计的,如果业务逻辑中有耗时操作,则需要自行设计线程池以便提高吞吐量,每个工作线程中都要运行一个event_base_loop和一个evhttp实例(这些evhttp实例需要用evhttp_bind_socket绑定到相同的端口上),具体参考官方issue。(PS: libevent版本为2.1.12)
https://github.com/libevent/libevent/issues/997
https://github.com/libevent/libevent/issues/1001
其次,evhttp 使用http1.1, 默认keep-alive是打开状态。连接成功的客户端连接被保存在evhttp的connections列表中,这些连接的事件由同一个event_base监管,并根据优先级串行处理,所以如果有耗时操作,将会严重拉低服务器性能。但是对于并发量不是很高的场景,单线程evhttp已经足够使用了。
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <string.h>
#ifndef _WIN32
#include <signal.h>
#endif
#include <iostream>
#include <string>
using namespace std;
#define WEBROOT "."
#define DEFAULTINDEX "index.html"
void offline_callback(struct evhttp_connection* evcon, void* arg)
{
struct evhttp_request* req = (struct evhttp_request*)arg;
if (req)
{
if (req->uri != NULL)
{
char* orignal_uri = evhttp_decode_uri(req->uri);
if (orignal_uri)
{
cout << "resquest uri:"<<orignal_uri<<endl;
free(orignal_uri);
}
}
}
cout << "client offline..." << endl;
}
/// <summary>
/// http请求的回调函数
/// 需要注意的方面:
/// 1. 每进来一个请求,在该请求被回复之前,对应的EV_READ事件会被禁用,回复之后会重新打开EV_READ事件。
/// 2. 每一个请求都要回复,即使请求有误也应该回复一个错误信息。以便及时打开EV_READ。
/// 3. 如果EV_READ没打开,则心跳包也会失效,无法及时关掉失效的连接,造成内存泄露。
/// 4. libevent http这种机制,在http pipeline模式下,会让底层TCP协议栈的发送方因为接收方读取过慢而阻塞。
/// </summary>
/// <param name="request"></param>
/// <param name="arg"></param>
void http_cb(struct evhttp_request* request, void* arg)
{
//1 获取浏览器的请求信息
const char* uri = evhttp_request_get_uri(request);
cout << "uri:" << uri << endl;
string cmdtype;
switch (evhttp_request_get_command(request))
{
case EVHTTP_REQ_GET:
cmdtype = "GET";
break;
case EVHTTP_REQ_POST:
cmdtype = "POST";
break;
}
cout << "cmdtype:" << cmdtype << endl;
// 消息报头
evkeyvalq* headers = evhttp_request_get_input_headers(request);
cout << "====== headers ======" << endl;
for (evkeyval* p = headers->tqh_first; p != NULL; p = p->next.tqe_next)
cout << p->key << ":" << p->value << endl;
// 读取客户端发来的请求正文 (GET为空,POST有表单信息 )
evbuffer* inbuf = evhttp_request_get_input_buffer(request);
char buf[1024] = { 0 };
cout << "======= Input data ======" << endl;
while (evbuffer_get_length(inbuf))
{
int n = evbuffer_remove(inbuf, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = '\0';
cout << buf << endl;
}
}
//2 回复浏览器
// 状态行 消息报头 响应正文
string filepath = WEBROOT;
filepath += uri;
if (strcmp(uri, "/") == 0)
{
//默认加入首页文件
filepath += DEFAULTINDEX;
}
//消息报头
//读取html文件返回正文
FILE* fp = fopen(filepath.c_str(), "rb");
if (!fp)
{
evhttp_send_reply(request, HTTP_NOTFOUND, "", 0);
return;
}
evbuffer* outbuf = evhttp_request_get_output_buffer(request);
for (;;)
{
int len = fread(buf, 1, sizeof(buf), fp);
if (len <= 0)break;
evbuffer_add(outbuf, buf, len);
}
fclose(fp);
evhttp_send_reply(request, HTTP_OK, "", outbuf);
//3. 连接相关参数更新
struct evhttp_connection* conn = evhttp_request_get_connection(request);
bufferevent* bev = evhttp_connection_get_bufferevent(conn);
int fd = bufferevent_getfd(bev);
struct event_base* base = evhttp_connection_get_base(conn);
//设置(更新)连接的存活时间(超时会发送心跳包探测) 默认时间为50
struct timeval timeout = {10,0};
bufferevent_set_timeouts(bev, &timeout,NULL);
// 检测到客户端关闭或者网络断开时,会触发evhttp_error_cb函数
// evhttp_error_cb内部会执行evhttp_connection_free,进而调用offline_callback
evhttp_connection_set_closecb(conn, offline_callback, request);
}
int main()
{
#ifdef _WIN32
//初始化socket库
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#else
//忽略SIGPIPE信号
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
#endif
std::cout << "test server!\n";
//创建libevent的上下文
event_base* base = event_base_new();
if (base)
cout << "event_base_new success!" << endl;
// http 服务器
//1 创建evhttp上下文
evhttp* http = evhttp_new(base);
//2 绑定端口和IP
struct evhttp_bound_socket* handle;
handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", 9799);
if (!handle) {
cout << "evhttp_bind_socket failed!" << endl;
}
//3 设定回调函数
evhttp_set_gencb(http, http_cb, 0);
//evhttp_set_bevcb();
//evhttp_set_cb();
if (base)
event_base_dispatch(base);
if (base)
event_base_free(base);
if (http)
evhttp_free(http);
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}