Libevent 是一个用C语言编写的、轻量级的开源高性能网络库,主要有以下几个亮点:
(1)事件驱动( event-driven),高性能;
(2)轻量级,专注于网络,不如 ACE 那么臃肿庞大;
(3)源代码相当精炼、易读;
(4)跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;
(5)支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;
(6)支持 I/O,定时器和信号等事件;
(7)注册事件优先级。
Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。
组成和功能
libevent包含如下部分:
- evutil
它抽象了一组辅助功能,并屏蔽了操作系统平台的不同。
- event 及 event_base
这是libevent的核心,提供了一组不依赖于操作系统平台的基于事件的非阻塞IO操作。
- bufferevent
这个功能基于libevent的事件驱动,提供了非常方便的IO读写,它也可以有多个后端支持,以充分利用操作系统的功能,例如Windows的IOCP API。
- evbuffer
这个模块是bufferevent的基础,提供了高效的读写方法。
- evhttp
一个简单的HTTP client/server实现。
- evdns
一个简单的DNS client/server实现。
- evrpc
一个简单的RPC实现。
libevent是一个常用的网络库,下面就看看在windows下面编译测试的过程吧。
一 环境
系统:win8.1
编译器:VS2008
官方下载地址:http://libevent.org/
版本:2.0.22-stable
二 编译静态库
1 解压
把上面下载到libevent-2.0.22-stable.tar.gz解压,得到libevent-2.0.22-stable文件夹
2 添加宏定义
在libevent-2.0.22-stable文件夹下找到下面三个文件:
event_iocp.c
evthread_win32.c
listener.c
打开并在开头加上宏定义:
#define _WIN32_WINNT 0x0500
因为event_iocp.c里用到<winbase.h>头文件里的函数定义,如InitializeCriticalSectionAndSpinCount,
<windows.h>会包含<winbase.h>,而<winbase.h>这个头文件里这个函数是这样定义的:
#if (_WIN32_WINNT >= 0x0403)
WINBASEAPI
BOOL WINAPI
InitializeCriticalSectionAndSpinCount(
__out LPCRITICAL_SECTION lpCriticalSection,
__in DWORD dwSpinCount
);
WINBASEAPI
DWORD
WINAPI
SetCriticalSectionSpinCount(
__inout LPCRITICAL_SECTION lpCriticalSection,
__in DWORD dwSpinCount
);
#endif
所以要定义_WIN32_WINNT这个宏,而且值要大于0x0403。
如果没有这个宏或不满足条件,编译器会假定这个函数没有定义,
等到链接时再寻找它,这样这个函数的符号就假定返回一个int,
而显示标准库文件里这个函数不是返回int,所以在链接时就会找不到这个函数符号。
注意:宏一定要定义在#include <windows.h>之前,不然还是没有作用。
3 编译
使用vs的命令行工具,cd到libevent-2.0.22-stable目录,执行脚本makefile.nmake,命令如下:
nmake /f Makefile.nmake
这样就会生成三个静态库:
libevent_core.lib
libevent_extras.lib
libevent.lib
三 使用示例
1 新建项目
新建一个控制台“空”项目
2 拷贝文件
2.1 在项目目录下建一个libevent文件夹
2.2 在libevent中新建一个lib文件夹,将上面三个lib文件copy到该目录下。
2.3 在libevent中再新建一个include文件夹,
将libevent-2.0.22-stable\include下的文件和文件夹copy到该目录下,
将libevent-2.0.22-stable\WIN32-Code下的文件和文件夹copy到该目录下,
2个event2目录下的文件合并一起。
3 项目配置
VC++目录:
包含目录,添加刚刚新建的include目录
库目录,添加刚刚的lib目录;
C/C++:
代码生成-->运行库:
Debug模式下选:多线程调试 (/MTd),
Release下模式下选:多线程 (/MT)
连接器:
输入->附加依赖项:
ws2_32.lib
wsock32.lib
libevent.lib
libevent_core.lib
libevent_extras.lib
另外两个库ws2_32.lib和wsock32.lib是用来编译Windows网络相关的程序库。
4 测试代码
4.1 新建一个main.c文件
4.2 从libevent-2.0.22-stable\sample目录下拷贝time-test.c文件中的代码到main中,代码如下:
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#pragma comment (lib,”Advapi32.lib”)
#ifndef WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif
#include "event2/bufferevent.h"
#include "event2/buffer.h"
#include "event2/listener.h"
#include "event2/util.h"
#include "event2/event.h"
#include <WinSock2.h>
static const char MESSAGE[] = "Hello, World!\n";
static const int PORT = 9995;
static void conn_writecb(struct bufferevent *bev, void *user_data)
{
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == 0)
{
printf("flushed answer\n");
bufferevent_free(bev);
}
}
static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF)
{
printf("Connection closed.\n");
}
else if (events & BEV_EVENT_ERROR)
{
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
}
static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = (struct event_base *)user_data;
struct timeval delay = { 2, 0 };
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
event_base_loopexit(base, &delay);
}
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = (struct event_base *)user_data;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev)
{
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin;
#ifdef WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
base = event_base_new();
if (!base)
{
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener)
{
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
if (!signal_event || event_add(signal_event, NULL)<0)
{
fprintf(stderr, "Could not create/add a signal event!\n");
return 1;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
windows下socket通信需要加载Ws2_32.lib,这个大家都知道,至于为何需要Advapi32.lib,可以参考:LNK2019:无法解析的外部符号_imp_CryptAcquireContextA@20
然后将libevent自带的例子C:\Users\zhang\Desktop\libevent-2.0.22-stable\sample|h\ello-world.c拷贝到工程中并编译。
需要注意的是编译时有两处 C2440:“初始化”:无法从“void *”转换为“event_base *” 的错误,强制转换一下即可。
http server:
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
bool startHttpServer(const char* ip, int port, void (*cb)(struct evhttp_request *, void *), void *arg)
{
//创建event_base和evhttp
event_base* base = event_base_new();
evhttp* http_server = evhttp_new(base);
if (!http_server) {
return false;
}
//绑定到指定地址上
int ret = evhttp_bind_socket(http_server, ip, port & 0xFFFF);
if (ret != 0) {
return false;
}
//设置事件处理函数
evhttp_set_gencb(http_server, cb, arg);
//启动事件循环,当有http请求的时候会调用指定的回调
event_base_dispatch(base);
evhttp_free(http_server);
return true;
}
void MyHttpServerHandler(struct evhttp_request* req, void* arg)
{
//创建要使用的buffer对象
evbuffer* buf = evbuffer_new();
if (!buf) {
return;
}
//获取请求的URI
const char* uri = (char*)evhttp_request_get_uri(req);
//添加对应的HTTP代码
evbuffer_add_printf(buf,"Hello,world");
/*
evbuffer_add_printf(buf,"<head><title>MyHttpServer</title></head>");
evbuffer_add_printf(buf, "<body>");
//根据URI显示不同的页面
if (strcmp(uri, "/") == 0) {
evbuffer_add_printf(buf,"<p>Welcome to my http server</p>");
} else if (strcmp(uri, "/about") == 0) {
evbuffer_add_printf(buf,"<p>I love C++</p>");
}
evbuffer_add_printf(buf, "</body>");
evbuffer_add_printf(buf,"</html>");
*/
//回复给客户端
evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
int main(int argc, char** argv)
{
//Windows 平台套接字库的初始化
#ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
//启动服务在地址 127.0.0.1:9000 上
startHttpServer("127.0.0.1", 9000, MyHttpServerHandler, NULL);
#ifdef WIN32
WSACleanup();
#endif
return 0;
}
四 在线文档:
http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/
五 源码分析:
《Libevent源码分析》对Libevent这个网络库的一些源代码分析
http://blog.csdn.net/sparkliang/article/category/660506
http://blog.csdn.net/luotuo44/article/category/2435521
libevent学习之二:Windows7(Win7)下编译libevent
快速理解libevent内在机制
高性能网络编程(二):上一个10年,著名的C10K并发连接问题
libevent event_set函数与event_add函数解析 [SYM]