安装libevent
从官方仓库 下载源码,按照Cmake方式安装,可能会缺少一些依赖包
sudo apt install libssl-dev
sudo apt install libmbedtls-dev libmbedtls10
编译程序的时候发现还是找不到,将libevent目录下的include文件复制到/usr/include
仍然不行
直接 sudo apt-get install libevent-dev
哈哈哈
通过gdb core dump方法查看程序异常时的堆栈信息
当发生"内存越界"等错误时,会产生"Segmention fault",并生成Core文件
Core文件默认是没有打开的,可以通过 ulimit -a
查看,用 ulimit -c unlimited
设置(只在当前窗口有效)
再执行 gdb program_name core
就可以查看出错时的堆栈信息啦
参考通过gdb core dump方法查看程序异常时的堆栈信息
初识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 );
}
Output:
gcc -o libevent_test libevent_test.c -levent
./libevent_test
timeout
^CCaught an interrupt signal; exiting cleanly in two seconds...
Demo简单,但是却描述了LibEvent库的主要逻辑
libevent开发流程
零、socket编程
基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem?),例如内存:默认情况下每个线程需要占用2~8M的栈空间。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。
libevent大概是这样的:
默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。
一、创建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 传递给回调函数的参数
函数返回值:成功返回一个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 *);
属性获取示例
点击查看代码
#include<stdio.h>
#include<event2/event.h>
int main() {
// libevent version
printf("LIBEVENT_VERSION_NUMBER:%d\n", LIBEVENT_VERSION_NUMBER);
printf("version: %s\n", event_get_version());
struct event_base *base;
base = event_base_new();
// return current the method of multi-thread io
const char *x = event_base_get_method(base);
printf("METHOD:%s\n", x);
// return base support bit mask
int t = event_base_get_features(base);
if(t & EV_FEATURE_ET) { // 支持边沿触发的后端
printf("EV_FEATURE_ET\n");
}
if(t & EV_FEATURE_O1) { // 添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度的后端
printf("EV_FEATURE_O1\n");
}
if(t & EV_FEATURE_FDS) { // /要求支持任意文件描述符,而不仅仅是套接字的后端
printf("EV_FEATURE_FDS\n");
}
int y = event_base_dispatch(base);
event_base_free(base);
return 1;
}
Output:
点击查看代码
LIBEVENT_VERSION_NUMBER:33622016
version: 2.1.8-stable
METHOD:epoll
EV_FEATURE_ET
EV_FEATURE_O1
事件循环 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(5555); //服务器端口号
//创建服务端套接字
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",5555);
//创建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 simple_chat_room.c -o simple_chat_room -levent
./simple_chat_room
listen port:5555
METHOD:epoll
效果:一个客户端的消息会发送到其他所有客户端