Linux网络编程基础7(Linux下libevent的使用(不带缓冲区的event和带缓冲区的bufferent))

    技术2022-07-11  81

    Linux网络编程基础7(Linux下libevent的使用(不带缓冲区的event和带缓冲区的bufferent))

    1. libevent介绍和安装1.1 libevent简介1.2 libevent安装 2. 不带缓冲区的event使用流程2.1 创建事件处理框架——event_base2.1 事件创建 ——event_new2.2 设置未决/非未决事件2.2.1 设置未决事件——把event_new事件添加到事件处理框架上2.2.2 设置非未决——从树上摘下event_new事件 2.3 事件循环2.3.1 循环开始2.3.2 循环终止 2.4 释放事件——释放event_base事件2.5 libevent内部时间的状态转换流程 3. 使用eventd读写管道的实例3.1 使用event读管道3.2 使用event写管道 4. 带缓冲区的事件bufferevent4.1 bufferevent 理解4.2 使用 bufferevent流程步骤4.2.1 创建带缓冲区的事件4.2.2 套接字通信,客户端连接服务器的函数4.2.3 给申请的bufferevent的缓冲区中的读/写缓冲区设置回调函数4.2.4 设置bufferevent缓冲区的禁用和启用4.2.5 释放bufferevent操作 4.3 服务端的链接监听器 - evconnlistener4.3.1 创建evconnlistener4.3.2 启用和禁用 evconnlistener(用得少)4.3.3 调整 evconnlistener 的回调函数(用得少)4.3.4 释放evconnlistener对应的资源 5. bufferevent的使用实例5.1 服务端5.2 客户端

    1. libevent介绍和安装

    1.1 libevent简介

    Libevent 是一个轻量级的开源高性能网络库,用来提高网络编程(高并发)的开发效率。其封装了socket通信和封装了IO复用。 优点是精简, 专注于网络, 性能高。 主要是事件驱动。

    1.2 libevent安装

    第一种: 官网:http://libevent.org/ 下载下来之后:

    ./configure生成makefilemake 编译源代码,生成一些库,动态, 静态,可执行程序sudo make install 将数据拷贝到对应的目录,如果目录不存在, 创建该目录

    第二种:

    $ sudo apt-get install libevent-dev

    我使用的第二种。

    2. 不带缓冲区的event使用流程

    常用的头文件是:

    #include <event2/event.h> #include <event2/listener.h>

    创建一个事件处理框架——使用event_base_new函数创建struct event_base结构体变量

    创建事件

    带缓冲区的——bufferevent_socket_new创建一个带缓冲区的事件 ->给缓冲区设置回调函数(用bufferevent_setcb()函数来进行设置) -> 开始事件循环

    不带缓冲区的——struct event(通过event_new函数获取该结构体变量)->event_add将创建出的事件添加到事件处理框架中 ->event_base_duspatch函数启动事件循环来处理添加的事件

    释放资源——event_base_free(释放框架),event_free(释放事件)

    编译的时候要加-levent

    2.1 创建事件处理框架——event_base

    使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。事件处理框架里面封装了很多IO转接函数,select、poll、epoll。

    事件处理框架在用户眼中就是一个结构体,里面封装了大量的回调函数,他就相当于之前写的epoll模型。

    相当于epoll红黑树的树根底座抽象层, 完成对event_base的封装每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”,或者说后端.

    1、创建event_base,此时消息循环还没启动

    struct event_base* event_base_new(void);

    通常写成:

    struct event_base* base = NULL; base = event_base_new();

    或者:

    struct event_base* base = event_base_new(); 失败返回NULL

    2、释放event_base

    event_base_free(struct event_base* base);

    3、循环监听base对应的事件, 等待条件满足

    event_base_dispatch(struct event_base* base);

    注意: 在进程中使用libevent的时候,fork会使得子进程也有一个base结构体,此时在使用时,需要对子进程中的base重新初始化

    子进程创建成功之后, 父进程可以继续使用event_base子进程中需要继续使用event_base需要重新进程初始化 int event_reinit(struct event_base* base);

    2.1 事件创建 ——event_new

    创建新事件——没有缓冲区

    #define EV_TIMEOUT 0x01 // 废弃 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 // 信号,libevent封装了信号相关的操作 #define EV_PERSIST 0x10 // 持续触发 #define EV_ET 0x20 // 边沿模式 //回调函数的函数原型,回调函数用于处理事件 //三个参数分别是int类型的文件描述符,文件描述符对应的事件以及操作 typedef void (*event_callback_fn)(evutil_socket_t, short, void *); struct event *event_new( //对epollin和epollout进行了封装 struct event_base *base, evutil_socket_t fd, // 文件描述符 实际上是int类型 short what, // 对应事件,读事件,写事件 event_callback_fn cb, // 事件的处理动作 void *arg );

    调用event_new()函数之后, 新事件处于已初始化和非未决状态

    未决就是有资格被处理,但是还没没被处理的事件非未决就是现在还没有资格被处理的事件

    2.2 设置未决/非未决事件

    2.2.1 设置未决事件——把event_new事件添加到事件处理框架上

    构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到event_base,之后 非未决事件 状态变成 未决事件.

    int event_add( struct event *ev, const struct timeval *tv ); 参数tv: NULL: 事件被触发, 对应的回调被调用 tv = {0, 100}, 如果设置了时间, 在该时间段内检测的事件没被触发, 时间到达之后, 回调函数还是会被调用 返回值: 函数调用成功返回0, 失败返回-1

    2.2.2 设置非未决——从树上摘下event_new事件

    对已经初始化的事件调用 event_del()将使其成为非未决和非激活的,如果事件不是未决的或者激活的,调用将没有效果。

    int event_del(struct event *ev); 返回值: 成功时函数返回 0,失败时返回-1

    2.3 事件循环

    event_base内部维护了一个消息循环,消息循环用于检测事件是否被触发了。

    一旦有了一个已经注册了某些事件的 event_base, 就需要让 libevent 等待事件并且通知事件的发生。

    2.3.1 循环开始

    启动event_base的事件循环

    #define EVLOOP_ONCE 0x01 事件只会被触发一次 事件没有被触发, 阻塞等 #define EVLOOP_NONBLOCK 0x02 非阻塞 等方式去做事件检测 不关心事件是否被触发了 #define EVLOOP_NO_EXIT_ON_EMPTY 0x04 没有事件的时候, 也不退出轮询检测 int event_base_loop(struct event_base *base, int flags); //用得少,可以指定循环的状态 返回值: 正常退出返回0, 失败返回-1

    最常用的还是:

    int event_base_dispatch(struct event_base* base); //常用 等同于没有设置标志的 event_base_loop ( )将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者event_base_loopexit()为止。

    循环过程中调用回调函数来处理事件

    2.3.2 循环终止

    返回值: 成功 0, 失败 -1

    方法一:

    如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出(相对柔和)

    int event_base_loopexit( struct event_base *base, const struct timeval *tv //设置多久之后退出循环 ); struct timeval { long tv_sec; long tv_usec; };

    方法二:

    让event_base 立即退出循环(强退)

    int event_base_loopbreak(struct event_base *base);

    2.4 释放事件——释放event_base事件

    void event_free(struct event *event);

    2.5 libevent内部时间的状态转换流程

    通过event_base产生事件处理框架通过event_new来创建事件,此时事件是非未决状态通过event_add函数将事件变成未决状态(此时事件才有被处理的资格)。当事件循环被开启(dispatch函数的作用)并且对应的事件被触发时,事件被激活。被激活的事件通过回调函数来进行处理,回调函数是事件循环时调用的。被处理过的事件又变成了非未决状态。event_add指定事件为持续触发:EV_PERSIST(这是一个宏)

    3. 使用eventd读写管道的实例

    3.1 使用event读管道

    #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> //回调函数的函数原型,回调函数用于处理事件 //三个参数分别是int类型的文件描述符,文件描述符对应的事件以及操作 //typedef void (*event_callback_fn)(evutil_socket_t, short, void *); // 对操作处理函数 回调函数read_cb void read_cb(evutil_socket_t fd, short what, void *arg) { // 读管道 char buf[1024] = {0}; //将管道中的数据读入buf中 int len = read(fd, buf, sizeof(buf)); printf("data len = %d, buf = %s\n", len, buf); //判断事件what里面是否有标志位EV_READ printf("read event: %s", what & EV_READ ? "Yes" : "No"); } // 读管道 int main(int argc, const char* argv[]) { unlink("myfifo"); //创建有名管道,0664是用户读写权限,最终的权限不是0664, mkfifo("myfifo", 0664); // open file,设置非阻塞O_NONBLOCK是为了让我们观察清除,默认是阻塞的 int fd = open("myfifo", O_RDONLY | O_NONBLOCK); if(fd == -1){ perror("open error"); exit(1); } // 读管道 event_base创建事件处理框架 struct event_base* base = NULL; base = event_base_new(); // event_new 创建事件 struct event* ev = NULL; //读管道,所以开启读操作EV_READ EV_PERSIST设置持续检测 //read_cb是回调函数 NULL的位置可以用来传参,没有参数就传null ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL); // 添加事件,第二个参数设置事件超时,设置为NULL则一直阻塞等待事件的发生 //如果设置成10,则阻塞等待10秒,十秒只能事件没有被触发,则强制调用该函数 event_add(ev, NULL); // 事件循环 event_base_dispatch(base); // dispatch退出之后,才会执行下面的代码 //释放资源 event_free(ev); //释放事件 event_base_free(base); //释放事件处理框架 close(fd); //释放文件描述符 return 0; }

    3.2 使用event写管道

    #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 = 0; sprintf(buf, "hello, world == %d\n", num++); write(fd, buf, strlen(buf)+1); } // 写管道 int main(int argc, const char* argv[]){ // open file 设置为非阻塞样式的写O_WRONLY | O_NONBLOCK 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_WRITE ——检测的写缓冲区是否有空间写 这里只写一次,因为没设置EV_PERSIST 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; }

    4. 带缓冲区的事件bufferevent

    使用时要加头文件:

    #include<event2/bufferevent.h>

      套接字通讯时,我们不常使用上面介绍的event事件,而是使用带缓冲区的事件(bufferevent)。

      一个文件描述符对应内核中的一块缓冲区,文件描述符可以操作读写缓冲区。libevent也提供了一块缓冲区,在收到数据的时候,libevent会将数据从内核中读取,然后存入自己的缓冲区中。

      bufferevent自带缓冲区,缓冲区分为两部分,一部分是读缓冲区,一部分是写缓冲区。event不带缓冲区。

    4.1 bufferevent 理解

    bufferevent是libevent为IO缓冲区操作提供的一种通用机制bufferevent 由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。读缓冲区和写缓冲区内部用队列实现的。因此数据先进先出,且读过之后数据就没了。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是, bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。当读缓冲区中有数据时,读缓冲区对应的回调函数会被调用。在该回调函数的作用是读数据,对应的函数是bufferevent_read()(默认)当写缓冲区中的数据被发送出去时,写缓冲区的回调函数(默认)就会被调用,该函数用于通知你数据已经被发送出去了。向写缓冲区写入数据的函数是bufferevent_write()在写缓冲区中只要有数据,数据就会被自动的发送出去,发送数据的工作不是bufferevent_write()做的。

    4.2 使用 bufferevent流程步骤

    4.2.1 创建带缓冲区的事件

    可以使用 bufferevent_socket_new()函数创建一个带缓存的事件

    struct bufferevent *bufferevent_socket_new( struct event_base *base, evutil_socket_t fd, //文件描述符 enum bufferevent_options options //对应的枚举选项 ); 用的最多的options(就是上面函数的第三个参数)—— BEV_OPT_CLOSE_ON_FREE 该宏用于释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等 struct bufferevent也是一个 event成功时函数返回一个 bufferevent,失败则返回 NULL。

    4.2.2 套接字通信,客户端连接服务器的函数

    int bufferevent_socket_connect( struct bufferevent *bev, // 封装了用于通讯的文件描述符 struct sockaddr *address, // server端的IP和端口 int addrlen // 地址长度,即第二个参数的结构体大小,sizeof即可获取 ); address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知libevent套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。连接完成之前可以向输出缓冲区添加数据。

    4.2.3 给申请的bufferevent的缓冲区中的读/写缓冲区设置回调函数

    void bufferevent_setcb( struct bufferevent *bufev, // bufferevent_socket_new的返回值 bufferevent_data_cb readcb, // 读缓冲区对应的回调函数,bufferevent_data_cb是函数指针的名字 // 调用bufferevent_read()函数读读缓冲区中的取数据 bufferevent_data_cb writecb, // 写缓冲区对应的回调函数 // 该回调函数通知你你写入了数据,如果不用该参数,传入NULL即可 bufferevent_event_cb eventcb, // 事件对应的回调函数,事件比如连接成功,断开连接,操作异常 void *cbarg // 该指针指向的内存会传给函数的第二个参数,即readcb );

    读缓冲区数据调用的回调函数(系统默认调用的) 当读缓冲区中有数据时,读缓冲区对应的回调函数会被调用。

    size_t bufferevent_read( struct bufferevent *bufev, //bufferevent函数地址 void *data, //buffer的地址 size_t size );

    写缓冲区调用的回调函数

    int bufferevent_write( struct bufferevent *bufev, const void *data, size_t size );

    读缓冲区数据的回调函数怎么写? bufferevent_data_cb(这是一个函数指针的名字)的函数原型为

    typedef void (*bufferevent_data_cb)( struct bufferevent *bev, //bufferevent的地址 void *ctx //接受传给回调函数的参数 ); ctx会收到bufferevent_setcb()函数(该函数的第二个参数是一个回调函数readcb,该回调函数的函数原型就是上面的代码)的第五个参数cbarg(该参数指向一块内存)传递给给回调函数readcb的内存。

    bufferevent_setcb函数的第四个参数bufferevent_event_cb的函数原型

    typedef void (*bufferevent_event_cb)( struct bufferevent *bev, short events, //通过判断标志位可以确定发生了什么事件 void *ctx ); events参数: EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。 BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。 BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR()。 BEV_EVENT_TIMEOUT:发生超时。 BEV_EVENT_EOF:遇到文件结束指示。 BEV_EVENT_CONNECTED:请求的连接过程已经完成

    4.2.4 设置bufferevent缓冲区的禁用和启用

    如果设置缓冲区不可用,则设置的对应回调函数就不会被调用了。 默认情况下,写缓冲区(EV_WRITE)是可用的,读缓冲区(EV_READ)是不可用的

    void bufferevent_enable( //设置缓冲区是否可可用 struct bufferevent *bufev, //bufferevent的地址 short events //对应的事件 ); void bufferevent_disable( struct bufferevent *bufev, short events ); short bufferevent_get_enabled( //用于判断缓冲区是否可用 struct bufferevent *bufev ); 可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

    4.2.5 释放bufferevent操作

    void bufferevent_free(struct bufferevent *bev);

    4.3 服务端的链接监听器 - evconnlistener

    4.3.1 创建evconnlistener

    在写socket的server端时,需要四步

    创建监听socket绑定监听等待并接受连接请求

    链接监听器只需要调用下面的函数即可完成上述四步,这就是链接监听器的优点:

    struct evconnlistener *evconnlistener_new_bind( struct event_base *base, // 事件处理框架 evconnlistener_cb cb, // 回调函数,接受连接请求之后,用户接下来要做的事情,写在这个回调函数里面即可 void *ptr, // 用于给回调函数传参 unsigned flags, // 常用的是 LEV_OPT_CLOSE_ON_FREE 和 LEV_OPT_REUSEABLE int backlog, // 绑定需要一个backlog,该值不能超过128,传入-1,则使用默认的最大的值 const struct sockaddr *sa, // 服务器的 IP 和 端口 信息 int socklen // struct sockaddr 的内存大小 );

    evconnlistenner_cb的函数原型(回调函数由系统调用,因此传参不会出现问题,但是实现回调函数的函数体时,需要知道参数都是什么。)

    typedef void (*evconnlistener_cb)( struct evconnlistener *listener, // 传入参数,evconnlistener_new_bind的返回值 evutil_socket_t sock, // 用于通信的文件描述符 struct sockaddr *addr, // 客户端的IP和端口信息 int len, // addr的地址大小 void *ptr // 用于获取传给回调函数的内存 );

    另一个相同功能的函数(不需要掌握)(该函数不负责绑定,需要在外部绑定之后,把绑定好的文件描述符作为参数传入,而上面的函数不需要传入文件描述符,因为他会给你创建一个)

    struct evconnlistener * evconnlistener_new( struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd );

    4.3.2 启用和禁用 evconnlistener(用得少)

    int evconnlistener_disable(struct evconnlistener *lev); int evconnlistener_enable(struct evconnlistener *lev);

    4.3.3 调整 evconnlistener 的回调函数(用得少)

    链接监听器在创建的时候指定了一个回调函数,如果你不想回调函数按照原来指定的方式去工作了,可以重新设置回调函数

    void evconnlistener_set_cb( struct evconnlistener *lev, //地址 evconnlistener_cb cb, //新的处理方式 void *arg //回调函数传入的参数 );

    4.3.4 释放evconnlistener对应的资源

    void evconnlistener_free(struct evconnlistener *lev);

    5. bufferevent的使用实例

    5.1 服务端

    相当于一个封装了epoll的,可以处理高并发的服务器

    #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)); char* p = "我已经收到了你发送的数据!"; printf("client say: %s\n", p); // 向缓冲区中写数据 bufferevent_write(bev, p, strlen(p)+1); printf("====== send buf: %s\n", p); } // 写缓冲区回调 void write_cb(struct bufferevent *bev, void *arg){ printf("我是写缓冲区的回调函数...\n"); } // 事件回调 events可用于判断当前发生了什么事件 void event_cb(struct bufferevent *bev, short events, void *arg){ //events & BEV_EVENT_EOF的结果非零时,则进入该if语句 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"); } //连接完成之后,对应通信操作 //evconnlistener_new_bind函数将base里面的用于通信的文件描述符fd作为参数传递给了回调函数 cb_listener void cb_listener(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *ptr){ printf("connect new client\n"); //获取传给回调函数的base(ptr指针指向了这块base) struct event_base* base = (struct event_base*)ptr; // 通信操作(主要是接受和发送数据) // 添加新事件 struct bufferevent *bev=NULL; //将文件描述符封装成带缓冲区的事件bufferevent //BEV_OPT_CLOSE_ON_FREE设置自动释放资源 //fd是用于通信的文件描述符 bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); // 此时已经获取了bufferevent缓冲区,该缓冲区分为读缓冲区和写缓冲区两部分 //给bufferevent缓冲区注册回调函数,注册之后不会被马上调用,操作系统会在时机适合的时候去调用回调函数 bufferevent_setcb(bev, //bufferevent_socket_new得到的缓冲区 read_cb, //读回调函数 write_cb, //写回调函数,可以设置为NULL,这样写缓冲区就不设置回调函数了 event_cb, //事件回调函数 NULL); //用于设置是否需要往回调里面传数据 //启用读缓冲区为可用的,否则读回调函数不会被调用,写回调默认是启用的 bufferevent_enable(bev, EV_READ); } int main(int argc, const char* argv[]){ //初始化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); //设置IP //创建事件处理框架 struct event_base* base= event_base_new(); // 创建监听的套接字、绑定、接收连接请求 struct evconnlistener* listener=NULL; //evconnlistener_new_bind函数会自动创建并接受链接请求,因此函数内部会存在阻塞 //当有新连接的时候,回调函数cb_listener就会被调用 //下行注释从左到右依次是函数evconnlistener_new_bind中的参数解释 //时间处理框架,回调函数,传入回调函数的参数,设置端口自动释放和复用,backlog设置为-1则使用默认最大的值,服务器IP地址和端口 listener = evconnlistener_new_bind(base, cb_listener, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr*)&serv, sizeof(serv)); //进入事件循环 event_base_dispatch(base); evconnlistener_free(listener); event_base_free(base); return 0; }

    5.2 客户端

    #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/bufferevent.h> #include<event2/listener.h> #include<arpa/inet.h> void read_cb(struct bufferevent *bev, void *arg){ char buf[1024] = {0}; //接受服务端发来的数据 len是读到的字节数 int len=bufferevent_read(bev, buf, sizeof(buf)); printf("Server say: %s\n", buf); //给对方发数据 写这句会出现bug,因为从终端给服务器发数据之后,服务器会有返回信息 //收到服务器的返回信息之后,读缓冲区中有了数据,会调用读回调函数(即read_cb) //完成读取之后,下面的函数会在写缓冲区中写入数据,这就被误以为是调用了回调函数send_cb造成的 //因此会形成闭环,无限的发送数据给服务器(服务器对于收到的数据会进行反馈,因此会无限的回复数据) //bufferevent_write(bev, buf, len + 1); } //写回调函数 void write_cb(struct bufferevent *bev, void *arg){ printf("I am Write_cb function....\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("成功连接到服务器\n"); return; } bufferevent_free(bev); printf("free bufferevent...\n"); } //终端接受输入,将接受的数据发送给server void send_cb(evutil_socket_t fd, short what, void *arg){ char buf[1024] = {0}; //获取bev地址 struct bufferevent* bev = (struct bufferevent*)arg; printf("请输入要发送的数据: \n"); //读取终端中的数据 int len=read(fd, buf, sizeof(buf)); //将从终端读到的数据写到bufferevent对应的写缓冲区 bufferevent_write(bev, buf, len+1); } int main(int argc, const char* argv[]){ //创建时间处理框架 struct event_base* base= event_base_new(); int fd = socket(AF_INET, SOCK_STREAM, 0); //BEV_OPT_CLOSE_ON_FREE可用帮助我们在释放资源的时候,释放文件描述符指向的资源 struct bufferevent* bev= bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); //连接服务器 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); //链接服务器 ,fd被封装成bev了,所以这个和connect函数差不多 bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv)); // 给缓冲区设置回调 bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); //启动读缓冲区,否则读回调函数不会被调用,默认情况下,写缓冲区是启用的,读缓冲区是关闭的 bufferevent_enable(bev, EV_READ | EV_PERSIST); //接受键盘输入 // 创建一个事件 STDIN_FILENO对应于终端的读操作,因为我们向终端写入数据 //EV_READ | EV_PERSIST 表示持续的读 send_cb是回调函数 struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, send_cb, bev); //将事件添加到base上 //第二个参数是事件等待的时间,如果该时间内事件没被触发,则强制执行该函数,如果不设置时间,则阻塞等待事件被触发 event_add(ev, NULL); //启动事件循环 event_base_dispatch(base); event_base_free(base); return 0; }
    Processed: 0.015, SQL: 9