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:要监听的事件
- #define EV_TIMEOUT 0x01 //超时事件
- #define EV_READ 0x02 //读事件
- #define EV_WRITE 0x04 //写事件
- #define EV_SIGNAL 0x80 //信号事件
- #define EV_PERSIST 0x10 //周期性触发
- #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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?