alin的学习之路(Linux网络编程:八)(libevent库)
alin的学习之路(Linux网络编程:八)(libevent库)
1. libevent库
1. 优点
- 开源
- 精简
- 跨平台
- 专注于网络通信 ——可以借助fd来进行 pipe、fifo 等通信
2. 下载安装
-
下载地址:https://libevent.org/
-
解压缩:tar zxvf libevent-2.1.8-stable.tar.gz
-
源码包安装:
如果有 README、readme 文件, 参考安装。 - ./configure 检查安装环境,生成 makefile 文件。
- make 生成 .o 文件 和 可执行文件。
- sudo make install 将必要的资源, 拷贝至系统指定目录。
测试是否安装成功:
进入 sample 目录中, 编译 .c 文件, 运行。
编译时,务必添加 指定库名的 选项 -levent
gcc hello-world.c -o hello -levent
安装成功后, 会在 系统的 /usr/local/lib 目录下,多出 libevent.so 和 libevent.a 库文件。
如果运行 ./hello-world 可执行文件,报出如下错误: error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory
解决方法
将库目录添加到 /etc/ld.so.cache 文件中
sudo vim /etc/ld.so.conf 文件,加入库所在路径即 /usr/local/lib/ 执行 sudo ldconfig -v ,该命令会重建/etc/ld.so.cache 文件
- 编译成功后启动可执行程序,使用 nc 命令指定端口号为 9995,得到输出即为成功
2. libevent 框架
-
创建base,使用 event_base_new()
#include <event2/event.h> struct event_base *event_base_new(void); struct event_base *base = event_base_new();
-
创建事件对象 event
// 常规事件 event event_new(); // 带缓冲区的事件 bufferevent bufferevent_socket_new();
-
添加事件到 base上
int event_add(struct event *ev, const struct timeval *tv);
-
循环监听事件满足
int event_base_dispatch(struct event_base *base); event_base_dispatch(base);
-
释放 base 和事件对象
void event_base_free(struct event_base *base); event_base_free(base);
3. 常规事件
1. 创建事件对象event
struct event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb; void *arg); base: event_base_new() 返回值。 fd: 绑定到 event对象上的 文件描述符 what: fd 对应的监听事件 (read、write、expcet) EV_READ 监听 一次 读事件 EV_WRITE 监听 一次 写事件 EV_PERSIST 持续触发。 结合 event_base_dispatch 函数使用。 保证持续循环。 可以使用 “|” 连接。EV_PERSIST|EV_READ 或 EV_PERSIST|EV_WRITE cb: 一旦,事件满足监听条, 回调的函数。 typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *) arg: 回调函数的参数。 返回值:成功创建的 事件对象
2. 添加、销毁事件对象event
// 添加事件 int event_add(struct event *ev, const struct timeval *tv);ev: event_new() 的返回值。 ————【注意】不是 event_base !!! tv: 通常 NULL // 销毁事件 int event_free(struct event *ev); 成功: 0, 失败: -1
3.编码实现读写 fifo
read_fifo.c
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <event2/event.h> #include <strings.h> void read_cb(evutil_socket_t fd, short what, void* arg) { char buf[BUFSIZ]; bzero(buf,sizeof(buf)); read(fd, buf, sizeof(buf)); printf("what:%s, read:%s\n",what & EV_READ ? "read YES" : "read NO", buf); sleep(1); } int main() { unlink("myfifo"); mkfifo("myfifo", 0664); int fd = open("myfifo",O_RDONLY|O_NONBLOCK); //创建base struct event_base* base = event_base_new(); //创建事件 //struct event *event_new(struct event_base *base,evutil_socket_t fd,short //what,event_callback_fn cb; void *arg); struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL); //添加事件 event_add(ev, NULL); //循环监听 event_base_dispatch(base); //释放事件和base event_base_free(base); event_free(ev); return 0; }
write_fifo.c
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <event2/event.h> #include <string.h> void read_cb(evutil_socket_t fd, short what, void* arg) { char buf[] = "hello libevent"; write(fd, buf, strlen(buf)+1); sleep(1); } int main() { int fd = open("myfifo",O_WRONLY|O_NONBLOCK); //创建base struct event_base* base = event_base_new(); //创建事件 //struct event *event_new(struct event_base *base,evutil_socket_t fd,short //what,event_callback_fn cb; void *arg); struct event* ev = event_new(base, fd, EV_WRITE|EV_PERSIST, read_cb, NULL); //添加事件 event_add(ev, NULL); //循环监听 event_base_dispatch(base); //释放事件和base event_base_free(base); event_free(ev); return 0; }
4. 一个小的函数
event_base_get_method(base); 函数可以返回当前base使用的多路IO转接方式
4. 事件的未决和非未决态
未决态:事件有资格被处理, 尚未被处理。
非未决态:事件没有资格被处理。
5. 带缓冲的事件bufferevent
1. bufferevent 特性
- 读缓冲:有数据 ——> 读回调函数会被调用 ——> 使用 bufferevent_read() (顶替 read() )——> 读数据
- 写缓冲:使用 bufferevent_write() ( 顶替 write() ) ——> 向 写缓冲,写数据 ——> 写缓冲中,一旦有数据,会自动刷新(写出给对端)——> 写完, 回调函数会被调用。
2. 创建、释放bufferevent
- 创建 bufferevent
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options); base: struct event_base ———— “base底座” fd: 要封装到 bufferevent 内的 fd options: BEV_OPT_CLOSE_ON_FREE。 作用:释放 bufferevent对象时, 同时释放内部封装的资源。 【注意】:bufferevent_socket_new函数内,没有指定 fd 对应的 回调函数。
- 释放 bufferevent
void bufferevent_free(struct bufferevent *bev); bev:bufferevent_socket_new 函数的返回值。
3. 给bufferevent设置回调函数
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg ); bufev: bufferevent_socket_new() 函数的返回值。 readcb: 设置 bufferevent 读缓冲,对应的回调。 如: read_cb(){ 用 bufferevent_read() 去读数据 } writecb: 设置 bufferevent 写缓冲,对应的回调。 ———— 通常 NULL 如: write_cb(){ 给调用者,发送写成功的“通知” } eventcb: 设置事件回调。 ———— 通常 NULL 对应: BEV_EVENT_CONNECTED:———— 用作客户端。 cbarg:上述回调函数的参数。
1. readcb对应的回调函数类型
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void * ctx); // 示例: void read_cb(struct bufferevent *bev, void *cbarg) { ..... bufferevent_read(); ———— 顶替read() } // bufferevent_read 函数原型 size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);
2. writecb对应的回调函数类型
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void * ctx); // 示例: void write_cb(struct bufferevent *bev, void *cbarg) { ..... 数据写完了,这个函数才会被调用!!! } // bufferevent_write 函数原型 int bufferevent_write(struct bufferevent *bev, const void *buf, size_t size);
3. eventcb对应的回调函数类型
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *cbarg); // 示例: void event_cb(struct bufferevent *bev, short events, void *cbarg) { events: BEV_EVENT_CONNECTED:———— 用作客户端。 ..... 当有状态、特殊状况产生时,该回调函数会被调用。 }
4. 启用、禁用bufferevent缓冲区
- 默认: write 缓冲是 enable
- read 缓冲 是 disable。 —— 不能直接使用 bufferevent_read(); 读取数。
void bufferevent_enable(struct bufferevent *bufev, short events); 启用缓冲区 bufev: bufferevent_socket_new() 函数的返回值 events: EV_READ、EV_WRITE、EV_READ|EV_WRITE 对应读和写缓冲区的启用
- 禁用
void bufferevent_disable(struct bufferevent *bufev, short events);
5. 借助bufferevent实现C/S通信
client端
原来:socket(); connect(); 现在 socket() + bufferevent_socket_connect()
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); bev: bufferevent_socket_new() 函数的返回值 address/addrlen: 等同与 connect() 参2、3
server端
原来:socket(); bind(); listen(); accept(); 现在:evconnlistener_new_bind()
// 这一个函数,相当于 socket(); bind(); listen(); accept(); 的作用。 #include <event2/listener.h> struct evconnlistener *evconnlistener_new_bind ( struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen); base:struct event_base --- “底座” cb:回调函数 一旦,该函数被回调,说明客户端已经成功连接。回调函数内部,应与客户端进行数据通信。 ptr:回调函数的参数 flags:LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE backlog:listen 函数的2参。 -1 表最大值。 sa: 服务端自己的地址结构 socklen: 服务端自己的地址结构的大小。 返回值:成功创建的监听器。
监听器 回调函数
typedef void (*evconnlistener_cb) ( struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr); 【注】:该回调函数,不由我们调用,是框架自动调用。因此,只需知晓参数含义即可。
释放监听服务器
void evconnlistener_free(struct evconnlistener *lev);
6. 使用libevent库实现C/S模型
1. 服务器端代码流程:
-
创建 base
-
绑定地址结构 用于 evconnlistener_new_bind() 函数创建监听器的传参
-
创建监听器 evconnlistener_new_bind()
监听器的回调函数用于进行通信,进行通信要使用 bufferevent 来监听读写和事件,所以 bufferevent的创建要在监听器的回调函数中。以下是创建监听器时的回调函数:
创建 bufferevent 事件 bufferevent_socket_new()
-
设置回调函数 bufferevent_setcb()
-
启动读回调 bufferevent_enable()
读回调:使用 bufferevent_read() 读取客户端发送来的数据并处理,处理后使用 bufferevent_write() 写回 - 写回调:bufferevent_write() 函数执行完毕后调用,无卵用
- 事件回调:通过判断 events & BEV_EVENT_EOF 可知客户端断开连接,events & BEV_EVENT_ERROR 可知有其他错误发生,这时直接调用 bufferevent_free() 释放 bufferevent
event_base_dispatch() 启动循环监听
释放监听器资源,释放base
2. 客户端代码流程:
- socket() 函数创建 通信套接字cfd
- 设置服务器的地址结构 srv_addr
- 创建base
- 创建bufferevent事件 bufferevent_socket_new()
- 设置回调函数 bufferevent_setcb() ,启动读回调 bufferevent_enable() 读回调:使用 bufferevent_read() 接收服务器发送来的消息,并在屏幕打印
- 写回调:打印说明写事件完成,写回调的打印信息应该紧接在 bufferevent_write() 之后
- 事件回调:(BEV_EVENT_CONNECTED & events) 为真时表示与服务器连接成功,可以使用事件回调来释放bufferevent的资源
3. 代码实现
服务器端
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #include <event2/bufferevent.h> #include <event2/listener.h> #define PORT 8000 void read_cb(struct bufferevent *bev, void *cbarg) { char buf[BUFSIZ]; int n = bufferevent_read(bev, buf, sizeof(buf)); for(int i=0 ;i<n ;++i) { buf[i] = toupper(buf[i]); } write(STDOUT_FILENO, buf, n); bufferevent_write(bev, buf, n); sleep(1); } void write_cb(struct bufferevent *bev, void* cbarg) { printf("服务器数据已发送\n"); } // 事件 void event_cb(struct bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF) { printf("connection closed\n"); } else if(events & BEV_EVENT_ERROR) { printf("some other error\n"); } bufferevent_free(bev); printf("buffevent 资源已经被释放...\n"); } //用于客户端连接上后的通信 void listen_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr) { //char cltip[16]; //printf("client accept ip:%s, port%d\n", // inet_ntop(AF_INET, (struct sockaddr_in*)addr->sin_addr.s_addr, cltip, sizeof(cltip)), // htons((struct sockaddr_in*)addr->sin_port)); struct event_base* base = (struct event_base*)ptr; //创建bufferevent事件 struct bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); //设置回调 bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); //启动读写缓冲区 bufferevent_enable(bev, EV_READ|EV_WRITE); } int main() { struct sockaddr_in srv_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(PORT); srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //创建base struct event_base* base = event_base_new(); //创建监听器 struct evconnlistener* listener = evconnlistener_new_bind(base, listen_cb, (void*)base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); //循环监听 event_base_dispatch(base); //释放 event_base_free(base); evconnlistener_free(listener); return 0; }
客户端
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <arpa/inet.h> #include <event2/bufferevent.h> #include <event2/listener.h> #define PORT 8000 void read_cb(struct bufferevent* bev, void *cbarg) { char buf[BUFSIZ]; int n = bufferevent_read(bev, buf, sizeof(buf)); buf[n] = '\0'; printf("服务器say: %s", buf); bufferevent_write(bev, buf, n); //将数据再写回服务器,做出一直发送的效果 } void write_cb(struct bufferevent* bev, void *cbarg) { printf("客户端写回调,没卵用!\n"); } void event_cb(struct bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF) { printf("connection closed\n"); } else if(events & BEV_EVENT_ERROR) { printf("some other error\n"); } else if(events & BEV_EVENT_CONNECTED) { printf("已经连接服务器...\\(^o^)/...\n"); return; } // 释放资源 bufferevent_free(bev); } void read_terminal(evutil_socket_t fd, short what, void *arg ) { struct bufferevent* bev = (struct bufferevent*)arg; char buf[BUFSIZ]; int n = read(fd, buf, sizeof(buf)); bufferevent_write(bev, buf, n); sleep(1); } int main() { int cfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in srv_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(PORT); inet_pton(AF_INET, "127.0.0.1", &srv_addr.sin_addr.s_addr); //创建base struct event_base* base = event_base_new(); struct bufferevent* bev; //创建befferevent事件 bev = bufferevent_socket_new(base, cfd, BEV_OPT_CLOSE_ON_FREE); //设置回调 bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); //启用读缓冲区 bufferevent_enable(bev, EV_READ|EV_WRITE); //连接服务器 bufferevent_socket_connect(bev, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); struct event* ev = event_new(base, STDOUT_FILENO, EV_READ|EV_PERSIST, read_terminal, bev); //添加事件 /vent_add(ev, NULL); //循环监听 event_base_dispatch(base); //释放bufferevent事件 event_free(ev); //释放base event_base_free(base); return 0; }
7. some small point
有关管道读写注意:当写端关闭,读端还在读的时候,无论阻塞和非阻塞read都会返回0,表示对端关闭
- 学习Linux网络编程教材
- Proxy源代码分析--谈谈如何学习linux网络编程
- 0-Linux 网络编程学习笔记导航
- [Linux网络编程学习笔记]FIFO的创建和使用
- linux网络编程学习笔记之一 -----各种基础知识小结
- linux C学习第二天之应用编程和网络编程笔记(上)
- 如何学习Linux网络编程
- Proxy源代码分析--谈谈如何学习linux网络编程
- linux网络设备应用与驱动编程学习4——模板与实例(B)——打开和释放方法
- linux网络编程学习笔记之六 -----I/O多路复用服务端
- Linux 网络编程学习---线程
- Linux网络编程学习笔记-socket编程3--5
- 0-Linux 网络编程学习笔记导航
- 我的Linux学习之路(二、配置网络)
- Android开发学习之路--网络编程之xml、json
- Linux网络编程基础之二--UDP --Unix学习总结之四
- Proxy源代码分析--谈谈如何学习linux网络编程 [转]
- Linux网络编程-学习笔记(基础TCP套接字函数)
- Linux网络编程(3):信号处理与定时机制简要学习
- Linux程序设计学习笔记----网络编程之网络数据包拆封包与字节顺序大小端