libevent文档
官方网站
官方文档
官方GitHub
libevent-2.1.11-stable.tar.gz
前言
Libevent是用于开发可伸缩网络服务器的事件通知库。Libevent API提供了一种机制,在文件描述符上发生特定事件或达到超时后执行回调函数。此外,Libevent还支持由于信号或定期超时而进行的回调。
Libevent用于取代在事件驱动的网络服务器中的事件的循环。应用程序只需调用event_base_dispatch(),然后动态添加或删除事件,而无需更改事件循环。
目前,Libevent支持/dev/poll、kqueue(2)、select(2)、poll(2)、epoll(4)和evports。内部事件机制完全独立于公开的事件API,简单的Libevent更新可以提供新的功能,而无需重新设计应用程序。因此,Libevent允许进行可移植的应用程序开发,并提供操作系统上可用的最可伸缩的事件通知机制。Libevent也可以用于多线程程序。Libevent应该在Linux、*BSD、Mac OS X、Solaris和Windows上编译。
其设计目标是:
可移植性:使用 libevent 编写的程序应该可以在 libevent 支持的所有平台上工作。即使 没有好的方式进行非阻塞 IO,libevent 也应该支持一般的方式,让程序可以在受限的环境中运行。
速度:libevent 尝试使用每个平台上最高速的非阻塞 IO 实现,并且不引入太多的额外开销。
可扩展性:libevent 被设计为程序即使需要上万个活动套接字的时候也可以良好工作。
方便:无论何时,最自然的使用 libevent 编写程序的方式应该是稳定的、可移植的。
libevent 由下列组件构成:
- evutil:用于抽象不同平台网络实现差异的通用功能。
- event 和 event_base:libevent 的核心,为各种平台特定的、基于事件的非阻塞 IO 后 端提供抽象 API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基 本的超时功能,检测 OS 信号。
- bufferevent:为 libevent 基于事件的核心提供使用更方便的封装。除了通知程序套接字 已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时 IO 已经真正 发生。
- evbuffer:在 bufferevent 层之下实现了缓冲功能,并且提供了方便有效的访问函数。
- evhttp:一个简单的 HTTP 客户端/服务器实现。
- evdns:一个简单的 DNS 客户端/服务器实现。
- evrpc:一个简单的 RPC 实现。
编译
linux
$ ./configure
$ make
$ make verify # (optional)
$ sudo make install
ARM
$ ./configure --host=arm-linux --prefix=/usr/local/libevent_arm CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++
$ make
$ make verify # (optional)
$ sudo make install
Building and installing Libevent
生成库
创建 libevent 时,默认安装下列库:
ibevent_core:所有核心的事件和缓冲功能,包含了所有的 event_base、evbuffer、 bufferevent 和工具函数。
ibevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括 HTTP、 DNS 和 RPC。
libevent:这个库因为历史原因而存在,它包含 libevent_core 和 libevent_extra 的内容。 不应该使用这个库,未来版本的 libevent 可能去掉这个库。
某些平台上可能安装下列库:
libevent_pthreads:添加基于 pthread 可移植线程库的线程和锁定实现。它独立于 libevent_core,这样程序使用 libevent 时就不需要链接到 pthread,除非实际上是以多线程的方式使用libevent。
libevent_openssl:这个库为使用 bufferevent 和 OpenSSL 进行加密的通信提供支持。 它独立于 libevent_core,这样程序使用 libevent 时就不需要链接到 OpenSSL,除非实际使用的是加密连接。
概述
标准用法
使用Libevent的每个程序都必须包含
库设置
在调用任何其他Libevent函数之前,需要设置库。如果要在多线程应用程序的多个线程中使用Libevent,则需要初始化线程支持:通常使用evthread_use_pthreads()或evthread_use_windows_threads()。有关更多信息,请参见
这也是可以用event_set_mem_functions替换Libevent的内存管理函数,并用event_enable_debug_mode()启用调试模式的地方。
创建event base
接下来,需要使用event_base_new()或event_base_new_with_config()创建一个event_base结构体。event_base负责跟踪哪些事件是“待处理的”(也就是说,被监视以查看它们是否变为活动的)以及哪些事件是“活动的”。每个事件都与单个event_base相关联。
事件通知
对于要监视的每个文件描述符,必须使用event_new()创建事件结构。(您也可以声明一个事件结构并调用event_assign()来初始化该结构的成员。)要启用通知,您可以通过调用event_add()将该结构添加到监视的事件列表中。只要事件结构处于活动状态,它就必须保持分配状态,因此通常应该在堆上分配它。
调度事件。
最后,调用event_base_dispatch()循环和分派事件。您还可以使用event_base_loop()进行更细粒度的控制。
目前,一次只能有一个线程调度给定的event_base 。如果希望一次在多个线程中运行事件,可以有一个将工作添加到工作队列中的event_base ,也可以创建多个event_base对象。
I/O缓冲区
Libevent在常规事件回调的基础上提供了缓冲I/O抽象。这个抽象称为bufferevent。bufferevent提供自动填充和移除的输入和输出缓冲区。缓冲事件的用户不再直接处理I/O,而是读取输入和写入输出缓冲区。
一旦通过bufferevent_socket_new()初始化,bufferevent结构就可以与bufferevent_enable()和bufferevent_disable()一起重复使用。您将调用bufferevent_read()和bufferevent_write(),而不是直接读取和写入套接字。
启用读取后,bufferevent将尝试从文件描述符读取并调用读取回调。每当输出缓冲区被排放到写低水位线(默认为0)以下时,就会执行写回调。
有关更多信息,请参阅
计时器
Libevent还可以用来创建计时器,在一定时间过期后调用回调。evtimer_new()宏返回要用作计时器的事件结构。要激活计时器,请调用evtimer_add()。可以通过调用evtimer_del()来停用计时器。(这些宏是围绕event_new()、event_add()和event_del()的精简封装;您也可以使用它们。)
异步DNS解析
Libevent提供了一个异步DNS解析器,应该使用它来代替标准DNS解析器函数。有关更多详细信息,请参阅
事件驱动的HTTP服务器
Libevent提供了一个非常简单的事件驱动HTTP服务器,可以嵌入到程序中并用于服务HTTP请求。
要使用此功能,您需要在程序中包含
RPC服务器和客户机的框架
Libevent提供了一个创建RPC服务器和客户端的框架。它负责对所有数据结构进行编组和解组。
API参考
要浏览libevent API的完整文档,请单击以下任何链接。
event2/event.h :主libevent头
event2/thread.h :多线程程序
event2/buffer.h 和event2/bufferevent.h :网络读写的缓冲区管理
event2/util.h : 可移植非阻塞网络代码的实用函数
event2/dns.h :异步DNS解析
event2/http.h :基于libevent的嵌入式HTTP服务器
event2/rpc.h :创建RPC服务器和客户端的框架
event2/watch.h :“准备”和“检查”观察者
详细说明
基于Libevent 2.0+,在C语言中编写快速可移植的异步网络IO程序。
Libevent详解与实践(一)
Libevent详解与实践(二)
Libevent详解与实践(三)
Libevent详解与实践(四)
Libevent详解与实践(五)
Libevent详解与实践(六)
Libevent详解与实践(七)
Libevent详解与实践(八)
Libevent详解与实践(九)
Libevent详解与实践(十)
示例
Timer
示例
#include
#include
static int numCalls = 0;
static int numCalls_now = 0;
struct timeval lasttime;
struct timeval lasttime_now;
static void timeout_cb(evutil_socket_t fd, short event, void *arg)
{
struct event *ev = (struct event *)arg;
struct timeval newtime,tv_diff;
double elapsed;
evutil_gettimeofday(&newtime, NULL);
evutil_timersub(&newtime, &lasttime, &tv_diff);
elapsed = tv_diff.tv_sec + (tv_diff.tv_usec / 1.0e6);
lasttime = newtime;
printf("[%.3f]timeout_cb %d \n",elapsed,++numCalls);
}
static void now_timeout_cb(evutil_socket_t fd, short event, void *arg)
{
struct event *ev = (struct event *)arg;
struct timeval tv = {3,0};
struct timeval newtime,tv_diff;
double elapsed;
evutil_gettimeofday(&newtime, NULL);
evutil_timersub(&newtime, &lasttime_now, &tv_diff);
elapsed = tv_diff.tv_sec + (tv_diff.tv_usec / 1.0e6);
lasttime_now = newtime;
printf("[%.3f]now_timeout_cb %d \n",elapsed,++numCalls_now);
//每次回调都将当前先del,再次添加
//if (!event_pending(ev,EV_PERSIST|EV_TIMEOUT,NULL))
//{
// printf("if\n");
// event_del(ev);
// event_add(ev, &tv);
//}
//添加新的event
if (!event_pending(ev,EV_PERSIST|EV_TIMEOUT,NULL))
{
struct event_base *evBase = event_get_base(ev);
event_del(ev);
event_free(ev);
ev = event_new(evBase, -1, EV_PERSIST|EV_TIMEOUT, now_timeout_cb, event_self_cbarg());
event_add(ev, &tv);
}
}
int main(int argc, char **argv)
{
struct event_base *evBase = NULL;
struct event_config *evConf = NULL;
struct event *ev_now_timeout = NULL;
struct event *ev_timeout = NULL;
struct timeval tv = {0,0};
struct timeval tv_now = {0,0};
//创建简单的event_base
evBase = event_base_new();
//创建带配置的event_base
evConf = event_config_new();//创建event_config
evBase = event_base_new_with_config(evConf);
//创建event
//传递自己event_self_cbarg()
ev_now_timeout = evtimer_new(evBase, now_timeout_cb, event_self_cbarg());
//设置时间
tv.tv_sec = 1;
tv.tv_usec = 500 * 1000;
ev_timeout = event_new(evBase, -1, EV_PERSIST|EV_TIMEOUT, timeout_cb, event_self_cbarg());
//添加event
event_add(ev_now_timeout, &tv_now);//立即执行一次,然后定时
event_add(ev_timeout, &tv);
//获取时间
evutil_gettimeofday(&lasttime, NULL);
evutil_gettimeofday(&lasttime_now, NULL);
//循环
//event_base_loop(evBase, 0);
event_base_dispatch(evBase);
//释放
event_free(ev_timeout);
event_free(ev_now_timeout);
event_config_free(evConf);
event_base_free(evBase);
}
实现了一个Timer定时器,添加了2个event,ev_now_timeout立即执行一次,然后按3s周期执行;ev_timeout以1.5s周期执行。
TCP
Server
#include
#include
#include
#include
#include
#include
// 读缓冲区回调
static void read_cb(struct bufferevent *bev, void *arg)
{
//读到buff
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
//读取evbuffer
//struct evbuffer *input = bufferevent_get_input(bev);
//size_t size = evbuffer_get_length(input);
//
//char *buf = (char *)malloc(size);
//evbuffer_remove(input, buf, size);
printf("[server]rece client data\n");
printf("[server]client say: %s\n", buf);
}
// 写缓冲区回调
static void write_cb(struct bufferevent *bev, void *arg)
{
printf("[server]write_cb\n");
}
// 事件
static void event_cb(struct bufferevent *bev, short events, void *arg)
{
if (events & BEV_EVENT_EOF)
{
printf("[server]connection close\n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("[server]connection error\n");
}else if(events & BEV_EVENT_CONNECTED)
{
printf("[server]connection success\n");
return;
}
bufferevent_free(bev);
printf("[server]bufferevent free\n");
}
static void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
struct bufferevent* bev = (struct bufferevent*)arg;
read(fd, buf, sizeof(buf));
//struct evbuffer *output = bufferevent_get_output(bev);
//evbuffer_add(output, buf, strlen(buf)+1);
bufferevent_write(bev, buf, strlen(buf)+1);
}
static void cb_listener(
struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr,
int len, void *ptr)
{
printf("[server]connect new client\n");
struct event_base* base = (struct event_base*)ptr;
// 通信操作
// 添加新事件
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "[server]error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
// 给bufferevent缓冲区设置回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(bev, EV_READ);
// 创建一个事件
struct event* ev = event_new(base, STDIN_FILENO,
EV_READ | EV_PERSIST,
send_cb, bev);
event_add(ev, NULL);
}
static void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
int err = EVUTIL_SOCKET_ERROR();
printf("[Server]Got an error %d (%s) on the listener.Shutting down.\n",
err, evutil_socket_error_to_string(err));
event_base_loopexit(base, NULL);
}
int main(int argc, const char* argv[])
{
struct sockaddr_in serv;
struct event_base* base;
struct evconnlistener* listener;
// init server
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(6666);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
base = event_base_new();
if (!base) {
printf("[server]Couldn't open event base\n");
return 1;
}
// 创建套接字
// 绑定
// 接收连接请求
//LEV_OPT_CLOSE_ON_FREE 如果设置了这个选项,释放连接监听器会关闭底层套接字。
//LEV_OPT_REUSEABLE 某些平台在默认情况下,关闭某监听套接字后,要过一会儿其他套接字才可以绑定到同一个端口。
//设置这个标志会让 libevent 标记套接字是可重用的,这样一旦关闭,可以立即打开其他套接字,在相同端口进行监听。
listener = evconnlistener_new_bind(base, cb_listener, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
36, (struct sockaddr*)&serv, sizeof(serv));
if (!listener) {
perror("[server]Couldn't create listener");
return 1;
}
//error处理
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
//释放
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
实现了一个TCP server,监听6666端口,与客户端建立连接,以后可以互相发送消息。
Client
实现了一个TCP client,连接本地6666端口,与客户端建立连接,以后可以互相发送消息。
#include
#include
#include
#include
#include
#include
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
printf("[client]rece server data\n");
printf("[client]server say: %s\n", buf);
}
void write_cb(struct bufferevent *bev, void *arg)
{
printf("[client]write_cb\n");
}
void event_cb(struct bufferevent *bev, short events, void *arg)
{
if (events & BEV_EVENT_EOF)
{
printf("[client]connection close\n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("[client]connection error\n");
}
else if(events & BEV_EVENT_CONNECTED)
{
printf("[client]connection success\n");
return;
}
bufferevent_free(bev);
printf("[client]bufferevent free\n");
}
void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
struct bufferevent* bev = (struct bufferevent*)arg;
read(fd, buf, sizeof(buf));
bufferevent_write(bev, buf, strlen(buf)+1);
}
int main(int argc, const char* argv[])
{
struct event_base *base;
struct bufferevent* bev;
struct sockaddr_in serv;
struct event* ev;
base = event_base_new();
if (!base) {
printf("[client]Couldn't open event base\n");
return 1;
}
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
//连接服务器
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(6666);
//解析 IP 地址
evutil_inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
//连接
bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
//设置回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(bev, EV_READ);
// 创建一个事件
//STDIN_FILENO:接收键盘的输入
ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
send_cb, bev);
event_add(ev, NULL);
event_base_dispatch(base);
//释放
bufferevent_free(bev);
event_free(ev);
event_base_free(base);
}
HTTP
Server
实现了一个http server。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
char uri_root[512];
static const struct table_entry {
const char *extension;
const char *content_type;
} content_type_table[] = {
{ "txt", "text/plain" },
{ "c", "text/plain" },
{ "h", "text/plain" },
{ "html", "text/html" },
{ "htm", "text/htm" },
{ "css", "text/css" },
{ "gif", "image/gif" },
{ "jpg", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "png", "image/png" },
{ "pdf", "application/pdf" },
{ "ps", "application/postscript" },
{ NULL, NULL },
};
/* 尝试猜测“path”的内容类型 */
static const char *
guess_content_type(const char *path)
{
const char *last_period, *extension;
const struct table_entry *ent;
last_period = strrchr(path, '.');
if (!last_period || strchr(last_period, '/'))
goto not_found; /* no exension */
extension = last_period + 1;
for (ent = &content_type_table[0]; ent->extension; ++ent) {
if (!evutil_ascii_strcasecmp(ent->extension, extension))
return ent->content_type;
}
not_found:
return "application/misc";
}
/* 用于/test URI请求的回调 */
static void
dump_request_cb(struct evhttp_request *req, void *arg)
{
const char *cmdtype;
struct evkeyvalq *headers;
struct evkeyval *header;
struct evbuffer *buf_in;
struct evbuffer *buf_out;
struct evhttp_uri *decoded = NULL;
struct evkeyvalq params;
char *decoded_path;
const char *path;
char cbuf[1024] = {0};
const char *uri = evhttp_request_get_uri(req);
switch (evhttp_request_get_command(req)) {
case EVHTTP_REQ_GET: cmdtype = "GET"; break;
case EVHTTP_REQ_POST: cmdtype = "POST"; break;
case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
default: cmdtype = "unknown"; break;
}
printf("Received a %s request for %s\nHeaders:\n",
cmdtype, uri);
headers = evhttp_request_get_input_headers(req);
for (header = headers->tqh_first; header;
header = header->next.tqe_next) {
printf(" %s: %s\n", header->key, header->value);
}
/*********************************/
/* 解析 URI */
decoded = evhttp_uri_parse(uri);
if (!decoded) {
printf("It's not a good URI. Sending BADREQUEST\n");
evhttp_send_error(req, HTTP_BADREQUEST, 0);
return;
}
/* 获取path */
path = evhttp_uri_get_path(decoded);
if (!path){
evhttp_send_error(req, HTTP_BADREQUEST, 0);
return;
}
printf("path: %s\n", path);
//解析URI的参数
//将URL数据封装成key-value格式,q=value1, s=value2
evhttp_parse_query(uri, ¶ms);
//得到a所对应的value
const char *a_data = evhttp_find_header(¶ms, "a");
printf("a=%s\n",a_data);
/*********************************/
if (strcmp(cmdtype,"POST") == 0)
{
//获取POST方法的数据
buf_in = evhttp_request_get_input_buffer(req);
if (buf_in==NULL)
{
printf("evBuf null, err\n");
goto err;
}
//获取长度
int buf_in_len = evbuffer_get_length(buf_in);
printf("evBuf len:%d\n",buf_in_len);
if(buf_in_len <= 0)
{
goto err;
}
//将数据从evbuff中移动到char *
int str_len = evbuffer_remove(buf_in,cbuf,sizeof(cbuf));
if (str_len <= 0)
{
printf("post parameter null err\n");
goto err;
}
printf("str_len:%d cbuf:%s\n",str_len,cbuf);
}
/*********************************/
buf_out = evbuffer_new();
if(!buf_out)
{
puts("failed to create response buffer \n");
return;
}
evbuffer_add_printf(buf_out,"%s","success");
evhttp_send_reply(req, 200, "OK", buf_out);
return;
err:
evhttp_send_error(req, HTTP_INTERNAL, 0);
}
static void
send_document_cb(struct evhttp_request *req, void *arg)
{
evhttp_send_error(req, 404, "url was not found");
}
static void
do_term(int sig, short events, void *arg)
{
struct event_base *base = (struct event_base *)arg;
event_base_loopbreak(base);
fprintf(stderr, "Got %i, Terminating\n", sig);
}
static int
display_listen_sock(struct evhttp_bound_socket *handle)
{
struct sockaddr_storage ss;
evutil_socket_t fd;
ev_socklen_t socklen = sizeof(ss);
char addrbuf[128];
void *inaddr;
const char *addr;
int got_port = -1;
fd = evhttp_bound_socket_get_fd(handle);
memset(&ss, 0, sizeof(ss));
if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
perror("getsockname() failed");
return 1;
}
if (ss.ss_family == AF_INET) {
got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
} else if (ss.ss_family == AF_INET6) {
got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
}
else {
fprintf(stderr, "Weird address family %d\n",
ss.ss_family);
return 1;
}
addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
sizeof(addrbuf));
if (addr) {
printf("Listening on %s:%d\n", addr, got_port);
evutil_snprintf(uri_root, sizeof(uri_root),
"http://%s:%d",addr,got_port);
} else {
fprintf(stderr, "evutil_inet_ntop failed\n");
return 1;
}
return 0;
}
int
main(int argc, char **argv)
{
struct event_base *base = NULL;
struct evhttp *http = NULL;
struct evhttp_bound_socket *handle = NULL;
struct evconnlistener *lev = NULL;
struct event *term = NULL;
int ret = 0;
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
ret = 1;
goto err;
}
//event_base
base = event_base_new();
if (!base) {
fprintf(stderr, "Couldn't create an event_base: exiting\n");
ret = 1;
}
/* 创建一个新的evhttp对象来处理请求。 */
http = evhttp_new(base);
if (!http) {
fprintf(stderr, "couldn't create evhttp. Exiting.\n");
ret = 1;
}
/* / test URI将所有请求转储到stdout并说200 OK。 */
evhttp_set_cb(http, "/test", dump_request_cb, NULL);
/*要接受任意请求,需要设置一个“通用”cb。 还可以为特定路径添加回调。 */
evhttp_set_gencb(http, send_document_cb, NULL);
//绑定socket
handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", 8888);
if (!handle) {
fprintf(stderr, "couldn't bind to port %d. Exiting.\n", 8888);
ret = 1;
goto err;
}
//监听socket
if (display_listen_sock(handle)) {
ret = 1;
goto err;
}
//终止信号
term = evsignal_new(base, SIGINT, do_term, base);
if (!term)
goto err;
if (event_add(term, NULL))
goto err;
//事件分发
event_base_dispatch(base);
err:
if (http)
evhttp_free(http);
if (term)
event_free(term);
if (base)
event_base_free(base);
return ret;
}
Client
实现了一个http client。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int ignore_cert = 0;
static void
http_request_done(struct evhttp_request *req, void *ctx)
{
char buffer[256];
int nread;
struct evbuffer *buf_in;
char cbuf[1024] = {0};
//错误处理,打印
if (!req || !evhttp_request_get_response_code(req)) {
struct bufferevent *bev = (struct bufferevent *) ctx;
unsigned long oslerr;
int printed_err = 0;
int errcode = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "some request failed - no idea which one though!\n");
/* 尝试打印 */
if (! printed_err)
fprintf(stderr, "socket error = %s (%d)\n",
evutil_socket_error_to_string(errcode),
errcode);
return;
}
fprintf(stderr, "Response line: %d %s\n",
evhttp_request_get_response_code(req),
evhttp_request_get_response_code_line(req));
//获取数据
buf_in = evhttp_request_get_input_buffer(req);
if (buf_in==NULL)
{
printf("evBuf null, err\n");
}
//获取长度
int buf_in_len = evbuffer_get_length(buf_in);
printf("evBuf len:%d\n",buf_in_len);
if(buf_in_len <= 0)
{
}
//将数据从evbuff中移动到char *
int str_len = evbuffer_remove(buf_in,cbuf,sizeof(cbuf));
if (str_len <= 0)
{
printf("post parameter null err\n");
}
printf("str_len:%d cbuf:%s\n",str_len,cbuf);
}
static void
err(const char *msg)
{
fputs(msg, stderr);
}
int
main(int argc, char **argv)
{
int r;
struct event_base *base = NULL;
struct evhttp_uri *http_uri = NULL;
const char *url = NULL, *data_file = NULL;
const char *scheme, *host, *path, *query;
char uri[256];
int port;
int retries = 0;
int timeout = -1;
struct bufferevent *bev;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req;
struct evkeyvalq *output_headers;
struct evbuffer *output_buffer;
int i;
int ret = 0;
//初始化url
url = "http://127.0.0.1:8888/test?a=123";
if (!url) {
goto error;
}
http_uri = evhttp_uri_parse(url);
if (http_uri == NULL) {
err("malformed url");
goto error;
}
scheme = evhttp_uri_get_scheme(http_uri);
//忽略大小写比较字符串
if (scheme == NULL || strcasecmp(scheme, "http") != 0) {
err("url must be http");
goto error;
}
host = evhttp_uri_get_host(http_uri);
if (host == NULL) {
err("url must have a host");
goto error;
}
port = evhttp_uri_get_port(http_uri);
if (port == -1) {
port = 80;
}
path = evhttp_uri_get_path(http_uri);
if (strlen(path) == 0) {
path = "/";
}
query = evhttp_uri_get_query(http_uri);
if (query == NULL) {
//将可变参数 “…” 按照format的格式格式化为字符串,然后再将其拷贝至str中。
snprintf(uri, sizeof(uri) - 1, "%s", path);
} else {
snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
}
uri[sizeof(uri) - 1] = '\0';
// 创建 event base
base = event_base_new();
if (!base) {
perror("event_base_new()");
goto error;
}
if (strcasecmp(scheme, "http") == 0) {
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
}
if (bev == NULL) {
fprintf(stderr, "bufferevent_socket_new() failed\n");
goto error;
}
evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev, host, port);
if (evcon == NULL) {
fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
goto error;
}
//重试
if (retries > 0) {
evhttp_connection_set_retries(evcon, retries);
}
//超时
if (timeout >= 0) {
evhttp_connection_set_timeout(evcon, timeout);
}
//回调
req = evhttp_request_new(http_request_done, bev);
if (req == NULL) {
fprintf(stderr, "evhttp_request_new() failed\n");
goto error;
}
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Host", host);
evhttp_add_header(output_headers, "Connection", "close");
//文件路径
data_file = "/home/zza/libevent/demo/test.txt";
if (data_file) {
char buf[1024];
output_buffer = evhttp_request_get_output_buffer(req);
//post file传统复制
//FILE* f = fopen(data_file, "rb");
//size_t s;
//size_t bytes = 0;
//if (!f) {
// goto error;
//}
//
//while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
// evbuffer_add(output_buffer, buf, s);
// bytes += s;
//}
//evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
//evhttp_add_header(output_headers, "Content-Length", buf);
//fclose(f);
//************************************************************
//post file使用evbuffer_add_file()或
//evbuffer_add_file_segment(),以避免不必要的复制
//int fd = open(data_file, O_RDONLY);
//if (fd == -1)
//{
// fprintf(stderr, "open %s failed\n", data_file);
// goto error;
//}
//evbuffer_add_file(output_buffer,fd,0,-1);
//ev_ssize_t size = evbuffer_copyout(output_buffer, buf, sizeof(buf));
//evhttp_add_header(output_headers, "Content-Length", buf);
//************************************************************
//http post json
//sprintf(buf,"%s","{\"a\":\"b\"}");
//evbuffer_add(output_buffer, buf, strlen(buf));
//
//evhttp_add_header(output_headers, "Content-Type", "application/json;charset=UTF-8");
//************************************************************
//http post format
sprintf(buf,"%s\r\n%s\r\n\r\n%s\r\n%s\r\n",
"--------------------------123",
"Content-Disposition: form-data; name=\"hello\"",
"world!!!!",
"--------------------------123");
evbuffer_add(output_buffer, buf, strlen(buf));
evhttp_add_header(output_headers, "Content-Type",
"multipart/form-data; boundary=--------------------------123");
//************************************************************
}
//文件路径为NULL时为get请求,非空post
//请求
r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
if (r != 0) {
fprintf(stderr, "evhttp_make_request() failed\n");
goto error;
}
event_base_dispatch(base);
goto cleanup;
error:
printf("error stop");
ret = 1;
cleanup:
if (evcon)
evhttp_connection_free(evcon);
if (http_uri)
evhttp_uri_free(http_uri);
if (base)
event_base_free(base);
return ret;
}
HTTPS
Client
/*
This is an example of how to hook up evhttp with bufferevent_ssl
It just GETs an https URL given on the command-line and prints the response
body to stdout.
Actually, it also accepts plain http URLs to make it easy to compare http vs
https code paths.
Loosely based on le-proxy.c.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int ignore_cert = 0;
static void
http_request_done(struct evhttp_request *req, void *ctx)
{
char buffer[256];
int nread;
if (!req || !evhttp_request_get_response_code(req)) {
/* If req is NULL, it means an error occurred, but
* sadly we are mostly left guessing what the error
* might have been. We'll do our best... */
struct bufferevent *bev = (struct bufferevent *) ctx;
unsigned long oslerr;
int printed_err = 0;
int errcode = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "some request failed - no idea which one though!\n");
/* Print out the OpenSSL error queue that libevent
* squirreled away for us, if any. */
while ((oslerr = bufferevent_get_openssl_error(bev))) {
ERR_error_string_n(oslerr, buffer, sizeof(buffer));
fprintf(stderr, "%s\n", buffer);
printed_err = 1;
}
/* If the OpenSSL error queue was empty, maybe it was a
* socket error; let's try printing that. */
if (! printed_err)
fprintf(stderr, "socket error = %s (%d)\n",
evutil_socket_error_to_string(errcode),
errcode);
return;
}
fprintf(stderr, "Response line: %d %s\n",
evhttp_request_get_response_code(req),
evhttp_request_get_response_code_line(req));
while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
buffer, sizeof(buffer)))
> 0) {
/* These are just arbitrary chunks of 256 bytes.
* They are not lines, so we can't treat them as such. */
fwrite(buffer, nread, 1, stdout);
}
}
static void
err(const char *msg)
{
fputs(msg, stderr);
}
static void
err_openssl(const char *func)
{
fprintf (stderr, "%s failed:\n", func);
/* This is the OpenSSL function that prints the contents of the
* error stack to the specified file handle. */
ERR_print_errors_fp (stderr);
exit(1);
}
int
main(int argc, char **argv)
{
int r;
struct event_base *base = NULL;
struct evhttp_uri *http_uri = NULL;
const char *url = NULL, *data_file = NULL;
const char *crt = NULL;
const char *scheme, *host, *path, *query;
char uri[256];
int port;
int retries = 0;
int timeout = -1;
SSL_CTX *ssl_ctx = NULL;
SSL *ssl = NULL;
struct bufferevent *bev;
struct evhttp_connection *evcon = NULL;
struct evhttp_request *req;
struct evkeyvalq *output_headers;
struct evbuffer *output_buffer;
int i;
int ret = 0;
enum { HTTP, HTTPS } type = HTTP;
//初始化url
url = "https://127.0.0.1:8888/login";
if (!url) {
goto error;
}
http_uri = evhttp_uri_parse(url);
if (http_uri == NULL) {
err("malformed url");
goto error;
}
scheme = evhttp_uri_get_scheme(http_uri);
if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
strcasecmp(scheme, "http") != 0)) {
err("url must be http or https");
goto error;
}
host = evhttp_uri_get_host(http_uri);
if (host == NULL) {
err("url must have a host");
goto error;
}
port = evhttp_uri_get_port(http_uri);
if (port == -1) {
port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
}
path = evhttp_uri_get_path(http_uri);
if (strlen(path) == 0) {
path = "/";
}
query = evhttp_uri_get_query(http_uri);
if (query == NULL) {
snprintf(uri, sizeof(uri) - 1, "%s", path);
} else {
snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
}
uri[sizeof(uri) - 1] = '\0';
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
// Initialize OpenSSL
SSL_library_init();
ERR_load_crypto_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
#endif
/* This isn't strictly necessary... OpenSSL performs RAND_poll
* automatically on first use of random number generator. */
r = RAND_poll();
if (r == 0) {
err_openssl("RAND_poll");
goto error;
}
/* Create a new OpenSSL context */
ssl_ctx = SSL_CTX_new(SSLv23_method());
if (!ssl_ctx) {
err_openssl("SSL_CTX_new");
goto error;
}
// Create event base
base = event_base_new();
if (!base) {
perror("event_base_new()");
goto error;
}
// Create OpenSSL bufferevent and stack evhttp on top of it
ssl = SSL_new(ssl_ctx);
if (ssl == NULL) {
err_openssl("SSL_new()");
goto error;
}
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
// Set hostname for SNI extension
SSL_set_tlsext_host_name(ssl, host);
#endif
if (strcasecmp(scheme, "http") == 0) {
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
} else {
type = HTTPS;
bev = bufferevent_openssl_socket_new(base, -1, ssl,
BUFFEREVENT_SSL_CONNECTING,
BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
}
if (bev == NULL) {
fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
goto error;
}
bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
// For simplicity, we let DNS resolution block. Everything else should be
// asynchronous though.
evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
host, port);
if (evcon == NULL) {
fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
goto error;
}
if (retries > 0) {
evhttp_connection_set_retries(evcon, retries);
}
if (timeout >= 0) {
evhttp_connection_set_timeout(evcon, timeout);
}
// Fire off the request
req = evhttp_request_new(http_request_done, bev);
if (req == NULL) {
fprintf(stderr, "evhttp_request_new() failed\n");
goto error;
}
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Host", host);
evhttp_add_header(output_headers, "Connection", "close");
if (data_file) {
/* NOTE: In production code, you'd probably want to use
* evbuffer_add_file() or evbuffer_add_file_segment(), to
* avoid needless copying. */
FILE * f = fopen(data_file, "rb");
char buf[1024];
size_t s;
size_t bytes = 0;
if (!f) {
goto error;
}
output_buffer = evhttp_request_get_output_buffer(req);
while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
evbuffer_add(output_buffer, buf, s);
bytes += s;
}
evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
evhttp_add_header(output_headers, "Content-Length", buf);
fclose(f);
}
r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
if (r != 0) {
fprintf(stderr, "evhttp_make_request() failed\n");
goto error;
}
event_base_dispatch(base);
goto cleanup;
error:
ret = 1;
cleanup:
if (evcon)
evhttp_connection_free(evcon);
if (http_uri)
evhttp_uri_free(http_uri);
if (base)
event_base_free(base);
if (ssl_ctx)
SSL_CTX_free(ssl_ctx);
if (type == HTTP && ssl)
SSL_free(ssl);
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
EVP_cleanup();
ERR_free_strings();
#if OPENSSL_VERSION_NUMBER < 0x10000000L
ERR_remove_state(0);
#else
ERR_remove_thread_state(NULL);
#endif
CRYPTO_cleanup_all_ex_data();
sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) */
#ifdef _WIN32
WSACleanup();
#endif
return ret;
}
Server
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
unsigned short serverPort = 8888;
void die_most_horribly_from_openssl_error (const char *func)
{
fprintf (stderr, "%s failed:\n", func);
/* This is the OpenSSL function that prints the contents of the
* error stack to the specified file handle. */
ERR_print_errors_fp (stderr);
exit (EXIT_FAILURE);
}
/* This callback gets invoked when we get any http request that doesn't match
* any other callback. Like any evhttp server callback, it has a simple job:
* it must eventually call evhttp_send_error() or evhttp_send_reply().
*/
static void
login_cb (struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = NULL;
const char *uri = evhttp_request_get_uri (req);
struct evhttp_uri *decoded = NULL;
/* 判断 req 是否是GET 请求 */
if (evhttp_request_get_command (req) == EVHTTP_REQ_GET)
{
struct evbuffer *buf = evbuffer_new();
if (buf == NULL) return;
evbuffer_add_printf(buf, "Requested: %s\n", uri);
evhttp_send_reply(req, HTTP_OK, "OK", buf);
return;
}
/* Get请求,直接return 200 OK */
if (evhttp_request_get_command (req) != EVHTTP_REQ_POST)
{
evhttp_send_reply (req, 200, "OK", NULL);
return;
}
}
/**
* 该回调负责创建新的SSL连接并将其包装在OpenSSL bufferevent中。
* 这是我们实现https服务器而不是普通的http服务器的方式。
*/
static struct bufferevent* bevcb(struct event_base *base, void *arg)
{
struct bufferevent* r;
SSL_CTX *ctx = (SSL_CTX *) arg;
r = bufferevent_openssl_socket_new (base,
-1,
SSL_new (ctx),
BUFFEREVENT_SSL_ACCEPTING,
BEV_OPT_CLOSE_ON_FREE);
return r;
}
static void server_setup_certs (SSL_CTX *ctx,
const char *certificate_chain,
const char *private_key)
{
printf ("Loading certificate chain from '%s'\n"
"and private key from '%s'\n",
certificate_chain, private_key);
if (1 != SSL_CTX_use_certificate_chain_file (ctx, certificate_chain))
die_most_horribly_from_openssl_error ("SSL_CTX_use_certificate_chain_file");
if (1 != SSL_CTX_use_PrivateKey_file (ctx, private_key, SSL_FILETYPE_PEM))
die_most_horribly_from_openssl_error ("SSL_CTX_use_PrivateKey_file");
if (1 != SSL_CTX_check_private_key (ctx))
die_most_horribly_from_openssl_error ("SSL_CTX_check_private_key");
}
static int
display_listen_sock(struct evhttp_bound_socket *handle)
{
struct sockaddr_storage ss;
evutil_socket_t fd;
ev_socklen_t socklen = sizeof(ss);
char addrbuf[128];
void *inaddr;
const char *addr;
int got_port = -1;
fd = evhttp_bound_socket_get_fd(handle);
memset(&ss, 0, sizeof(ss));
if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
perror("getsockname() failed");
return 1;
}
if (ss.ss_family == AF_INET) {
got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
} else if (ss.ss_family == AF_INET6) {
got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
}
else {
fprintf(stderr, "Weird address family %d\n",
ss.ss_family);
return 1;
}
addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
sizeof(addrbuf));
if (addr) {
printf("Listening on %s:%d\n", addr, got_port);
} else {
fprintf(stderr, "evutil_inet_ntop failed\n");
return 1;
}
return 0;
}
static int serve_some_http (void)
{
struct event_base *base;
struct evhttp *http;
struct evhttp_bound_socket *handle;
//创建event_base
base = event_base_new ();
if (! base)
{
fprintf (stderr, "Couldn't create an event_base: exiting\n");
return 1;
}
/* 创建一个 evhttp 句柄,去处理用户端的requests请求 */
http = evhttp_new (base);
if (! http)
{
fprintf (stderr, "couldn't create evhttp. Exiting.\n");
return 1;
}
/******************************************/
/* 创建SSL上下文环境 ,可以理解为 SSL句柄 */
SSL_CTX *ctx = SSL_CTX_new (SSLv23_server_method ());
SSL_CTX_set_options (ctx,
SSL_OP_SINGLE_DH_USE |
SSL_OP_SINGLE_ECDH_USE |
SSL_OP_NO_SSLv2);
/* Cheesily pick an elliptic curve to use with elliptic curve ciphersuites.
* We just hardcode a single curve which is reasonably decent.
* See http://www.mail-archive.com/[email protected]/msg30957.html */
EC_KEY *ecdh = EC_KEY_new_by_curve_name (NID_X9_62_prime256v1);
if (! ecdh)
die_most_horribly_from_openssl_error ("EC_KEY_new_by_curve_name");
if (1 != SSL_CTX_set_tmp_ecdh (ctx, ecdh))
die_most_horribly_from_openssl_error ("SSL_CTX_set_tmp_ecdh");
/* 选择服务器证书 和 服务器私钥. */
const char *certificate_chain = "server-certificate-chain.pem";
const char *private_key = "server-private-key.pem";
/* 设置服务器证书 和 服务器私钥 到
OPENSSL ctx上下文句柄中 */
server_setup_certs (ctx, certificate_chain, private_key);
/*
使我们创建好的evhttp句柄 支持 SSL加密
实际上,加密的动作和解密的动作都已经帮
我们自动完成,我们拿到的数据就已经解密之后的
设置用于为与给定evhttp对象的连接创建新的bufferevent的回调。
您可以使用它来覆盖默认的bufferevent类型,
例如,使此evhttp对象使用SSL缓冲区事件而不是未加密的事件。
新的缓冲区事件必须在未设置fd的情况下进行分配。
*/
evhttp_set_bevcb (http, bevcb, ctx);
/******************************************/
/* 设置http回调函数 */
//默认回调
//evhttp_set_gencb (http, send_document_cb, NULL);
//专属uri路径回调
evhttp_set_cb(http, "/login", login_cb, NULL);
/* 设置监听IP和端口 */
handle = evhttp_bind_socket_with_handle (http, "0.0.0.0", serverPort);
if (! handle)
{
fprintf (stderr, "couldn't bind to port %d. Exiting.\n",(int) serverPort);
return 1;
}
//监听socket
if (display_listen_sock(handle)) {
return 1;
}
/* 开始阻塞监听 (永久执行) */
event_base_dispatch (base);
return 0;
}
int main (int argc, char **argv)
{
/*OpenSSL 初始化 */
signal (SIGPIPE, SIG_IGN);
SSL_library_init ();
SSL_load_error_strings ();
OpenSSL_add_all_algorithms ();
printf ("Using OpenSSL version \"%s\"\nand libevent version \"%s\"\n",
SSLeay_version (SSLEAY_VERSION),
event_get_version ());
/* now run http server (never returns) */
return serve_some_http ();
}