Linux网络编程 Part5 libevent

概述

优点

  • 开源
  • 精简
  • 支持跨平台
  • 专注于网络通信

源码包安装步骤:可参考具体README文件

  1. ./configure检查安装环境,生成makefile
  2. make生成.o和可执行文件
  3. sudo make install 将必要的资源拷贝至系统指定目录
  4. 进入sample目录,运行demo验证是否安装成功
  5. 编译使用库的.c时,需要加-levent选项
  6. 库名libevent.so的路径可以在/usr/local/lib查看

特性:基于“事件”异步通信模型 → 回调

libevent框架

框架

  1. 创建event_base(地基)
    • struct event_base *event_base_new(void);
      • struct event_base *base = event_base_new();
  2. 创建事件evnet
    • event → event_new();
    • bufferevent → bufferevent_socket_new();
  3. 将事件添加到event_base上:int event_add(struct event *ev, const struct timeval *tv);
  4. 循环监听事件满足
    • int event_base_dispatch(struct event_base *base);
      • event_base_dispatch(base);
  5. 释放event_base:event_base event_base_free(base);

其他函数了解

  • 查看支持哪些多路IO(比如select、poll、epoll)
    • const char **event_get_supported_methods(void);
  • 查看当前用的多路IO(一般为epoll)
    • const char *event_base_get_method(const struct event_base *base);
  • fork后使子进程重新初始化event_base,避免继承父进程的event_base
    • int event_reinit(struct event_base *base);

常规事件:event

头文件#include<event2/event.h>

创建事件

struct event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb; void *arg); 创建一个event事件

参数:

  • base:event_base_new()的返回值
  • fd:绑定到bufferevent事件上的文件描述符
  • what:对应的监听类型
    • EV_READ:一次读事件
    • EV_WRTIE:一次写事件
    • EV_PERSIST:持续触发
      • 结合event_base_dispatch()使用
      • 通常搭配读或写
        • EV_PERSIST | EV_READ
        • EV_PERSIST | EV_WRTIE
  • cb:一旦事件满足监听条件,回调的函数
    • typedef void(*event_callback_fn)(evutil_socket_t fd, short what, void *arg)
      • 三个参数分别对应event_new()的第二、三、五个参数
  • arg:回调函数的参数

返回值:成功创建的struct event事件对象

添加事件

int event_add(struct event *ev, const struct timeval *tv); 将事件添加到event_base上

参数:

  • ev:event对象,即event_new()的返回值
  • tv:超时时间
    • NULL:不会超时,即一直等到事件被触发,回调函数被调用
    • 非零值:若等待时间内事件没有被触发,则时间一到回调函数被调用

移除事件

int event_del(struct event *ev); 将事件从event_base上取下(了解)

参数:event对象,即event_new()的返回值

释放事件

int event_free(struct event *ev); 销毁一个事件

参数:event对象,即event_new()的返回值

未决和非未决

  • 非未决:没有资格被处理(即使满足对应监听事件)
  • 未决:有资格被处理,但尚未被处理(因为对应事件还未被触发)

编码练习:libevent + fifo实现读写

read

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<event2/event.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

void read_cb(evutil_socket_t fd, short what, void *arg) {
    char buf[BUFSIZ] = {0};
    read(fd, buf, sizeof(buf));

    printf("read from write : %s\n", buf);

    sleep(1);

    return ;
}

int main(int argc, char *argv[]) {
    unlink("test_fifo");
    mkfifo("test_fifo", 0644); //创建fifo

    int fd = open("test_fifo", O_RDONLY | O_NONBLOCK); //打开fifo的读端
    if(fd == -1) sys_err("open error");
    
    struct event_base *base = event_base_new(); //创建event_base

    struct event *ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL); //创建事件

    event_add(ev, NULL); //添加事件

    event_base_dispatch(base); //循环监听

    //释放base和event
    event_base_free(base);
    event_free(ev);

    return 0;
}

write

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<event2/event.h>
#include<errno.h>
#include<fcntl.h>

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

void write_cb(evutil_socket_t fd, short what, void *arg) {
    char buf[] = "hello libevent ~ ";
    static int num = 0;
    sprintf(buf, "%d", ++num);
    write(fd, buf, strlen(buf) + 1);

    sleep(1);

    return ;
}

int main(int argc, char *argv[]) {
    int fd = open("test_fifo", O_WRONLY | O_NONBLOCK); //打开fifo的写端
    if(fd == -1) sys_err("open error");

    struct event_base *base = event_base_new(); //创建event_base

    struct event *ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL); //创建事件

    event_add(ev, NULL); //添加事件

    event_base_dispatch(base); //循环监听

    //释放base和event
    event_base_free(base);
    event_free(ev);

    return 0;
}

带缓冲区的事件:bufferevent

头文件#include<event2/bufferevent.h>

相较常规event事件而言,bufferevent专注于socket通信

bufferevent原理

一个bufferevent对象中有read/write两个缓冲区,借助队列实现,先进先出,读走数据就没了

  • 读:读缓冲中有数据 → 读回调函数被调用 → 使用bufferevent_read() → 从读缓冲中读数据
  • 写: 使用bufferevent_write() → 向写缓冲中写数据 → 当写缓冲区有数据后会自动发送给对端 → 发送完成后,写回调函数被调用(只起到通知的作用)

相关函数

创建/销毁bufferevent

struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options); 创建一个bufferevent事件对象,类比event_new()

参数:

  • base:event_base_new()的返回值
  • fd:绑定到bufferevent事件上的文件描述符
  • options:BEV_OPT_CLOSE_ON_FREE

返回值:成功创建的bufferevent事件对象

void bufferevent_socket_free(struct bufferevent *ev); 释放一个bufferevent事件对象

给bufferevent设置回调

对比event:event_new(fd, callback函数, ~)event_add() → 挂到event_base上

而bufferevent在new的时候并没有设置回调函数,需要另外使用函数bufferevent_setcb()设置回调函数

void bufferevent_setcb(struct bufferevent * bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);

参数:

  • bufev:bufferevent_socket_new()返回的bufferevent对象
  • readcb:设置bufferevent读缓冲对应的回调函数(内部使用bufferevent_read()读数据)
  • writecb:设置bufferevent写缓冲对应的回调函数(只起到通知作用,因此可以传NULL)
  • eventcb:设置事件回调,可以传NULL
  • cbarg:上述回调函数使用的参数

readcb对应的回调函数

函数类型:typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);

void read_cb(struct bufferevent *bev, void *cbarg) {
    .....
    bufferevent_read(); //读数据:相当于从bufferevent输入缓冲区中移除数据  
}

bufferevent_read()函数原型:size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);

writecb对应的回调函数

函数类型:同上

bufferevent_write()函数原型:int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);

eventcb对应的回调函数

函数类型:typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);

void event_cb(struct bufferevent *bev, short events, void *ctx) {
    .....}

参数events:一般传BEV_EVENT_CONNECTED(请求的连接过程已完成,实现客户端时可用)

禁用/启用缓冲区

新建的bufferevent对象写缓冲是enable状态,读缓冲是disable状态

void bufferevent_enable(struct bufferevent *bufev, short events); 启用缓冲区

参数:

  • bufev:bufferevent_socket_new()返回的bufferevent对象
  • events:EV_READ、EV_WRITE、EV_READ|EV_WRITE

客户端连接服务器

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); 等效于socket() + connect()

参数:

  • bev: bufferevent 事件对象(封装了fd)
  • address:connect()的参2
  • len:connect()的参3

服务器创建/释放监听器

头文件#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); 等效于socket() + bind() + listen() + accept()

参数:

  • base:event_base_new()的返回值
  • cb:回调函数,即建立连接之后,用户要做的通信操作
  • ptr:传递给回调函数,一般传event_base
  • flags:可识别的标志,一般传LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE(释放bufferevent同时关闭底层套接字 | 端口复用)
  • backlog:listen()的参2,指定连接上限,一般传-1,表示使用默认最大值
  • sa:服务器地址结构
  • socklen:服务器地址结构大小

返回值:成功创建的监听器

监听器回调函数

typedef void(evconnlistener_cb)(struct evconnlistener *listener, evutil _socket_t sock, struct sockaddr *addr, *int len, void *ptr);

参数:

  • listener:evconnlistener_new_bind()返回值
  • sock:用于通信的文件描述符
  • addr:客户端地址结构
  • len:客户端地址结构大小
  • ptr:evconnlistener_new_bind()参3传递进来的ptr,一般是event_base

ps:该回调函数由框架自动调用,不用我们手动调用,因此只需要知道参数含义即可

释放监听器

void evconnlistener_free(struct evconnlistener *lev);

libevent实现TCP连接

服务器

思路:

  1. 使用event_base_new()创建event_base
  2. 使用evconnlistener_new_bind()创建监听器,在其回调函数listner_cb()中,处理接受连接后的操作
  3. 监听器的回调函数被调用,说明有一个客户端连接上来,会得到一个cfd,用与跟客户端进行通信
  4. 在监听器的回调函数中:
    • 使用bufferevent_socket_new()创建一个bufferevent事件对象,将cfd封装到该事件对象中
    • 使用bufferevent_setcb()函数给该bufferevent对象的read、write、event设置回调函数
    • 设置该bufferevent对象的读缓冲、写缓冲的使能状态:enable/disable
    • 当监听的事件满足时,对应回调函数会被调用,接受/发送数据:bufferevent_read()/bufferevent_write()
  5. 使用event_base_dispath()启动循环监听
  6. 释放资源

ps:监听器只用于监听连接请求,bufferevent监听read、write、event请求

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>

// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg) {
    char buf[1024] = {0};   
    bufferevent_read(bev, buf, sizeof(buf));
    printf("client say: %s\n", buf);

    char *p = "我是服务器, 已经成功收到你发送的数据!";
    // 发数据给客户端
    bufferevent_write(bev, p, strlen(p)+1);
    sleep(1);
}

// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg) {
    printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\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 cb_listener(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *ptr) {
   printf("connect new client\n");

   struct event_base* base = (struct event_base*)ptr;
   // 通信操作
   // 添加新事件
   struct bufferevent *bev;
   bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

   // 给bufferevent缓冲区设置回调
   bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
   bufferevent_enable(bev, EV_READ);
}

int main(int argc, const char* argv[]) {

    // init server 
    struct sockaddr_in serv;

    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);

    struct event_base* base;
    base = event_base_new();
    // 创建套接字
    // 绑定
    // 接收连接请求
    struct evconnlistener* listener;
    listener = evconnlistener_new_bind(base, cb_listener, base, 
                                  LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 
                                  36, (struct sockaddr*)&serv, sizeof(serv));

    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_base_free(base);

    return 0;
}

客户端

思路:

  1. 使用event_base_new()创建event_base
  2. 使用bufferevent_socket_new()创建一个用于跟服务器通信的bufferevent事件对象
  3. 使用bufferevent_socket_connect()连接服务器
  4. 使用bufferevent_setcb()函数给该bufferevent对象的read、write、event设置回调函数
  5. 设置该bufferevent对象的读缓冲、写缓冲的使能状态:enable/disable
  6. 接受/发送数据:bufferevent_read()/bufferevent_write()
  7. 使用event_base_dispath()启动循环监听
  8. 释放资源

代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <arpa/inet.h>

void read_cb(struct bufferevent *bev, void *arg) {
    char buf[1024] = {0}; 
    bufferevent_read(bev, buf, sizeof(buf));

    printf("fwq say:%s\n", buf);

    bufferevent_write(bev, buf, strlen(buf)+1);
    sleep(1);
}

void write_cb(struct bufferevent *bev, void *arg) {
    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) {
    // 读数据
    char buf[1024] = {0};
    int len = read(fd, buf, sizeof(buf));

    struct bufferevent* bev = (struct bufferevent*)arg;
    // 发送数据
    bufferevent_write(bev, buf, len+1);
}

int main(int argc, const char* argv[]) {
    struct event_base* base = NULL;
    base = event_base_new();

    int fd = socket(AF_INET, SOCK_STREAM, 0);

    // 通信的fd放到bufferevent中
    struct bufferevent* bev = NULL;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

    // init server info
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

    // 连接服务器
    bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));

    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);

    // 设置读回调生效
	// bufferevent_enable(bev, EV_READ);

    // 创建事件
    struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
                                 read_terminal, bev);
    // 添加事件                     
    event_add(ev, NULL);

    event_base_dispatch(base);

    event_free(ev);
    
    event_base_free(base);

    return 0;
}


不准投币喔 👆

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇