Libevent的入门级使用(二)

一、Libevent的地基event_base

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

1
struct event_base *event_base_new(void);

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

1
void event_base_free(struct event_base *);

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

1
int event_reinit(struct event_base *base);

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

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

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

1
int event_base_loop(struct event_base *, int flags);

flags的取值:

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

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

1
int event_base_dispatch(struct event_base *);

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

1
2
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函数

1
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回调函数,原型如下:

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

arg回调函数的参数

2.event_add函数

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

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

参数说明:

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

3.event_del函数

1
int event_del(struct event *);

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

4.event_free()函数

1
void event_free(struct event *);

释放event_ne申请的event节点  

四、Libevent使用示例  

server端示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#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端示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#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;
}

客户端示例代码:  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#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 @   TechNomad  阅读(198)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示