基于Libevent的简易聊天室设计----从0开始

来源:微信公众号「编程学习基地」

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_newevtimer_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_dispatchevent_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

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeRoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值