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 client
  • libevent实现echoclient
  • libevent http client
  • libevent 在 Android 上的一个改进

使用 libevent 实现一个 http server ,有这么几个步骤:

  1. 监听
  2. 启动事件循环
  3. 接受连接
  4. 解析 http 请求
  5. 回应客户端

关于监听, 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 WIN32sin.sin_addr.S_un.S_addr = inet_addr(g_host);
#elsesin.sin_addr.s_addr = inet_addr(g_host);
#endifsin.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_DEBUGprintf("httpserver, accept one connection from %s:%d\n", address, port);
#endifconn = new_http_connection(evconnlistener_get_base(listener),fd,address,port);
}static void _accept_error_cb(struct evconnlistener *listener, void *ctx)
{int err = EVUTIL_SOCKET_ERROR();printf("httpserver, got an error %d (%s) on the listener.\n", err, evutil_socket_error_to_string(err));
}

在创建调用 evconnlistener_new_bind 时我们传入了 _accept_connection 函数,当有连接进来时,_accept_connection 调用 new_http_connection 函数来处理。错误处理回调 _accept_error_cb 是通过 evconnlistener_set_error_cb 设置的,在上面的错误处理回调函数中,我们仅仅是输出了一条日志。

解析 http 请求,这里还是使用 《使用http_parser解析URL》一文中提到的http_parser 。先看下 new_http_connection 函数的实现:

struct http_connection * new_http_connection(struct event_base *evbase,evutil_socket_t fd,char *address, int port)
{struct http_connection * conn = (struct http_connection*)malloc(sizeof(struct http_connection));conn->evbase = evbase;conn->fd = fd;conn->peer_address = strdup(address);conn->peer_port = port;conn->tv_timeout.tv_sec = 10;conn->tv_timeout.tv_usec = 0;conn->bev = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE);bufferevent_setcb(conn->bev, _read_callback, _write_callback, _event_callback, conn);bufferevent_enable(conn->bev, EV_READ|EV_TIMEOUT);bufferevent_set_timeouts(conn->bev, &conn->tv_timeout, &conn->tv_timeout);conn->parser_settings.on_message_begin = onHttpMessageBegin;conn->parser_settings.on_url = onHttpUrl;conn->parser_settings.on_header_field = onHttpHeaderField;conn->parser_settings.on_header_value = onHttpHeaderValue;conn->parser_settings.on_headers_complete = onHttpHeadersComplete;conn->parser_settings.on_body = onHttpBody;conn->parser_settings.on_message_complete = onHttpMessageComplete;conn->cur_header_tag = 0;conn->cur_tag_cap = 0;conn->cur_tag_size = 0;conn->cur_header_value = 0;conn->cur_value_cap = 0;conn->cur_value_size = 0;conn->header_tags = 0;conn->header_size = 0;conn->header_capacity = 0;conn->header_values = 0;conn->fp = 0;conn->data_size = 0;conn->remain = 0;conn->method = 0;conn->path = 0;conn->query_string = 0;conn->status_code = 0;conn->parser.data = conn;http_parser_init(&conn->parser, HTTP_REQUEST);return conn;
}

上面的代码根据传入的 socket 描述符和 event_base 完成了传入连接的配置工作。主要有几部分:

  • 创建 bufferevent
  • 设置读写回调
  • 配置 http_parser_setting ,主要是一些回调函数,http_parser 分析数据后酌情调用
  • 初始化 http_parser,调用 http_parser_init,注意传入类型是 HTTP_REQUEST

上面代码中的结构体 struct http_connection 保存了一个连接相关的所有数据,其定义如下:

struct http_connection {struct http_parser parser;struct http_parser_settings parser_settings;char *cur_header_tag;int cur_tag_cap;int cur_tag_size;char *cur_header_value;int cur_value_cap;int cur_value_size;char buffer[BUFFER_SIZE];struct event_base *evbase;evutil_socket_t fd;struct bufferevent *bev;struct timeval tv_timeout;char *peer_address;int peer_port;int state;unsigned write_enabled:1;unsigned user_stop:1;/* request info */const char * method;char * path;char * query_string;char version[4];char **header_tags;char **header_values;int header_capacity;int header_size;/* response info */FILE *fp;long remain;int data_size;int status_code;
};

需要说明的是,这里只是个示例, http 请求、响应、连接处理全部放在了一起,看起来比较方面。

关于 http 头部、数据解析, http_parser 会为我们做好一切,我们只要保存即可。

关于 http 响应,需要我们自己构建状态行、必要的头部信息(如 Content-Length )。

所有这些,请参考文后的代码。

回应客户端的这里使用 bufferevent socket 。 libevent 抽象了一种缓冲机制,来给大多数应用场景提供方便,关于 bufferevents ,请参考官方文档(http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html)。

使用 bufferevent socket 处理连接非常简单,只需要设置读写回调即可,这在上面已经提到,不再赘述。

到这里为止,关于一个 http server 的所有事情就说完了。下面是 http_connection.c 的所有代码( new_http_connection 函数的代码在前面),可以正常运行。为使代码比较清晰,这里关于错误、封装、解耦等都简化处理了。

#define HTTP_HEADER_BEGIN        0x1
#define HTTP_HEADER_COMPLETE     0x2
#define HTTP_MESSAGE_BEGIN       0x4
#define HTTP_MESSAGE_COMPLETE    0x8#define STATUS_CODE(code, str) case code: return str;static const char * _status_string(int code)
{switch(code){STATUS_CODE(100, "Continue")STATUS_CODE(101, "Switching Protocols")STATUS_CODE(102, "Processing")                 // RFC 2518) obsoleted by RFC 4918STATUS_CODE(200, "OK")STATUS_CODE(201, "Created")STATUS_CODE(202, "Accepted")STATUS_CODE(203, "Non-Authoritative Information")STATUS_CODE(204, "No Content")STATUS_CODE(205, "Reset Content")STATUS_CODE(206, "Partial Content")STATUS_CODE(207, "Multi-Status")               // RFC 4918STATUS_CODE(300, "Multiple Choices")STATUS_CODE(301, "Moved Permanently")STATUS_CODE(302, "Moved Temporarily")STATUS_CODE(303, "See Other")STATUS_CODE(304, "Not Modified")STATUS_CODE(305, "Use Proxy")STATUS_CODE(307, "Temporary Redirect")STATUS_CODE(400, "Bad Request")STATUS_CODE(401, "Unauthorized")STATUS_CODE(402, "Payment Required")STATUS_CODE(403, "Forbidden")STATUS_CODE(404, "Not Found")STATUS_CODE(405, "Method Not Allowed")STATUS_CODE(406, "Not Acceptable")STATUS_CODE(407, "Proxy Authentication Required")STATUS_CODE(408, "Request Time-out")STATUS_CODE(409, "Conflict")STATUS_CODE(410, "Gone")STATUS_CODE(411, "Length Required")STATUS_CODE(412, "Precondition Failed")STATUS_CODE(413, "Request Entity Too Large")STATUS_CODE(414, "Request-URI Too Large")STATUS_CODE(415, "Unsupported Media Type")STATUS_CODE(416, "Requested Range Not Satisfiable")STATUS_CODE(417, "Expectation Failed")STATUS_CODE(418, "I\"m a teapot")              // RFC 2324STATUS_CODE(422, "Unprocessable Entity")       // RFC 4918STATUS_CODE(423, "Locked")                     // RFC 4918STATUS_CODE(424, "Failed Dependency")          // RFC 4918STATUS_CODE(425, "Unordered Collection")       // RFC 4918STATUS_CODE(426, "Upgrade Required")           // RFC 2817STATUS_CODE(500, "Internal Server Error")STATUS_CODE(501, "Not Implemented")STATUS_CODE(502, "Bad Gateway")STATUS_CODE(503, "Service Unavailable")STATUS_CODE(504, "Gateway Time-out")STATUS_CODE(505, "HTTP Version not supported")STATUS_CODE(506, "Variant Also Negotiates")    // RFC 2295STATUS_CODE(507, "Insufficient Storage")       // RFC 4918STATUS_CODE(509, "Bandwidth Limit Exceeded")STATUS_CODE(510, "Not Extended")                // RFC 2774}return 0;
}static void _prepare_response(struct http_connection *conn);
static void _disable_write(struct http_connection *conn);
static void _enable_write(struct http_connection *conn);
static void _close_socket(struct http_connection * conn);
static void _peacefull_close(struct http_connection * conn);
static void _send_response_header(struct http_connection *conn);/*
* http_parser callback
*/
static int onHttpMessageBegin(http_parser *parser)
{struct http_connection * conn = (struct http_connection *)parser->data;conn->state |= HTTP_MESSAGE_BEGIN;return 0;
}static int onHttpUrl(http_parser *parser, const char *at, size_t length)
{struct http_connection *conn = (struct http_connection *)parser->data;int i= 0;conn->path = (char*)malloc(length+1);strncpy(conn->path, at, length);conn->path[length] = 0;for(; i < length && at[i] != '?'; i++);if(i < length){/* got query string */i++;if(i < length){int qlen = length - i;conn->query_string = (char*)malloc(qlen + 1);strncpy(conn->query_string, at+i, qlen);conn->query_string[qlen] = 0;}}return 0;
}static inline void check_insert_header(http_parser *parser, struct http_connection *conn)
{/** insert the header we parsed previously* into the header map*/if( (conn->cur_header_tag && conn->cur_header_tag[0] != 0) &&(conn->cur_header_value && conn->cur_header_value[0] != 0)){if(!conn->header_tags ||conn->header_size == conn->header_capacity){conn->header_capacity += 8;conn->header_tags = (char**)realloc(conn->header_tags,sizeof(char*)*conn->header_capacity);conn->header_values = (char**)realloc(conn->header_tags, sizeof(char*)*conn->header_capacity);}conn->header_tags[conn->header_size] = conn->cur_header_tag;conn->header_values[conn->header_size++] = conn->cur_header_value;/**  clear header value. this sets up a nice* feedback loop where the next time* HeaderValue is called, it can simply append*/conn->cur_header_tag = 0;conn->cur_tag_cap = 0;conn->cur_tag_size = 0;conn->cur_header_value = 0;conn->cur_value_cap = 0;conn->cur_value_size = 0;}
}static void check_dynamic_string(char **str, int *cap, int size, int add_size)
{if(!*str || size + add_size >= *cap){*cap = size + add_size + 64;*str = (char*)realloc(*str, *cap);}
}static int onHttpHeaderField(http_parser *parser, const char *at, size_t length)
{struct http_connection * conn = (struct http_connection *)parser->data;check_insert_header(parser, conn);check_dynamic_string(&conn->cur_header_tag,&conn->cur_tag_cap, conn->cur_tag_size, length);strncpy(conn->cur_header_tag + conn->cur_tag_size, at, length);return 0;
}static int onHttpHeaderValue(http_parser *parser, const char *at, size_t length)
{struct http_connection * conn = (struct http_connection *)parser->data;check_dynamic_string(&conn->cur_header_value,&conn->cur_value_cap, conn->cur_value_size, length);strncpy(conn->cur_header_value + conn->cur_value_size, at, length);return 0;
}static int onHttpHeadersComplete(http_parser *parser)
{printf("server, http_connection, onHttpHeadersComplete\n");if(parser){struct http_connection * conn = (struct http_connection *)parser->data;if(conn){
#ifdef HTTP_SERVER_DEBUGint i = 0;
#endifcheck_insert_header(parser, conn);conn->state |= HTTP_HEADER_COMPLETE;conn->method = http_method_str((enum http_method)conn->parser.method);sprintf(conn->version, "%.1d.%.1d", conn->parser.http_major,conn->parser.http_minor);#ifdef HTTP_SERVER_DEBUGprintf("server,http_connection, %d headers\n", conn->header_size);for(; i < conn->header_size; i++){printf("header key %s value %s\n", conn->header_tags[i], conn->header_values[i]);}#endif}}return 0;
}static int onHttpBody(http_parser *parser, const char *at, size_t length)
{/* TODO: implement */return 0;
}static int onHttpMessageComplete(http_parser *parser)
{struct http_connection * conn = (struct http_connection *)parser->data;if(conn){_prepare_response(conn);;conn->state |= HTTP_MESSAGE_COMPLETE;_enable_write(conn);_send_response_header(conn);switch(conn->status_code){case 200:case 206:break;default:_disable_write(conn);_peacefull_close(conn);break;}}return 0;
}static void _prepare_response(struct http_connection *conn)
{char *filename = conn->path;char *p;int i = 0;if(strncmp(conn->method, "GET", 3) != 0){conn->status_code = 403;return;}while(*filename == '/') filename++;p = filename;while(*p != '?' && *p != 0) p++;if(*p == '?') *p = 0;conn->fp = fopen(filename, "rb");if(conn->fp){fseek(conn->fp, 0, SEEK_END);conn->remain = ftell(conn->fp);fseek(conn->fp, 0, SEEK_SET);conn->status_code = 200;printf("open %s OK\n", filename);}else{conn->status_code = 404;}
}static void _send_response_header(struct http_connection *conn)
{char * p = conn->buffer;p += sprintf(p, "HTTP/1.1 %d %s\r\n",conn->status_code,_status_string(conn->status_code));if(conn->status_code == 200){p += sprintf(p, "Content-Length: %d\r\n""Content-Type: application/octet-stream\r\n",conn->remain);}p += sprintf(p, "\r\n");//printf("response headers: %s\n", conn->buffer);bufferevent_write(conn->bev, conn->buffer, p - conn->buffer);
}static void _close_socket(struct http_connection * conn)
{if(conn && conn->fd != 0){if(conn->bev){bufferevent_free(conn->bev);conn->bev = 0;}evutil_closesocket(conn->fd);conn->fd = 0;}
}static void _peacefull_close(struct http_connection * conn)
{if( evbuffer_get_length(bufferevent_get_output(conn->bev)) == 0){printf("http_connection, all data sent, close connection(%s:%d)\n", conn->peer_address, conn->peer_port);/* delete this connection */delete_http_connection(conn);return;}else{printf("http_connection, wait bufferevent_socket to flush data\n");}
}static void _event_callback(struct bufferevent *bev, short what, void *ctx)
{struct http_connection * conn = (struct http_connection *)ctx;if( (what & (BEV_EVENT_READING | BEV_EVENT_TIMEOUT)) == (BEV_EVENT_READING | BEV_EVENT_TIMEOUT)){/* TODO: check socket alive */}else if((what &(BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT)) == (BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT)){/* TODO: check socket alive */}else if(what & BEV_EVENT_ERROR){/* TODO: error notify */printf( "http_connection, %s:%d, error - %s\n", conn->peer_address,conn->peer_port,evutil_socket_error_to_string( evutil_socket_geterror(conn->fd) ) );_close_socket(conn);}
}static void _disable_write(struct http_connection *conn)
{if(conn->write_enabled){bufferevent_disable(conn->bev, EV_WRITE|EV_TIMEOUT);conn->write_enabled = 0;}
}static void _enable_write(struct http_connection *conn)
{if(!conn->write_enabled){bufferevent_enable(conn->bev, EV_WRITE | EV_TIMEOUT);conn->write_enabled = 1;}
}// default write callback
static void _write_callback(struct bufferevent *bev, void * args)
{struct http_connection * conn = (struct http_connection *)args;if(conn->fp){if(feof(conn->fp)){printf("http_connection, call peacefull_close via EOF\n");_peacefull_close(conn);}else{int to_read = BUFFER_SIZE;if(to_read > conn->remain) to_read = conn->remain;conn->data_size = fread(conn->buffer, 1, to_read, conn->fp);conn->remain -= conn->data_size;
#ifdef HTTP_SERVER_DEBUGprintf("http_connection, read %d bytes\n", conn->data_size);
#endifif(conn->data_size)bufferevent_write(bev, conn->buffer, conn->data_size);}}else{printf("http_connection, call peacefull_close via fp NULL\n");_peacefull_close(conn);}
}// default read callback
static void _read_callback(struct bufferevent *bev, void * args)
{struct http_connection * conn = (struct http_connection *)args;int n;while( (n = bufferevent_read(bev, conn->buffer, BUFFER_SIZE)) > 0 ){http_parser_execute(&conn->parser, &conn->parser_settings, conn->buffer, n);}
}void delete_http_connection(struct http_connection *conn)
{int i = 0;_disable_write(conn);_close_socket(conn);/* free resources */if(conn->peer_address)free(conn->peer_address);if(conn->fp)fclose(conn->fp);if(conn->cur_header_tag)free(conn->cur_header_tag);if(conn->cur_header_value)free(conn->cur_header_value);if(conn->path)free(conn->path);if(conn->query_string)free(conn->query_string);for(; i < conn->header_size; i++){free(conn->header_tags[i]);free(conn->header_values[i]);}free(conn->header_tags);free(conn->header_values);free(conn);
}

libevent实现http server相关推荐

  1. 基于Libevent的HTTP Server

    简单的Http Server 使用Libevent内置的http相关接口,可以很容易的构建一个Http Server,一个简单的Http Server如下: #include <event2/e ...

  2. libevent和libcurl实现http和https服务器 cJSON使用

    前言 libevent和libcurl都是功能强大的开源库:libevent主要实现服务器,包含了select.epoll等高并发的实现:libcurl实现了curl命令的API封装,主要作为客户端. ...

  3. ott盒子 MySQL_Linux+Nginx+MySql+Php既LNMP源码安装

    前言: 我们都知道Apache作为一款出色的web服务器占据了市场大半个江山,他的地位目前还无人能取代,但是除了Apache,,在web服务器软件行列,Nginx以其性能稳定.功能丰富.运维简单.处理 ...

  4. 基于libevent和unix domain socket的本地server

    https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial 根据这一篇写一个最简单的demo.然后开始写client. cl ...

  5. libevent 实现的socket 通信 server以及解决找不到动态库的方法

    注: client 个人觉得没必要用 libvent来实现就没写 注:由于 涉及到回调函数的使用 如有疑惑的可以先去了解回调函数的实现机理 先来说一下 libevent主要是干啥的 : 内部封装了 s ...

  6. 【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50631393 简介 由于本项目是纯异步的,而对于大量 socket 连接,使用 selec ...

  7. libevent入门教程:Echo Server based on libevent - Blog of Felix021 - 日,泯然众人矣。

    libevent入门教程:Echo Server based on libevent 转载请注明出自 http://www.felix021.com/blog/read.php?2068 ,如是转载文 ...

  8. libevent介绍

    libevent是一款事件驱动的网络开发包 由于采用 c 语言开发 体积小巧,跨平台,速度极快. 通常我们在建立服务器的处理模型的时候,主要是下面集中模型; (1)    a new Connecti ...

  9. Libevent实现TCP服务循环监听

    目标 建立一个简单的tcp服务,可持续的监听客户端的连接和请求 细节 libevent stream socket EV_READ | EV_PERSIST 代码 要义libevent对stream_ ...

最新文章

  1. 非常有必要了解的Springboot启动扩展点
  2. mysql取最接近的两个值_Mysql:获取一行中另一个字段的最高值和最...
  3. 关于group by 和having(数据库)
  4. e影安全智能浏览器_【启耀玻璃】智能调光玻璃有什么特点? - 调光艺术玻璃|防火防弹玻璃|LOW-E节能玻璃|隔音隔热玻璃|特种安全玻璃|夹层中空玻璃-...
  5. JavaEE课程目标、个人目标、互联网应用和企业级应用的区别
  6. python 重写断言_历时四年,Dropbox 用 Rust 重写同步引擎核心代码
  7. mysql中的运算符的执行顺序_【MySQL】执行顺序
  8. Java反射机制的使用方法
  9. linux yum自动挂载_LINUX6安装YUM仓库和实现开机自动挂载
  10. mongodb3.0 性能測试报告 一
  11. Python找出某元素的索引下标
  12. web表单设计:点石成金_如何设计安全的Web表单:验证,清理和控制
  13. LeetCode——maximal-rectangle
  14. Optional 是个好东西,你会用么?| 原力计划
  15. 7.看板方法---使用看板进行协调
  16. WINDOWS搜索dll的路径顺序
  17. 苹果8wifi找不到服务器,苹果8连不上wifi怎么办
  18. Ubuntu18.04 tc指令模拟网络丢包与延时
  19. 不仅仅是游戏,王者荣耀如何突破次元壁?
  20. Ubuntu Linux全方位学习,哪一种Ubuntu官方版本适合你?

热门文章

  1. ios piv6遭拒绝
  2. [经验技巧] 路由mini安装OpenWRT源的Transmission插件,实现PT下载(需SSH)
  3. 华硕z170a如何开启m2_华硕z170主板装win7教程及BIOS设置
  4. 2021/12/14 nginx包下载安装步骤记录
  5. 【java毕业设计】基于java+swing的模拟写字板设计与实现(毕业论文+程序源码)——模拟写字板
  6. 肠道微生物群的老化及其对宿主免疫力的影响
  7. 你绝没见过的奢华 全球最贵的13样东西
  8. SAP发送邮件作为附件
  9. 英语语法2-一般过去时
  10. SynchroTrap-基于松散行为相似度的欺诈账户检测算法