libevent的基本使用

前言

libevent是一个用C语言编写的,轻量级的开源高性能事件通知库,主要有以下几个特点:事件驱动,高性能,轻量级,专注于网络,支持多平台,支持多种I/O复用技术,select,poll,epoll等,支持I/O定时器和信号事件,注册事件优先级

什么叫I/0多路复用

下面举一个例子,模拟一个tcp服务器处理30个客户socket。
假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:

  1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。
    这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。
  2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
  3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
  4. 这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。

转自 链接:https://www.zhihu.com/question/28594409/answer/52835876
在这里插入图片描述

其实I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。
在同一个线程里面,通过拨动开关的方式,来同时传输多个I/0流,就是epoll会将他们监视起来,然后就像波动开关一样,谁有数据就拨向谁

libevent的地基event_base

在使用libevent这个库的时候,就像我们盖房子一样,需要一个地基,我们这个接口就是来完成这个工作的,在他的基础上会有的一个事件集合去检查哪一个事件是激活的

struct event_base * event_base_new(void);
申请到的指针可以通过event_base_free释放
event_base_free(struct event_base *)

如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:
int event_reinit(struct event_base *base);

等待事件产生,循环等待event_loop

他类似于while(1)的功能,去循环监视事件的发生
int event_base_dispatch(struct event_base *base);
调用该函数,相当于没有设置标志位的event_base_loop。程序将会一直运行,直到没有需要检测的事件了,或者被结束循环的api终止。

int event_base_dispatch(struct event_base *base);
调用该函数,相当于没有设置标志位的event_base_loop。程序将会一直运行,直到
没有需要检测的事件了,或者被结束循环的api终止。

int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
struct timeval {
	long    tv_sec;                    
	long    tv_usec;
  两个函数的区别是如果正在执行激活事件的回调函数,那么event_base_loopexit
  将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间
  后立即结束循环),而event_base_loopbreak会立即终止循环。            
事件驱动-event

在这里插入图片描述
我们来说一下上面的图,主要是几个状态,

  1. 在创建event *指针的时候是无效的
  2. 未决态,相当于创建了事件,但是事件没用处于被监听的状态
  3. 未决,就是事件已经监视,还没有发生
  4. 激活,事件已经发生
struct event *event_new(struct event_base *base, evutil_socket_t fd,
       short events, event_callback_fn cb, void *arg);
event_new负责新创建event结构指针,同时指定对应的地基base,还有对应的文件描述符,
事件,以及回调函数和回调函数的参数。参数说明:
base 对应的根节点
fd 要监听的文件描述符
events 要监听的事件
#define  EV_TIMEOUT         0x01   //超时事件
#define  EV_READ                  0x02 //读事件
#define  EV_WRITE                0x04  //写事件
#define  EV_SIGNAL              0x08     //信号事件
#define  EV_PERSIST              0x10   //周期性触发
#define  EV_ET                        0x20 //边缘触发,如果底层模型支持
cb 回调函数,原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
arg 回调函数的参数
7.	int event_add(struct event *ev, const struct timeval *timeout);
将非未决态事件转为未决态,相当于调用epoll_ctl函数,开始监听事件是否产生。
参数说明:
	Ev 就是前面event_new创建的事件
Timeout 限时等待事件的产生,也可以设置为NULL,没有限时。
8.	int event_del(struct event *ev);
将事件从未决态变为非未决态,相当于epoll的下树(epoll_ctl调用EPOLL_CTL_DEL操作)操作。
9.	void event_free(struct event *ev);
释放event_new申请的event节点。

服务器端简单的实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <event2/event.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

struct event * connev[10];
void readcb (evutil_socket_t fd,short events,void *arg)
{
	int num=(int) arg;
	int n;
	char buf[1024];
	bzero(buf,sizeof(buf));
	n=read(fd,buf,sizeof(buf));
	if (n<=0)
	{
		close(fd);
		event_del(connev[num]);
		return;
	}
	send(fd,buf,n,0);
}
void func(evutil_socket_t fd,short events,void *arg)
{
	int num=-1;
	struct event_base *base=(struct event_base *) arg;
	struct sockaddr_in addr;
	int len=sizeof(addr);
	//阻塞接受
	int new=accept(fd,(struct sockaddr*)&addr,&len);
	num++;
	if (new>0)
	{
		connev[num] =event_new (base,new,EV_READ|EV_PERSIST,readcb,(int*)num);
		if (connev ==NULL)
		{
			exit(0);
		}
		event_add (connev[num],NULL);
		puts("3");
	}
}

int main(int argc, char *argv[])
{
	int sfd=socket(AF_INET,SOCK_STREAM,0);
	if (sfd<0)
	{
		perror ("socket");
		exit(-1);
	}
	int opt=1;
	setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	//ip配置
	struct sockaddr_in ip;
	ip.sin_family=AF_INET;
	ip.sin_port =htons(10088);
	ip.sin_addr.s_addr =htonl (INADDR_ANY);
	socklen_t len=sizeof(ip);
	if (bind(sfd,(struct sockaddr*)&ip,len))
	{
		perror ("bind");
		exit (-1);
	}
	listen(sfd,5);

	//获取地基
	struct event_base *base =event_base_new();
	if (base==NULL)
	{
		printf("error base\n");
		exit(-1);
	}
	struct event *ev=event_new(base,sfd,EV_READ|EV_PERSIST,func,base);
	if (ev==NULL)
	{
		printf ("event\n");
		exit(-1);
	}
	//将事件添加到地基上,并调用内部的回调函数
	event_add(ev,NULL);
	//进入循环
	event_base_dispatch(base);

	//free
	event_base_free(base);
	event_free(ev);
	close(sfd);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值