使用libevent实现进程间通讯
libevent概述
Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
起源
起初对libevent、libev、libuv等网络库有所耳目,未能拜读,最近在开发公司项目的时,发现底层组件之间交互采用了libevent进行编写,遂对其学习研究。公司中采用libevent 2.0之前的版本进行研发,对比发现,旧版本中很多函数过于冗杂,现对libevent 2.0之后的新版本进行学习研究:
在Libevent2.0之前的版本中,没有event_assign或者event_new函数,而只有event_set函数,该函数返回的event与“当前”base相关联。如果有多个event_base,则还需要调用event_base_set函数指明event与哪个base相关联
获取源码
Github: https://github.com/libevent/libevent
初识libevent
- 服务端:在上述路径执行 ./hello-world
- 客户端:采用nc进行访问 nc 127.0.0.1 9995 9995为上述代码中指定的端口号

每有客户端注册时 server端将打印 flushed answer,同时,client端打印Hello, World!
框架学习– event_base 重中之重
夸张的理解为:libevent的世界中,event_base作为万物起源
使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base
结构体持有一个事件集合,可以检测以 确定哪个事件是激活的。(相当于epoll红黑树的树根)
从思想上出发
- 我们可以把libevent项目想象为造火箭的过程,我们都只是螺丝工,那么造火箭需要做什么?
1. 拿出火箭壳(创建框架)
2. 造螺丝 (创建事件)
3. 拧螺丝 (添加事件)
4. 造火箭(事件循环)
一:掏出火箭壳 —>event_base()创建与释放
我们开始第一步:创建一个event_base
// 创建event_base struct event_base* event_base_new(void)
// 释放event_base_free event_base_free(struct event_base* base);
二:造螺丝 —>event_new()创建与释放
注:创建事件:(当前篇只针对不带缓冲区event事件进行讲解,有关缓存学习请见后续更新)
// 创建新事件 struct event *event_new( struct event_base *base, evutil_socket_t fd, - // 文件描述符 - int 底层是对epollin与epollout的封装 short what, event_callback_fn cb, // 事件的处理回调函数 void *arg //回调函数传参 ); // 事件的处理回调函数 typedef void (*event_callback_fn)(evutil_socket_t, short, void *); // short what #define EV_TIMEOUT 0x01 // 已淘汰(忽略) #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 //libevent封装了信号相关的操作 SIGNAL #define EV_PERSIST 0x10 // 持续触发 #define EV_ET 0x20 // 边沿模式
在程序的最后我们需要 event_free 进行释放(但我们不得不提前考虑)
// 创建event_free void event_free(struct event *event);
调用event_new()函数之后, 新事件处于已初始化和非未决状态 (翻译:螺丝造好了,还没拧到火箭时的状态)
三:拧螺丝 —>event_add()相关函数
创建事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。
使用event_add()将事件添加到event_base。 (将螺丝拧上去)
非未决事件 -> 未决事件.
// event_add int event_add( struct event *ev, const struct timeval *tv );
| – tv:参数 | Value |
|---|---|
| NULL: | 事件被触发, 对应的回调被调用 |
| tv = {0, n} | 设置的时间,在改时间段内检测的事件没被触发, 时间到达之后, 回调函数还是会被调用 |
函数调用成功返回0, 失败返回-1
设置非未决(作为了解实际中少用)
未决事件 -> 非未决事件.
//对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。 int event_del(struct event *ev); //成功时函数返回 0,失败时返回-1。
四:一节一节造火箭 —>event_base_dispatch()相关函数
最后,我们只需将添加 事件循环
使用event_base_dispatch()函数
// event_base_dispatch(简化版event_base_loop()) int event_base_dispatch(struct event_base* base); //等同于没有设置标志的 event_base_loop ( ) //将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者 event_base_loopexit()为止
关于event_base_loop()函数
// event_base_loop() int event_base_loop(struct event_base *base, int flags); //正常退出返回0, 失败返回-1 //flages #define EVLOOP_ONCE 0x01 //事件只会被触发一次 //事件没有被触发, 阻塞等 #define EVLOOP_NONBLOCK 0x02 //非阻塞 等方式去做事件检测 //不关心事件是否被触发了 #define EVLOOP_NO_EXIT_ON_EMPTY 0x04 //没有事件的时候, 也不退出轮询检测
关于退出事件循环函数event_base_loopexit()与event_base_loopbreak():
执行当前后退出 event_base_loopexit()
//如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出 int event_base_loopexit( struct event_base *base, const struct timeval *tv ); //参数struct timeval *tv struct timeval {
long tv_sec; long tv_usec; };
立即退出循环 event_base_loopbreak()
//让event_base 立即退出循环 int event_base_loopbreak(struct event_base *base); //返回值: 成功 0, 失败 -1
Demo
//write_fifo.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> #include <event2/event.h> // 回调函数 void write_cb(evutil_socket_t fd, short what, void *arg) {
// write管道 char buf[1024] = {
0}; static int num = 666; sprintf(buf, "hello, world == %d\n", num); write(fd, buf, strlen(buf)+1); } int main(int argc, const char* argv[]) {
// open file int fd = open("myfifo", O_WRONLY | O_NONBLOCK); if(fd == -1) {
perror("open error"); exit(1); } // 写管道 struct event_base* base = NULL; base = event_base_new(); // 创建事件 struct event* ev = NULL; // 检测的写缓冲区是否有空间写 ev = event_new(base, fd, EV_WRITE , write_cb, NULL); // 添加事件 event_add(ev, NULL); // 事件循环 event_base_dispatch(base); // 释放资源 event_free(ev); event_base_free(base); close(fd); return 0; }
//read_fifo #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> #include <event2/event.h> // 读取回调函数 void read_cb(evutil_socket_t fd, short what, void *arg) {
// 读管道 char buf[1024] = {
0}; int len = read(fd, buf, sizeof(buf)); printf("data len = %d, buf = %s\n", len, buf); printf("read event: %s", what & EV_READ ? "Yes" : "No");//对what类型对判断 } int main(int argc, const char* argv[]) {
unlink("myfifo"); //创建有名管道 mkfifo("myfifo", 0664); // open file int fd = open("myfifo", O_RDONLY | O_NONBLOCK); if(fd == -1) {
perror("open error"); exit(1); } // 读管道 struct event_base* base = NULL; base = event_base_new(); // 创建事件 struct event* ev = NULL; ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL); // 添加事件 event_add(ev, NULL); // 事件循环 event_base_dispatch(base); // 释放资源 event_free(ev); event_base_free(base); close(fd); return 0; }
./read ./write 依次执行即可
下一篇将讲述 带数据缓冲区 – Bufferevent …利用Bufferevent 实现网络通讯
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207638.html原文链接:https://javaforall.net
