开源。精简。跨平台(Windows、Linux、maxos、unix)。专注于网络通信(不一定非用在网络当中,比如下面的读写管道)。
libevent特性:基于"事件",面向“文件描述符”的异步(回调)通信模型。
异步:函数"注册"时间和函数真正被执行的时间不同,函数真正是被内核调用(等待某一条件满足)
【事件库:就干一件事,监听文件描述符的库机制。
先创建一个事件底座,然后创建你想监听的时间,插到底座上,libevent底座就会帮忙监听事件,当事件发生时,就会自动调用事件的回调函数。】
- //创建event_base(事件底座)
- struct event_base* base = event_base_new();
-
- //创建事件event
- 常规事件event:event_new();
- 例如:
- struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
-
- buffer event: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); //调用
-
- //释放event_base
- event_base_free(base);
- /*
- This example program provides a trivial server program that listens for TCP
- connections on port 9995. When they arrive, it writes a short message to
- each client connection, and closes each connection once it is flushed.
- Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
- */
-
- #include
- #include
- #include
- #include
- #ifndef _WIN32
- #include
- # ifdef _XOPEN_SOURCE_EXTENDED
- # include
- # endif
- #include
- #endif
-
- #include
- #include
- #include
- #include
- #include
-
- static const char MESSAGE[] = "Hello, World!\n";
-
- static const int PORT = 9995;
-
- static void listener_cb(struct evconnlistener *, evutil_socket_t,
- struct sockaddr *, int socklen, void *);
- static void conn_writecb(struct bufferevent *, void *);
- static void conn_eventcb(struct bufferevent *, short, void *);
- static void signal_cb(evutil_socket_t, short, void *);
-
- //main函数中是使用框架,剩下外面的函数就是事件的回调
- int main(int argc, char **argv)
- {//创建事件底座指针
- struct event_base *base;
- struct evconnlistener *listener;
- //创建事件指针
- struct event *signal_event;
- //创建套接字的地址结构
- struct sockaddr_in sin;
- #ifdef _WIN32
- WSADATA wsa_data;
- WSAStartup(0x0201, &wsa_data);
- #endif
- //真正创建事件底座
- base = event_base_new();
- if (!base) {
- fprintf(stderr, "Could not initialize libevent!\n");
- return 1;
- }
-
- memset(&sin, 0, sizeof(sin));
- //初始化套接字的地址结构
- sin.sin_family = AF_INET;
- sin.sin_port = htons(PORT);
- //一个函数干了套接字通信的 socket、listen、bind、accept几个函数的事情
- listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
- LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
- (struct sockaddr*)&sin,
- sizeof(sin));
-
- if (!listener) {
- fprintf(stderr, "Could not create a listener!\n");
- return 1;
- }
- //注册一个信号事件
- signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
-
- if (!signal_event || event_add(signal_event, NULL)<0) {
- fprintf(stderr, "Could not create/add a signal event!\n");
- return 1;
- }
- //循环监听,相当于 while循环加里面的epoll_wait;下面的循环有很大概率没有机会调用
- event_base_dispatch(base);
- //释放之前创建的事件
- evconnlistener_free(listener);
- event_free(signal_event);
- event_base_free(base);
-
- printf("done\n");
- return 0;
- }
-
- //下面是千篇一律,定义了三个回调,分别独立对应三个事件。看一个就行
- static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
- struct sockaddr *sa, int socklen, void *user_data)
- {
- struct event_base *base = user_data;
- struct bufferevent *bev;
-
- bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
- if (!bev) {
- fprintf(stderr, "Error constructing bufferevent!");
- event_base_loopbreak(base);
- return;
- }
- bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
- bufferevent_enable(bev, EV_WRITE);
- bufferevent_disable(bev, EV_READ);
-
- bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
- }
-
- static void conn_writecb(struct bufferevent *bev, void *user_data)
- {
- struct evbuffer *output = bufferevent_get_output(bev);
- if (evbuffer_get_length(output) == 0) {
- printf("flushed answer\n");
- bufferevent_free(bev);
- }
- }
-
- static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
- {
- if (events & BEV_EVENT_EOF) {
- printf("Connection closed.\n");
- } else if (events & BEV_EVENT_ERROR) {
- printf("Got an error on the connection: %s\n",
- strerror(errno));/*XXX win32*/
- }
- /* None of the other events can happen here, since we haven't enabled
- * timeouts */
- bufferevent_free(bev);
- }
-
- static void signal_cb(evutil_socket_t sig, short events, void *user_data)
- {
- struct event_base *base = user_data;
- struct timeval delay = { 2, 0 };
-
- printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
-
- event_base_loopexit(base, &delay);
- }
- struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fd cb,void* arg);
- //
- 参数:
- base:基事件, 也就是event_base_new()的返回值
- fd:绑定到event上的文件描述符,监听的对象
- what:文件描述符对应的事件(r/w/e),监听类型是什么
- what的取值:
- EV_READ:读一次后,退出循环
- EV_WRITE:写一次,退出循环
- EV_PERSIST:持续触发,可以理解为while(read())或while(write())
- cb:一旦满足监听条件,回调的函数
- arg:回调函数的参数
-
- //返回值:成功返回创建的事件对象event
-
参数跟上面一致(实际用时候不用传参)
- typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void* arg);
- f
- int event_add(struct event* ev,const strcut timeval* tv);
- //参数:
- ev是要添加的事件对象,就是event_new的返回值
- tv一般传NULL,表示一直等到事件被触发,回调函数才会被调用。如果传非0,会等待事件被触发,如果事件一直不触发,时间到,回调函数依然会被调用
-
- //返回值:成功返回0;失败返回-1
- int event_del(struct event* ev);
- //ev是要摘下的事件对象,就是event_new的返回值
- int event_free(strcut event* ev);
- //ev是要销毁的事件对象,就是event_new的返回值
- void perr_exit(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 writer:%s\n", buf);
- printf("what=%s\n", what & EV_READ ? "Yes" : "No");
- sleep(1);
-
- return;
- }
-
- int main(int argc, char* argv[]) {
- int ret = 0;
- int fd = 0;
-
- unlink("myfifo");
- mkfifo("myfifo", 0644);
-
- fd = open("myfifo", O_RDONLY | O_NONBLOCK);//借助epoll反应堆,默认都是非阻塞
- if (fd == -1)
- perr_exit("open error");
- //读管道时候,管道中不一定有数据,此时就需要有事件概念介入了
- //创建事件基座
- struct event_base* 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_base_free(base);
- close(fd);
- return 0;
- }
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- // 对操作处理函数
- 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);
-
- sleep(1);
- }
-
-
- // 写管道
- int main(int argc, const char* argv[])
- {
- // open file
- //int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
- int fd = open("myfifo", O_WRONLY);
- 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);
- ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);
-
- // 添加事件
- event_add(ev, NULL);
-
- // 事件循环
- event_base_dispatch(base);
-
- // 释放资源
- event_free(ev);
- event_base_free(base);
- close(fd);
-
- return 0;
- }
未决:有资格被处理,但还没有被处理 (只剩数据没到达了)
非未决:没有资格被处理 (除了数据没到达,还有其他监听条件没满足,比如事件没add)
小注意:事件被处理完,如果设置了persist,add会被再调用一次
bufferevent的事件对象fd有两个缓冲区。
读:读缓冲区有数据,读回调函数被调用,使用bufferevent_read()读数据
写:使用bufferevent_write,向写缓冲中写数据,该缓冲区中有数据自动写出,写完后,回调函数被调用(鸡肋)
原理:bufferent利用队列实现两个缓冲区(数据只能读一次,读走就没, FIFO)
- struct bufferevent* bufferevent_socket_new(struct event_base* base,
- evutil_socket_t fd,
- enum bfferevent_options options)
- //入参
- base:基事件,event_base_new函数的返回值
- fd:封装到bufferevent内的fd(绑定在一起),即lfd
- enum表示枚举类型,一般取BEV_OPT_CLOSE_ON_FREE(关闭时释放底层套接字)
-
- //返回
- 成功返回bufferevent事件对象
对比event,有区别。这个不是在 new的时候设置的回调,而是借助后面的 bufferevent_setcb函数专门设置回调。
- void bufferevent_socket_free(struct bufferevent* ev)
- //入参
- bufev:bufferevent_socket_new()函数的返回值
- readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())
- writecb:鸡肋,传NULL即可
- eventcb:可传NULL
- cbarg:回调函数的参数
- 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()读,而不是read())
-
- writecb:鸡肋,传NULL即可
-
- eventcb:备用回调,比如异常回调,也可传NULL
-
- cbarg:三个回调函数的(相同)参数
- void read_cb(struct bufferevent* bev,void* arg){
- ...
- bufferevent_read();
- }
为什么 read_cb里面不封装read,因为没有fd给read传。fd通过new函数封装好的bufferevent传。
size_t bufferevent_read(struct bufferevent* bufev,void* data,size_t size);
- int bufferevent_write(struct bufferevent* bufev,const void* data,size_t size)
- typedef void (*bufferevent_event_cb)(struct bufferevent* bev,short events,void* ctx)
默认新建的bufferevent写缓冲是enable的,而读缓冲是disable的
通过下面函数操作缓冲区读写使能:
- void bufferevent_enable(struct bufferevent* bufev,short events); //启用缓冲区
- void bufferevnet_disable(struct bufferevent* bufev,short events); //禁用
-
- /*例如:开启读缓冲*/
- void bufferevent_enable(bufev,EV_READ);
-
- //events的值可传入三个宏:
- EV_READ
- EV_WRITE
- EV_READ|EV_WRITE
也可获取缓冲区的禁用状态:
- short bufferevent_get_enable(struct bufferevent* bufev)
- int bufferevent_socket_connect(struct bufferevent* bev,struct sockaddr* address,int addrlen);
-
- //入参
- bev:bufferevent事件对象(封装了fd)
- address,len:等同于connect()的参2和参3
这一个函数可以完成socket(),bind(),listen(),accept()
四个函数的作用
- struct evconnlistener* evconnlistener_new_bind(struct event_base* base,
- evconnlistener_cb cb,
- void* ptr,
- unsigned flags,
- int backlog,
- const struct sockaddr* sa,
- int socklen);
- //入参
- cb:监听回调函数(建立连接后用户要做的操作)
- ptr:回调函数的参数
- flags:可识别的标志,通常传:
- LEV_OPT_CLOSE_ON_FREE(释放bufferevent时关闭底层传输端口, 这将关闭底层套接字, 释放底层bufferevent等)
- LEV_OPT_REUSEABLE(可以设置端口复用)
- backlog:相当于listen的参2,最大连接数, 传-1表示使用默认的最大值
- sa:服务器自己的地址结构
- socklen:sa的大小
回调函数 evconnlistener_cb cb:
监听成功后,说明客户端链接成功,原来的accept传出的客户端地址结构,现在在这里传出。
- typedef void (*evconnlistener_cb)(struct evconnlistener* listener,
- evutil_socker_t sock,
- struct sockaddr* addr,
- int len,
- void* ptr);
- //入参:
- listener:evconnlistener_new_bind 的返回值
- sock:用于通信的文件描述符,即cfd
- addr:传出,客户端的地址结构
- len:客户端地址结构的长度
- ptr:外部ptr传进来的值
- 创建基事件event_base
- 使用evconlistener_new_bind()创建监听服务器,用来专门监听客户端链接事件,有新链接上来,就调用其回调函数(完成
socket(),bind(),listen(),accept()
四个函数的作用)
- 设置evconlistener_new_bind()回调函数listner_cb(),该回调函数被调用,说明有一个新的客户端连接上来,会得到一个新的fd,用于跟客户端通信。
- 创建bufferevent事件对象:bufferevent_socket_new(),将fd封装到这个事件对象中
- 使用bufferevent_setcb()函数给bufferevent的read,write,event设置回调函数
- 设置读写缓冲enable
- 启动循环event_base_dispath(),监听通信事件()
- 当监听的事件满足时,read_cb会被调用,在其内部bufferevent_read(),读
- 释放连接
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- // 读缓冲区回调
- 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);//执行完后回调write_cb
- 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;
-
- //创建出用于通信的socket事件对象bufferevent,并给bufferevent缓冲区设置回调和读使能
- struct bufferevent *bev;
- //2参fd被封装在新的事件对象 bufferevent中
- bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
- 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;
- }
- 创建event_base
- 使用bufferevent_socket_new()创建一个用跟服务器通信的bufferevent事件对象
- 使用bufferevent_socket_connect连接服务器
- 使用bufferevent_setcb()给bufferevent对象的read,write,event设置回调
- 设置bufferevent对象的读写缓冲区使能 (4、5放connect前应该也没问题)
- 接受,发送数据bufferevent_read()/bufferevent_write()
- 启动循环监听event_base_dispath()
- 释放资源
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- 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;
- }
问自己:bufferevent和epoll反应堆的关系???