文章目录
Libevent
libevent 是一个轻量级的事件触发的网络库。它适用于windows、linux、bsd等多种平台,它是跨平台的。libevent是c语言编写的一个开源的网络库。
获取源码
Github: https://github.com/libevent/libevent
libevent安装(ubantu16.04安装)
su root #必须为root用户下,否则make install失败
wget http://monkey.org/~provos/libevent-1.4.14b-stable.tar.gz
tar xzf libevent-1.4.14b-stable.tar.gz
cd libevent-1.4.14b-stable
./configure --prefix=/opt/libevent
make
make install
wget失败直接去官网下载Release版本,地址:https://github.com/libevent/libevent/releases
库安装完成之后,查看库是否安装成功
➜ Desktop ls -al /opt/libevent
total 24
drwxr-xr-x 6 root root 4096 Dec 4 21:48 .
drwxr-xr-x 4 root root 4096 Dec 4 21:48 ..
drwxr-xr-x 2 root root 4096 Dec 4 21:48 bin
drwxr-xr-x 2 root root 4096 Dec 4 21:48 include
drwxr-xr-x 2 root root 4096 Dec 4 21:48 lib
drwxr-xr-x 3 root root 4096 Dec 4 21:48 share
测试示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
int main() {
puts("init a event_base!");
struct event_base *base; //定义一个event_base
base = event_base_new(); //初始化一个event_base
const char *x = event_base_get_method(base); //查看用了哪个IO多路复用模型,linux一下用epoll
printf("METHOD:%s\n", x);
int y = event_base_dispatch(base); //事件循环。因为我们这边没有注册事件,所以会直接退出
event_base_free(base); //销毁libevent
return 1;
}
➜ Desktop gcc -o event_base event_base.c -levent
event_base.c:11:28: fatal error: event2/event.h: No such file or directory
#include <event2/event.h>
^
compilation terminated.
报错解决方案:安装libevent-dev
sudo apt-get install libevent-dev
[sudo] password for deroy:
E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?
无法解锁xxx,不用管网上的kill
所有进程
,直接rm /var/lib/dpkg/lock-frontend
➜ Desktop sudo rm /var/lib/dpkg/lock-frontend
➜ Desktop sudo apt-get install libevent-dev
E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)
E: Unable to lock the administration directory (/var/lib/dpkg/), is another process using it?
有报错?继续rm /var/lib/dpkg/lock
➜ Desktop sudo apt-get install libevent-dev
Reading package lists... Done
...
...
Processing triggers for libc-bin (2.23-0ubuntu11) ...
大功告成
➜ Desktop gcc -o event_base event_base.c -levent
➜ Desktop ./event_base
init a event_base!
METHOD:epoll
测试成功:这只是一个简单的demo
初识libevent
libevent提供的simple有点劝退,这里用《Linux高性能服务器编程》中Libevent源码分析里面的demo
#include <sys/signal.h>
#include <event.h>
void signal_cb( int fd, short event, void* argc )
{
struct event_base* base =(struct event_base*)argc;
struct timeval delay = { 2, 0 };
printf( "Caught an interrupt signal; exiting cleanly in two seconds...\n" );
event_base_loopexit( base, &delay );
}
void timeout_cb( int fd, short event, void* argc )
{
printf( "timeout\n" );
}
int main()
{
struct event_base* base = event_init();
struct event* signal_event = evsignal_new( base, SIGINT, signal_cb, base );
event_add( signal_event, NULL );
struct timeval tv = { 1, 0 };
struct event* timeout_event = evtimer_new( base, timeout_cb, NULL );
event_add( timeout_event, &tv );
event_base_dispatch( base );
event_free( timeout_event );
event_free( signal_event );
event_base_free( base );
}
编译运行
gcc -o libevent_test libevent_test.c -levent
./libevent_test
timeout
^CCaught an interrupt signal; exiting cleanly in two seconds...
Demo简单,但是却描述了LibEvent库的主要逻辑
libevent开发流程
一、创建event_base对象
调用event_init
函数创建event_base
对象。一个event_base
相当于一个rector
实例.
struct event_base* base = event_init();
二、创建具体的事件处理器
创建具体的事件处理器,并设置从属的Rector实例,evsignal_new
和evtimer_new
分别用于创建信号事件处理器和定时事件处理器。
struct event* signal_event = evsignal_new( base, SIGINT, signal_cb, base );//创建信号事件处理器并设置从属的Rector实例
struct event* timeout_event = evtimer_new( base, timeout_cb, NULL );//创建定时事件处理器并设置从属的Rector实例
这两个函数有一个统一入口函数event_new
struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fn cb,void* arg);
参数1:base 指定新创建的事件处理器从属的 Rector,
参数2:fd指定与该事件处理器关联的句柄
参数3:events需要监控的事件
EV_TIMEOUT 0x01 定时事件
EV_READ 0x02 可读事件
EV_WRITE 0x04 可写事件
EV_SIGNAL 0x08 信号事件
EV_PERSIST 0x10 永久事件
/*边沿触发事件,需要I/O复用系统调用支持*/
EV_ET 0x20
参数4:callback指定目标事件的回调函数
参数5:callback_arg
参数6:传递给回调函数的参数
函数返回值:成功返回一个event类型对象,
三、将事件处理器添加到注册事件队列
调用event_add
函数将事件处理器添加到注册事件队列中去
四、执行事件循环
调用event_base_dispatch
函数执行事件循环
五、释放资源
事件循环之后free掉资源
libevent里面的一些函数
设置端口重用
evutil_make_listen_socket_reuseable(server_socketfd);
设置无阻赛 实体在evutil.c中,是对fcntl操作
evutil_make_socket_nonblocking(server_socketfd);
返回一个字符串,标识内核事件机制(kqueue的,epoll的,等等)
const char *x = event_base_get_method(base); //查看用了哪个IO多路复用模型,linux一下用epoll
程序进入无限循环,等待就绪事件并执行事件处理
int event_base_dispatch(struct event_base *);
函数声明 | 功能 |
---|---|
const char **event_get_supported_methods(void); | 返回一个指针 ,指向 libevent 支持的IO多路方法名字数组,这个数组的最后一个元素是NULL |
const char *event_base_get_method(const struct event_base *base); | 返回 event_base 正在使用的IO多路方法 |
enum event_method_feature event_base_get_features(const struct event_base *base); | 返回 event_base 支持的特征的比特掩码 |
属性获取示例
#include <event2/event.h>
#include <stdio.h>
int main()
{
//libevent的版本
printf("Starting Libevent %s. Available methods are:\n", event_get_version());
//检查支持的IO多路方法
const char **methods = event_get_supported_methods();
for (int i=0; methods[i] != NULL; ++i) {
printf(" %s\n", methods[i]);
}
struct event_base *base = event_base_new();
enum event_method_feature f;
if (!base)
{
puts("Couldn't get an event_base!");
}
else
{
//返回 event_base 正在使用的IO多路方法
printf("Using Libevent with backend method :%s\n",event_base_get_method(base));
//返回 event_base 支持的特征的比特掩码
f = event_base_get_features(base);
if ((f & EV_FEATURE_ET)) //支持边沿触发的后端
printf(" Edge-triggered events are supported.\n");
if ((f & EV_FEATURE_O1)) //添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度的后端
printf(" O(1) event notification is supported.\n");
if ((f & EV_FEATURE_FDS)) //要求支持任意文件描述符,而不仅仅是套接字的后端
printf(" All FD types are supported.\n");
}
}
Starting Libevent 2.0.21-stable. Available methods are:
epoll
poll
select
Using Libevent with backend method :epoll
Edge-triggered events are supported.
O(1) event notification is supported.
事件循环 event_loop
一旦创建好事件根基
event_base
,并且在根基上安插好事件之后,需要对事件循环监控(换句话说就是等待事件的到来,触发事件的回调函数),有两种方式可以达到上面描述的功能,即:event_base_dispatch
和event_base_loop
int event_base_dispatch(struct event_base *); //程序进入无限循环,等待就绪事件并执行事件处理
int event_base_loop(struct event_base *base, int flags); //
参数flags
- EVLOOP_ONCE:相当于epoll_wait阻塞方式&&只调用一次 ⇒ 当没有事件到来时,程序将一直阻塞在event_base_loop函数;直到有任意一个事件到来时,程序才不会阻塞在event_base_loop,将会继续向下执行。
- EVLOOP_NONBLOCK:相当于epoll_wait非阻塞方式&&只调用一次 ⇒ 即使没有事件到来,程序也不会阻塞在event_base_loop
- EVLOOP_NO_EXIT_ON_EMPTY:等价于event_base_dispatch ⇒ 将一直循环监控事件 ⇒ 直到没有已经注册的事件 || 调用了event_base_loopbreak()或 event_base_loopexit()为止
事件循环的退出的情况
引起循环退出的情况:
- event_base中没有事件了
- 调用event_base_loopbreak 事件循环会停止 (立即停止)
- 调用event_base_loopexit (等待所有事件结束后停止)
- 程序错误
libevent简易聊天室
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
int cli_socket[1024];
int cli_index = 0;
//读取客户端
void do_read(evutil_socket_t fd, short event, void *arg) {
//继续等待接收数据
char buf[1024]; //数据传送的缓冲区
int len;
if ((len = recv(fd, buf, 1024, 0)) > 0) {
buf[len] = '\0';
printf("%s", buf);
for(int index = 0;index<cli_index;index++){
if (send(cli_socket[index], buf, len, 0) < 0) { //将接受到的数据写回客户端
perror("write");
}
}
}
if(len == 0)
{
printf("fd:%d close\n",fd);
close(fd);
}
if(len <0)
{
perror("recv");
}
}
//回调函数,用于监听连接进来的客户端socket
void do_accept(evutil_socket_t fd, short event, void *arg) {
int client_socketfd;//客户端套接字
struct sockaddr_in client_addr; //客户端网络地址结构体
int in_size = sizeof(struct sockaddr_in);
//客户端socket
client_socketfd = accept(fd, (struct sockaddr *) &client_addr, &in_size); //等待接受请求,这边是阻塞式的
if (client_socketfd < 0) {
puts("accpet error");
exit(1);
}
cli_socket[cli_index++] = client_socketfd;
printf("Connect from %s:%u ...!\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
//类型转换
struct event_base *base_ev = (struct event_base *) arg;
//socket发送欢迎信息
char * msg = "Welcome to Libevent socket\n";
int size = send(client_socketfd, msg, strlen(msg), 0);
//创建一个事件,这个事件主要用于监听和读取客户端传递过来的数据
//持久类型,并且将base_ev传递到do_read回调函数中去
struct event *ev;
ev = event_new(base_ev, client_socketfd, EV_TIMEOUT|EV_READ|EV_PERSIST, do_read, base_ev);
event_add(ev, NULL);
}
//入口主函数
int main() {
int server_socketfd; //服务端socket
struct sockaddr_in server_addr; //服务器网络地址结构体
memset(&server_addr,0,sizeof(server_addr)); //数据初始化--清零
server_addr.sin_family = AF_INET; //设置为IP通信
server_addr.sin_addr.s_addr = INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
server_addr.sin_port = htons(8001); //服务器端口号
//创建服务端套接字
server_socketfd = socket(PF_INET,SOCK_STREAM,0);
if (server_socketfd < 0) {
puts("socket error");
return 0;
}
evutil_make_listen_socket_reuseable(server_socketfd); //设置端口重用
evutil_make_socket_nonblocking(server_socketfd); //设置无阻赛
//绑定IP
if (bind(server_socketfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))<0) {
puts("bind error");
return 0;
}
//监听,监听队列长度 5
listen(server_socketfd, 10);
printf("listen port:%d\n",8001);
//创建event_base 事件的集合,多线程的话 每个线程都要初始化一个event_base
struct event_base *base_ev;
base_ev = event_base_new();
const char *x = event_base_get_method(base_ev); //获取IO多路复用的模型,linux一般为epoll
printf("METHOD:%s\n", x);
//创建一个事件,类型为持久性EV_PERSIST,回调函数为do_accept(主要用于监听连接进来的客户端)
//将base_ev传递到do_accept中的arg参数
struct event *ev;
ev = event_new(base_ev, server_socketfd, EV_TIMEOUT|EV_READ|EV_PERSIST, do_accept, base_ev);
//注册事件,使事件处于 pending的等待状态
event_add(ev, NULL);
//事件循环
event_base_dispatch(base_ev);
//销毁event_base
event_base_free(base_ev);
return 1;
}
gcc -o socket socket.c -levent
./socket
listen port:8001
METHOD:epoll
nc 0.0.0.0 8001