使用libevent实现一个简单的tcp服务端

一、概述

  1.特点:

     1.事件驱动、高性能、轻量级、专注于网络

     2.源代码精炼、易读

     3.跨平台

     4.支持多种I/O多路复用技术,如epoll 、poll 、select等

     5.支持I/O和信号等事件

  2.使用libevent 函数之前需要分配一个或者多个 event_base 结构体, 每个event_base结构体持有一个事件集合, 可以检测以确定哪个事件是激活的, event_base结构相当于epoll红黑树的树根节点, 每个event_base都有一种用于检测某种事件已经就绪的 “方法”(回调函数)

通常情况下可以通过event_base_new函数获得event_base结构。

  3.相关函数

1 struct event_base *event_base_new(void);    
  函数说明: 获得event_base结构
  参数说明: 无
  返回值: 
      成功返回event_base结构体指针;
      失败返回NULL;

2 void event_base_free(struct event_base *);   
    函数说明: 释放event_base指针

3 int event_reinit(struct event_base *base);  
    函数说明: 如果有子进程, 且子进程也要使用base, 则子进程需要对event_base重新初始化, 此时需要调用event_reinit函数.
  函数参数: 由event_base_new返回的执行event_base结构的指针
  返回值: 成功返回0, 失败返回-1

对于不同系统而言, event_base就是调用不同的多路IO接口去判断事件是否已经被激活, 对于linux系统而言, 核心调用的就是epoll, 同时支持poll和select.

查看libevent支持的后端的方法有哪些:
const char **event_get_supported_methods(void);
函数说明: 获得当前系统(或者称为平台)支持的方法有哪些
参数: 无
返回值: 返回二维数组, 类似与main函数的第二个参数**argv.

const char * event_base_get_method(const struct event_base *base);
函数说明: 获得当前base节点使用的多路io方法
函数参数: event_base结构的base指针.
返回值: 获得当前base节点使用的多路io方法的指针

libevent在地基打好之后, 需要等待事件的产生, 也就是等待事件被激活, 所以程序不能退出, 对于epoll来说, 
我们需要自己控制循环, 而在libevent中也给我们提供了API接口, 类似while(1)的功能. 
int event_base_dispatch(struct event_base *base);   //
函数说明: 进入循环等待事件
参数说明:由event_base_new函数返回的指向event_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会立即终止循环。

typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
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    //边缘触发, 如果底层模型支持设置                    则有效, 若不支持则无效.
      
若要想设置持续的读事件则: EV_READ | EV_PERSIST
cb 回调函数, 原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
注意: 回调函数的参数就对应于event_new函数的fd, event和arg

#define evsignal_new(b, x, cb, arg)             \                                                                    
      event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))

int event_add(struct event *ev, const struct timeval *timeout);
函数说明: 将非未决态事件转为未决态, 相当于调用epoll_ctl函数(EPOLL_CTL_ADD), 开始监听事件是否产生, 相当于epoll的上树操作.
参数说明:
  ev: 调用event_new创建的事件
timeout: 限时等待事件的产生, 也可以设置为NULL, 没有限时。

int event_del(struct event *ev);
函数说明: 将事件从未决态变为非未决态, 相当于epoll的下树(epoll_ctl调用      EPOLL_CTL_DEL操作)操作。
参数说明: ev指的是由event_new创建的事件.

void event_free(struct event *ev);
函数说明: 释放由event_new申请的event节点。

 

二、示例代码

//编写libevent服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event2/event.h>

struct event *connev = NULL;
void readcb(evutil_socket_t fd,short events,void *arg){
    int n;
    char buf[1024];
    memset(buf,0x00,sizeof(buf));
    n = read(fd,buf,sizeof(buf));
    if(n<=0){
        close(fd);
        //将通讯文件描述符对应的事件从base地基上删除
        event_del(connev);
    }else{
        write(fd,buf,n);
    }
}


//创建连接回调事件
void conncb(evutil_socket_t fd,short events ,void *arg){
    struct event_base *base = (struct event_base*)arg;

    //接收新的客户端连接
    int cfd = accept(fd,NULL,NULL);
    if(cfd>0){
        //创建通信文件描述对应的事件并设置回调函数为readcb
        connev = event_new(base,cfd,EV_READ|EV_PERSIST,readcb,NULL);
        if(connev==NULL){
            //退出循环
            event_base_loopexit(base,NULL);
        }
        //将通信文件描述符对应的事件上event_base地基
        event_add(connev,NULL);

    }
}


int main(int argc, char const *argv[])
{
    
    //1.创建socket
    int lfd = socket(AF_INET,SOCK_STREAM,0);

    //2.设置端口复用
    int opt;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    //3.绑定
    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(lfd,(struct sockaddr *)&serv,sizeof(serv));

    //4.监听
    listen(lfd,128);

    //5.创建地基
    struct event_base *base = event_base_new();
    if(base==NULL){
        perror("event_base_new error");
        return -1;
    }

    //6.创建文件描述符对应的事件
    struct event *ev = event_new(base,lfd,EV_READ|EV_PERSIST,conncb,base);
    if(ev==NULL){
        perror("event_new error");
        return -1;
    }

    //7.将新的事件节点上base地基
    event_add(ev,NULL);
    //8.进行事件分发(进入事件循环等待)
    event_base_dispatch(base);

    //9.释放资源

    event_base_free(base);
    event_free(ev);

    close(lfd);
    return 0;
}

 

posted on 2021-12-21 10:14  飘杨......  阅读(845)  评论(0编辑  收藏  举报