Libevent的入门级使用(二)

一、Libevent的地基event_base

在使用libevent的函数之前,需要先申请一个或event_base结构,相当于盖房子时的地基,在event_base基础上会有一个事件集合,可以检测哪个事件是激活的(就绪),通常情况下可以通过event_base_new函数获得event_base结构,函数如下:

struct event_base *event_base_new(void);

申请到event_base结构体指针可以通过event_base_free进行释放,函数如下:

void event_base_free(struct event_base *);

如果fork出了子进程,想在子进程这继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:

int event_reinit(struct event_base *base);

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

二、等待事件产生(循环等待event_loop)

Libevent在地基打好之后,需要等待事件的产生,也就是等待想要等待的事件的激活,那么程序不能退出,对于epoll来说,我们需要自己控制循环,而在libevent中也给我们提供了api接口。函数如下:

int event_base_loop(struct event_base *, int flags);

flags的取值:

  • #define EVLOOP_ONCE  0x01:只触发一次,如果事件没有被触发,阻塞等待。
  • #define EVLOOP_NONBLOCK:非阻塞方式检测事件是否被 触发,不管事件是否被触发,都会立即返回。

而大多数我们都调用libevent给我们提供的另一个api,函数如下:

int event_base_dispatch(struct event_base *);

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

int event_base_loopexit(struct event_base *, const struct timeval *);
int event_base_loopbreak(struct event_base *);

 两个函数的区别是如果正在执行激活事件的回调函数,那么event_base_loopexit将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间后立即结束循环),而event_base_loopbreak会立即终止循环。

三、事件驱动event

事件驱动实际上是libevent的核心思想,主要的状态转化如下:

主要的几个状态:

  • 无效的指针:此时仅仅是定义了struct event *ptr;
  • 非未决:相当于创建了事件,但是事件还没有处于被监听状态,类似于我们使用epoll的时候定义了struct epoll_event ev并且对ev的两个字段进行了赋值,但是此时尚未调用epoll_ctl。
  • 未决:就是对事件开始监听,暂时未有事件产生,相当于调用epoll_ctl。
  • 激活:代表监听的事件已经产生,这时需要处理,相当于epoll所说的事件就绪。

Libevent的事件驱动对应的结构体为struct event,对应的函数在图上也比较清晰。

1.event_new函数

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:要监听的事件
    1. #define EV_TIMEOUT     0x01  //超时事件
    2. #define EV_READ                0x02  //读事件
    3. #define EV_WRITE              0x04  //写事件
    4. #define EV_SIGNAL                  0x80  //信号事件
    5. #define EV_PERSIST          0x10  //周期性触发
    6. #define EV_ET                     0x20  //边缘触发,如果底层模型支持

cb回调函数,原型如下:

typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

arg回调函数的参数

2.event_add函数

int event_add(struct event *ev, const struct timeval *timeout);

将非未决态事件转为未决态,相当于调用epoll_ctl函数,开始监听事件是否产生。

参数说明:

  • ev:就是前面event_new创建的事件
  • timeout:限时等待事件的产生,也可以设置未NULL,没有限时。

3.event_del函数

int event_del(struct event *);

 将事件从未决状态变为非未决状态,相当于epoll的下树(epoll_ctl调用EPOLL_CTL_DEL操作)操作。

4.event_free()函数

void event_free(struct event *);

释放event_ne申请的event节点  

四、Libevent使用示例  

server端示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include <event.h>

#define MAXLINE 4096
#define PORT 8000

void connect_fd_cb(int cfd, short event, void *arg) {
    char sendbuf[MAXLINE] = {0}, recbuf[MAXLINE] = {0};
    //读取客户端发来的信息
    ssize_t len = read(cfd, recbuf, sizeof(recbuf));
    if(len < 0){
        perror("read data error");
        //event_del()
        return;
    }

    printf("接收客户端的请求:%s\n", recbuf);

    //向客户端发送信息
    printf("回复客户端信息:");
    fgets(sendbuf, sizeof(sendbuf), stdin);
    write(cfd, sendbuf, sizeof(sendbuf));
}

void listen_fd_cb(int lfd, short event, void *arg) {
    struct event_base *base = (struct event_base *)arg;

    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr));
    socklen_t len = sizeof(client_addr);

    int connect_fd = -1;
    if((connect_fd = accept(lfd, (struct sockaddr*)&client_addr, &len)) == -1){
        printf("accept socket error: %s(error: %d)\n", strerror(errno), errno);
        return;
    } else {
        printf("client socket connect success!\n");
        char ip[16] = {0};
        printf("new client ip = %s, port = %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, 16), ntohs(client_addr.sin_port));

        //将connect_fd上树
        struct event *ev = event_new(base, connect_fd, EV_READ | EV_PERSIST, connect_fd_cb, NULL);
        event_add(ev, NULL);
    }
}

int main() {
    //定义服务器监听套接字和连接套接字
    int listen_fd = -1;//初始化为-1
    struct sockaddr_in servaddr;//定义服务器对应的套接字地址

    //初始化套接字地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;//IPv4
    servaddr.sin_port = htons(PORT);//设置监听端口
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//表示接收任意IP的连接请求

    //创建套接字
    if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        //如果创建套接字失败,返回错误信息
        //strerror(int errnum)获取错误的描述字符串
        printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //绑定套接字和本地IP地址和端口
    if(bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        //绑定出现错误
        printf("bind socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    //使得listen_fd变成监听描述符
    if(listen(listen_fd, 10) == -1){
        printf("listen socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    //accept阻塞等待客户端请求
    printf("等待客户端发起连接\n");

    //创建event_base根节点
    struct event_base *base = event_base_new();
    //初始化listen_fd上树节点
    struct event *ev = event_new(base, listen_fd, EV_READ | EV_PERSIST, listen_fd_cb, base);
    //上树
    event_add(ev, NULL);
    //循环监听
    event_base_dispatch(base); //阻塞
   event_free(ev);
    close(listen_fd);
    event_base_free(base);

    return 0;
}

上述的Server端代码存在一定的缺陷,当第二个客户端连接成功后,ev和connect_fd的值会被覆盖掉,那么此时就无法再去操作第一个连接成功的客户端。所以下面的示例是可以连接多个客户端,也可以操作多个客户端,并不会相互影响。

数组版的Server端示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <ctype.h>

#define MAXLINE 4096
#define MAX_CLIENT 1024
#define PORT 8000

typedef struct FdEventMap {
    int fd; //文件描述符
    struct event *ev; //对应事件
} FdEvent;

FdEvent mFdEvents[MAX_CLIENT];

void init_event_arrary() {
    int i = 0;
    for (i = 0; i < MAX_CLIENT; ++i) {
        mFdEvents[i].fd = -1;
        mFdEvents[i].ev = NULL;
    }
}

int add_event(int fd, struct event *ev) {
    int i = 0;
    for (i = 0; i < MAX_CLIENT; ++i) {
        if (mFdEvents[i].fd < 0) {
            break;
        }
    }

    if (i == MAX_CLIENT) {
        printf("too many clients connected..\n");
        return -1;
    }

    mFdEvents[i].fd = fd;
    mFdEvents[i].ev = ev;

    return  0;
}

void destroy_event_array() {
    int i = 0;
    for (i = 0; i < MAX_CLIENT; ++i) {
       if (mFdEvents[i].fd > 0 && mFdEvents[i].ev) {
           close(mFdEvents[i].fd);
           mFdEvents[i].fd = -1;
           event_free(mFdEvents[i].ev);
       }
    }
}

struct event* get_event_by_fd(int fd) {
    int i = 0;
    for (i = 0; i < MAX_CLIENT; ++i) {
        if (mFdEvents[i].fd == fd) {
            //找到匹配的文件描述符
            return mFdEvents[i].ev;
        }
    }

    return  NULL;
}

void read_cb(evutil_socket_t fd, short events, void *arg) {
    char buffer[256] = {0};
    int ret = recv(fd, buffer, sizeof(buffer), 0);
    if (ret <= 1) {
        close(fd);
        event_del(get_event_by_fd(fd));
    } else {
        if (ret > 0) {
            printf("接收客户端的请求:%s\n", buffer);
            int i = 0;
            for (i = 0; i < ret; ++i) {
                buffer[i] = toupper(buffer[i]);
            }
            send(fd, buffer, ret, 0);
        }
    }
}

void listen_fd_cb(int lfd, short event, void *arg) {
    struct event_base *base = (struct event_base *)arg;

    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr));
    socklen_t len = sizeof(client_addr);

    int connect_fd = -1;
    if((connect_fd = accept(lfd, (struct sockaddr*)&client_addr, &len)) == -1){
        printf("accept socket error: %s(error: %d)\n", strerror(errno), errno);
        return;
    } else if (connect_fd > 0) {
        printf("client socket connect success, fd = %d\n", connect_fd);
        char ip[16] = {0};
        printf("new client ip = %s, port = %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, 16), ntohs(client_addr.sin_port));

        //继续监听
        struct event *read_ev = event_new(base, connect_fd, EV_READ | EV_PERSIST, read_cb, base);
        event_add(read_ev, NULL);
        //添加到数组中
        add_event(connect_fd, read_ev);
    }
}

int main() {
    //定义服务器监听套接字和连接套接字
    int listen_fd = -1;//初始化为-1
    struct sockaddr_in servaddr;//定义服务器对应的套接字地址

    //初始化套接字地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;//IPv4
    servaddr.sin_port = htons(PORT);//设置监听端口
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//表示接收任意IP的连接请求

    //创建套接字
    if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        //如果创建套接字失败,返回错误信息
        //strerror(int errnum)获取错误的描述字符串
        printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //绑定套接字和本地IP地址和端口
    if(bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        //绑定出现错误
        printf("bind socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    //使得listen_fd变成监听描述符
    if(listen(listen_fd, 10) == -1){
        printf("listen socket error: %s(error: %d)\n", strerror(errno), errno);
        exit(0);
    }

    //accept阻塞等待客户端请求
    printf("等待客户端发起连接\n");

    //创建事件-设置回调
    init_event_arrary(); //初始化事件数组

    //创建event_base根节点
    struct event_base *base = event_base_new();
    //初始化listen_fd上树节点
    struct event *ev = event_new(base, listen_fd, EV_READ | EV_PERSIST, listen_fd_cb, base);
    //上树
    event_add(ev, NULL);
    //循环监听
    event_base_dispatch(base); //阻塞

    event_free(ev);
    destroy_event_array();
    event_base_free(base);

    return 0;
}

客户端示例代码:  

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#define MAXLINE 4096
#define PORT 8000

int main(void){
    //定义客户端套接字
    int sockfd = -1;
    //定义想连接的服务器的套接字地址
    struct sockaddr_in servaddr;

    //发送和接收数据的缓冲区
    char sendbuf[MAXLINE], recbuf[MAXLINE];

    //初始化服务器套接字地址
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;//IPv4
    servaddr.sin_port = htons(PORT);//想连接的服务器的端口
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器的IP地址

    //创建套接字
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
        return 0;
    }

    //向服务器发送连接请求
    if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        //连接失败
        printf("connect socket error: %s(error: %d)\n", strerror(errno), errno);
        return 0;
    }

    while(1) {
        //向服务器发送信息
        printf("向服务器发送信息:");
        fgets(sendbuf, sizeof(sendbuf), stdin);
        write(sockfd, sendbuf, sizeof(sendbuf));

        //从服务器接收信息
        ssize_t len = read(sockfd, recbuf, sizeof(recbuf));
        if(len < 0){
            if(errno == EINTR){
                continue;
            }
            break;
        }

        printf("服务器回应:%s\n", recbuf);
    }

    //关闭套接字
    close(sockfd);

    return  1;
}

 

posted @ 2023-12-04 16:21  TechNomad  阅读(32)  评论(0编辑  收藏  举报