一. 学习背景
libevent是一个很有名的开源库了, 在网络通讯方面应用很广。libevent是一个事件触发、异步事件的高性能开源网络库,在Windows、Linux、BSD、Mac OS这些平台上都适用。它内部使用了select、epoll等系统调用,作为底层网络库已被广泛应用。
二. libevent主要特点
1) 基于事件驱动, 高性能
2) 轻量级,专注于网络通信场景
3) 跨平台,支持Windows、Linux、BSD、Mac OS
4) 支持select、poll、epoll、kqueue等多路复用技术
三. 安装libevent
我在linux下安装的是2.1.6-beta版本(下载链接), 详细步骤如下:
wget "https://github.com/libevent/libevent/archive/release-2.1.6-beta.tar.gz"
tar
-
zxvf release-2.1.6-beta
.
tar
.
gz
cd libevent
-
release-2.1.6-beta
./
configure
make
make install
注: 2.1.6解压后有可能找不到configure文件, 我借用了2.0.22版本的configure文件, 运行后似乎能正常安装。
四. libevent的主要组件
1) evhttp: HTTP客户端/服务端的逻辑实现
2) evdns: DNS客户端/服务端的逻辑实现
3) evrps: RPC的逻辑实现
4) evutil: 隔离平台差异性的通用功能
5) event/evelibeventnt_base:
libevent的核心基础;
事件驱动的、平台无关的非阻塞IO抽象API, 通知程序进行套接字的读写操作;
提供超时处理、系统信号的检测等。
6) bufferevent:
基于事件的更上层封装;
除通知套接字可读/写外, 还提供了基于缓冲区的读写功能。
7) evbuffer: 在bufferevent层下实现的缓冲功能,并提供缓冲区下存取操作的对外方法
五. http server/client的C++示例
服务端server.cpp示例
#include <stdlib.h>
#include <stdio.h>
#include "event2/http.h"
#include "event2/event.h"
#include "event2/buffer.h"
#define BUF_LEN 4096
void request_receive_callback(struct evhttp_request* request, void* arg)
{
const struct evhttp_uri* evhttp_uri = evhttp_request_get_evhttp_uri(request);
char url[BUF_LEN];
evhttp_uri_join(const_cast<struct evhttp_uri*>(evhttp_uri), url, BUF_LEN);
printf("Accept request of url:%s\n", url);
// 创建用于响应请求的对象
struct evbuffer* evbuf = evbuffer_new();
if (!evbuf)
{
printf("Create evbuffer failed!\n");
return ;
}
// 填充响应内容
evbuffer_add_printf(evbuf, "This is response as request of %s", url);
// 发送响应体
evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
// 释放buffer
evbuffer_free(evbuf);
}
int main(int argc, char** argv)
{
if (argc != 2)
{
printf("usage:%s port\n", argv[0]);
return 1;
}
int port = atoi(argv[1]);
if (port <= 0)
{
printf("port invalid:%s\n", argv[1]);
return 1;
}
// 初始化event base对象
struct event_base* base = event_base_new();
if (!base)
{
printf("create event_base failed!\n");
return 1;
}
// 初始化 http 请求对象
struct evhttp* http = evhttp_new(base);
if (!http)
{
printf("create evhttp failed!\n");
return 1;
}
// 绑定监听地址与端口
if (evhttp_bind_socket(http, "0.0.0.0", port) != 0)
{
printf("Bind socket failed when listen port:%d\n", port);
return 1;
}
// 设置请求处理回调方法
evhttp_set_gencb(http, request_receive_callback, NULL);
// 派发事件
event_base_dispatch(base);
return 0;
}
客户端client.cpp示例
#include "event2/http.h"
#include "event2/http_struct.h"
#include "event2/event.h"
#include "event2/buffer.h"
#include "event2/dns.h"
#include "event2/thread.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/queue.h>
#include <event.h>
void remote_read_callback(struct evhttp_request* remote_rsp, void* arg)
{
event_base_loopexit((struct event_base*)arg, NULL);
}
// 打印响应头部信息
int read_header_callback(struct evhttp_request* remote_rsp, void* arg)
{
fprintf(stderr, "< HTTP/1.1 %d %s\n",
evhttp_request_get_response_code(remote_rsp),
evhttp_request_get_response_code_line(remote_rsp));
struct evkeyvalq* headers = evhttp_request_get_input_headers(remote_rsp);
struct evkeyval* header;
TAILQ_FOREACH(header, headers, next)
{
fprintf(stderr, "< %s: %s\n", header->key, header->value);
}
fprintf(stderr, "< \n");
return 0;
}
// 打印响应主题内容
void read_chunked_callback(struct evhttp_request* remote_rsp, void* arg)
{
char buf[4096];
struct evbuffer* evbuf = evhttp_request_get_input_buffer(remote_rsp);
int n = 0;
while ((n = evbuffer_remove(evbuf, buf, 4096)) > 0)
{
fwrite(buf, n, 1, stdout);
}
printf("\n");
}
void request_error_callback(enum evhttp_request_error error, void* arg)
{
fprintf(stderr, "Request failed.\n");
event_base_loopexit((struct event_base*)arg, NULL);
}
void connection_close_callback(struct evhttp_connection* connection, void* arg)
{
fprintf(stderr, "Connection has been closed.\n");
event_base_loopexit((struct event_base*)arg, NULL);
}
int main(int argc, char** argv)
{
if (argc != 2)
{
printf("Usage: %s url", argv[1]);
return 1;
}
char* url = argv[1];
struct evhttp_uri* uri = evhttp_uri_parse(url);
if (!uri)
{
fprintf(stderr, "Parse url failed!\n");
return 1;
}
const char* host = evhttp_uri_get_host(uri);
if (!host)
{
fprintf(stderr, "Parse host failed!\n");
return 1;
}
int port = evhttp_uri_get_port(uri);
if (port < 0) port = 80;
const char* request_url = url;
const char* path = evhttp_uri_get_path(uri);
if (path == NULL || strlen(path) == 0)
{
request_url = "/";
}
printf("url:%s host:%s port:%d path:%s request_url:%s\n", url, host, port, path, request_url);
// 初始化event_base
struct event_base* base = event_base_new();
if (!base)
{
fprintf(stderr, "Create event base failed!\n");
return 1;
}
// 初始化evdns_base_new
struct evdns_base* dnsbase = evdns_base_new(base, 1);
if (!dnsbase)
{
fprintf(stderr, "Create dns base failed!\n");
}
// 创建evhttp_request对象,设置返回状态响应的回调函数
struct evhttp_request* request = evhttp_request_new(remote_read_callback, base);
evhttp_request_set_header_cb(request, read_header_callback);
evhttp_request_set_chunked_cb(request, read_chunked_callback);
evhttp_request_set_error_cb(request, request_error_callback);
struct evhttp_connection* connection = evhttp_connection_base_new(base, dnsbase, host, port);
if (!connection)
{
fprintf(stderr, "Create evhttp connection failed!\n");
return 1;
}
// 创建连接对象成功后, 设置关闭回调函数
evhttp_connection_set_closecb(connection, connection_close_callback, base);
// 添加http头部
evhttp_add_header(evhttp_request_get_output_headers(request), "Host", host);
// 发起http请求
evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);
// 派发事件
event_base_dispatch(base);
return 0;
}
编译并运行server
g++ server.cpp -L/usr/local/lib -levent -o httpServer
./httpServer 7000
编译并运行client
g++ client.cpp -L/usr/local/lib -levent -o httpClient
./httpClient http://127.0.0.1:7000/
六. 小结
如果用python tornado框架来实现这个功能, 可能就只需很简洁的几行代码。所以关于技术选型的问题, 还是要考虑开发效率与程序性能这二者之间的综合考虑。