使用libevent实现基于event的tcp服务器

Libevent 介绍Libevent 是一个用于编写快速可移植非阻塞 IO 的库。其设计目标为:

可移植性

使用 Libevent 编写的程序应该可以在 Libevent 支持的所有平台上运行。即使没有真正好的方法来做非阻塞 IO,Libevent 也应该支持马马虎虎的方法,这样你的程序才能在受限的环境中运行。

速度

Libevent 尝试在每个平台上使用最快的可用非阻塞 IO 实现,并且不会像这样做那样引入太多开销。

可扩展性

Libevent 被设计为即使在需要有数万个活动套接字的程序中也能很好地工作。

方便

只要有可能,使用 Libevent 编写程序的最自然的方式应该是稳定的、可移植的方式。

 Libevent 的核心思想:

在linux系统上, 其实质就是epoll反应堆.

libevent是事件驱动, epoll反应堆也是事件驱动, 当要监测的事件发生的时候, 就会调用事件对应的回调函数, 执行相应操作. 特别提醒: 事件回调函数是由用户开发的, 但是不是由用户显示去调用的, 而是由libevent去调用的.

Libevent 的相关API介绍:

struct event_base *event_base_new(void)
函数说明:event_base_new() 函数分配并返回具有默认设置的新事件库(event_base结构)。它检查环境变量并返回一个指向新 event_base 的指针。如果有错误,则返回 NULL。
void event_base_free( struct event_base *base)
函数说明:完成event_base后,释放event_base的指针。
int event_reinit( struct event_base *base)
函数说明:在调用 fork() 创建子进程之后,并非所有事件后端都保持干净。因此,如果程序使用 fork() 或相关系统调用来启动一个新进程,并且希望在子进程中继续使用 event_base,则可能需要重新初始化它。成功返回0, 失败返回-1。

对于不同系统而言, event_base就是调用不同的多路IO接口去判断事件是否已经被激活, 对于linux系统而言, 核心调用的就是epoll, 同时支持poll和select.
const  char **event_get_supported_methods( void )
函数说明:event_get_supported_methods() 函数返回一个指向此版本 Libevent 中支持的方法名称数组的指针。数组中的最后一个元素为 NULL。
const  char *event_base_get_method( const  struct event_base *base)
函数说明:获得当前base节点使用的多路IO方法,调用返回 event_base 使用的实际方法的名称。 
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)
函数说明:如果希望活动的事件循环在删除之前停止运行,可以调用两个略有不同的函数。event_base_loopexit() 函数告诉 event_base 在给定时间过去后停止循环。如果tv参数为 NULL,则 event_base 立即停止循环。
event_base_loopbreak() 函数告诉 event_base 立即退出其循环。


struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg)
typedef void (*event_callback_fn)(evutil_socket_t, short, void *)//回调函数
void event_free(struct event *event)
函数说明:event_new() 函数尝试分配和构造一个新事件以用于basewhat参数是上面列出的一组标志。(它们的语义在下面描述)如果fd是非负数,它是我们将观察读取或写入事件的文件描述符。当事件处于活动状态时,Libevent 将调用提供的cb函数,将其作为参数传递:文件描述符fd 、触发的所有事件的位域以及构造函数时为arg传递的值。如果出现内部错误或无效参数,event_new() 将返回 NULL。所有新事件都已初始化且非挂起。要使事件挂起,请调用 event_add()(如下所述)。要解除分配事件,请调用 event_free()。
参数what指出要监听的一组事件:
                    #define EV_TIMEOUT 0x01  //超时事件
                    #define EV_READ 0x02     //读事件
                    #define EV_WRITE 0x04    //写事件
                    #define EV_SIGNAL 0x08   //信号事件
                    #define EV_PERSIST 0x10   //周期性触发
                    #define EV_ET 0x20        //边缘触发

int event_add( struct event *ev, const  struct timeval *tv)
函数说明:对非挂起事件调用 event_add 使其在其配置的基础中挂起。该函数在成功时返回 0,在失败时返回 -1。如果tv为 NULL,则添加事件而没有超时。否则,tv是以秒和微秒为单位的超时大小。
int event_del(struct event *ev)
函数说明:对已初始化的事件调用 event_del 使其处于非挂起和非活动状态。如果事件未挂起或未激活,则没有效果。返回值为 0 表示成功,-1 表示失败。

实现基于event的tcp服务器总体步骤:

1 搭建服务器的固定三步:

  • --创建socket
  • --绑定bind
  • --监听listen

2 调用event_base_new函数创建event_base节点.

3 创建要监听的事件event, 主要就是监听事件和读数据的事件.

        设置好监听事件的回调函数,然后event_add上树---->有新的连接, 则 调用accept接受新的连接---->将这个新的连接设置好回调函数(一般是设置读事件), 然后继续event_add上树, 若有客户端关闭连接则从树上摘除该事件节点.

4 调用event_base_dispatch进入循环等待事件的发生.

5 释放资源:调用event_base_free释放根节点和调用event_free释放事件节点.

实现如下:

//编写libevent服务端
#include <iostream>
#include<stdlib.h>
#include<ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <event2/event.h>
using namespace std;
#define MAX 1000
struct evenfd
{
     evutil_socket_t fd;
     struct event *ev;
}event[MAX];

void init_ev_fd ()
{
	int i=0;
	for(i=0;i<MAX;i++)
		{
			event[i].fd=-1;
			event[i].ev=nullptr;
		}
}
void setEventFd(evutil_socket_t fd,struct event *ev)
{
	int i=0;
	//查找存放的位置
	for(i=0;i<MAX;i++)
	{
		if(event[i].fd==-1)
			{
				break;
			}
	}
	//找不到合适的存放位置,退出进程
	if(i==MAX)
		{
			exit(1);
		}
		event[i].fd=fd;
		event[i].ev=ev;
}

int findEv(int fd)
{
	int i=0;
	for(i=0;i<MAX;i++)
	{
		if(event[i].fd==fd)
			{
				break;
			}
	}
	if(i==MAX)
		{
			cout<<"not find fd"<<endl;
			exit(1);
		}
		return i;
	
}
//struct event *readev = NULL;
//通信文件描述符对应的事件的回调函数
void readcb(evutil_socket_t cfd, short events, void *arg)
{
	int n;
	char buf[1024];
	
	memset(buf, 0x00, sizeof(buf));
	int num=findEv(cfd);
	n = read(cfd, buf, sizeof(buf));
	if(n<=0)
	{
		cout<<"The client closes the connection, n=["<<n<<"]-byte"<<endl;
		
		//关闭cfd
		close(cfd);
		//将cfd对应的事件从base下删除
		event_del(event[num].ev);
		//释放event节点
		event_free(event[num].ev);
		//重置数组
		event[num].fd=-1;
		event[num].ev=nullptr;
	}
	for (int i=0;i<n;i++)
	{
		buf[i]=toupper(buf[i]);
	}
	write(cfd, buf, n);
}
//typedef void (*event_callback_fn)(evutil_socket_t fd, 
//			short events, void *arg);
//监听文件描述符对应的回调函数
void conncb(evutil_socket_t lfd, short events, void *arg)
{
	struct event_base *base = (struct event_base *)arg;
	//接受新的客户端连接
	int cfd = accept(lfd, NULL, NULL);
	if(cfd>0)
	{
		//创建cfd对应的事件节点
		struct event*readev = event_new(base, cfd, EV_READ|EV_PERSIST, readcb, NULL);
		
		//将cfd对应的事件节点上base
		event_add(readev, NULL);
		setEventFd(cfd,readev);
	}
}
int main()
{
	
	init_ev_fd();
	//创建socket
	int fd = socket(AF_INET, SOCK_STREAM, 0);

	//设置端口复用
	int opt = 1;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	serv.sin_port = htons(8888);
	serv.sin_family = AF_INET;
	bind(fd, (struct sockaddr*)&serv, sizeof(serv));

	//监听
	listen(fd, 120);
	
	//创建地基节点
	struct event_base *base = event_base_new();
	if(base==NULL)
	{
		return -1;
	}
	
	//创建监听文件描述符对应的事件节点
	//struct event *event_new(struct event_base *base, evutil_socket_t fd, 
	  //short events, event_callback_fn cb, void *arg);
	struct event *event = event_new(base, fd, EV_READ | EV_PERSIST, conncb, base);
	if(event==NULL)
	{
		event_base_free(base);
		return -1;
	}
	event_add(event, NULL);
	
	//进入循环
	event_base_dispatch(base);
	
	//释放地基节点
	event_base_free(base);
	
	close(fd);
	
	return 0;
}

执行服务端代码后,在其他终端窗口上同时使用nc命令进行测试: nc 127.1 8888, 多开几个终端窗口使用nc命令进行测试. 

测试结果如下:

 结果表明:服务端程序能同时接受多个客户端请求,实现了IO多路复用,提高了通信效率。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于LibeventTCP服务器可以通过以下几个步骤实现: 1. 引入Libevent库:在代码中添加#include <event2/event.h>头文件,并在链接选项中添加-l event选项来引入Libevent库。 2. 创建event_base:使用event_base_new()函数创建一个event_base对象,这个对象将被用来管理所有的事件。 3. 创建监听socket:使用socket()函数创建一个TCP监听socket,并设置为非阻塞模式。然后使用event_new()函数创建一个event对象,用于监听该socket上的事件。 4. 绑定监听socket:使用bind()函数将监听socket绑定到指定的端口上。 5. 设置监听socket监听状态:使用listen()函数将监听socket设置为监听状态,并将event对象与监听socket绑定起来。 6. 处理事件循环:使用event_base_dispatch()函数进入事件循环,等待事件的发生。 7. 处理连接事件:当有新的连接请求到达监听socket时,Libevent会触发EV_READ事件,此时可以使用accept()函数接收连接,并使用event_new()函数创建一个event对象,用于监听连接socket上的事件。 8. 处理读写事件:当有数据到达连接socket时,Libevent会触发EV_READ事件,此时可以使用recv()函数接收数据,并使用event_add()函数将event对象加入到event_base中,监听连接socket上的EV_WRITE事件。当可以向连接socket写入数据时,Libevent会触发EV_WRITE事件,此时可以使用send()函数向连接socket写入数据。 以上就是基于LibeventTCP服务器的基本实现步骤。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值