文章目录

  • 一、事件event和事件管理器event_base介绍
  • 二、libevent流程简介(注册->检测->分派)
  • 三、libevent的好处
  • 四、代码比较
    • 4.1 原来reactor代码
    • 4.2 libevent封装reactor的代码
      • 4.2.1 单reactor的封装
      • 4.2.2 memcached 多reactor的封装(多个网络线程中处理)
  • 五、异步请求池的实现
  • 六、libevent接口
    • 6.1 struct event_base* event_base_new 创建事件管理器
    • 6.2 event_new 创建事件
    • 6.3 event_set 设置事件
    • 6.3 event_base_set 设置事件管理器和事件的映射关系
    • 6.4 event_add 注册事件,包括时间事件;相当于 epoll_ctl;
    • 6.5 event_del 注销事件
    • 6.6 int event_base_loop 进入事件循环
    • 6.7 void event_base_free(struct event_base*) 释放event_base结构体
    • 6.8 int event_reinit(struct event_base* base) fork子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化
    • 6.9 int event_base_dispatch(struct event_base* base),程序会一直在while循环执行epoll_wait()函数
      • 注意:event_new 相当于 malloc + event_set + event_base_set
    • 6.10 bufferevent读写数据API(操作evbuffer的不删除缓冲区数据)
    • 6.11 bufferevent连接监听器evconnlistener_new_bind和evconnlistener_new(与前面的区别是认为socket已经初始化好,bind完成,甚至也可以做完listen)
    • 6.12 evsignal_new
    • 6.13 bufferevent_socket_connect
    • 6.13 bufferevent_setcb设置读写对应的回调函数
    • 6.14 bufferevent_enable,如果相应事件不置为true,bufferevent是不会读写数据的
  • 七、总体流程
    • 普通的events事件的使用方法
    • 实操:使用libevent编写tcp服务器流程
    • bufferevent事件流程
  • 八、官方测试代码解析
    • hello_world.c(libevent跨平台服务器代码)
    • time-test.c(空)
    • signal-test.c(空)
    • linux版本的libevent客户端代码(转移到其他博文,修改为跨平台代码)
    • linux版本libevent服务端代码(比起上面那个服务器接受的客户端有限,非跨平台)
  • libevent就是对reactor模式简单的封装,用了libevent就不需要关注平台特性,无论是linux、window还是mac平台,将网络io处理转化为事件处理

一、事件event和事件管理器event_base介绍

  • event 时间(类似epoll处理的相关的事件)

事件包括:(自己网络编程的时候耦合太高)
1.连接的建立 3次握手
2.连接断开 4次挥手
3.消息的到达 read()
4.消息发送完毕 write()

  • event_base 事件管理器(类似epoll\poll\selcet)

二、libevent流程简介(注册->检测->分派)

1.注册感兴趣的事件-event_add(我们需要写的)
2.事件管理器检测事件的种类-
3.同步的分派异步的请求处理-callback(我们需要写的)

三、libevent的好处

1.平台无关
2.将网络io转化为事件的处理
3.忽略具体的参数细节,io函数的细节,errno的返回值等
4.对具体事件的封装(事件举例:网络io事件、定时事件、信号事件)

四、代码比较

4.1 原来reactor代码

  • 原来进行网络编程的时候,连接断开,连接收发数据耦合在一起了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>#include <errno.h>
#include <sys/epoll.h>#define BUFFER_LENGTH       1024
#define LISTEN_PORT         100struct sockitem { //int sockfd;int (*callback)(int fd, int events, void *arg);char recvbuffer[BUFFER_LENGTH]; //char sendbuffer[BUFFER_LENGTH];int rlength;int slength;
};// mainloop / eventloop --> epoll -->
struct reactor {int epfd;struct epoll_event events[512];};struct reactor *eventloop = NULL;int recv_cb(int fd, int events, void *arg);int send_cb(int fd, int events, void *arg) {struct sockitem *si = (struct sockitem*)arg;send(fd, si->sendbuffer, si->slength, 0); //struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;//ev.data.fd = clientfd;si->sockfd = fd;si->callback = recv_cb;ev.data.ptr = si;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);}//  ./epoll 8080
//接受数据
int recv_cb(int fd, int events, void *arg) { //int clientfd = events[i].data.fd;struct sockitem *si = (struct sockitem*)arg;struct epoll_event ev;//char buffer[1024] = {0};int ret = recv(fd, si->recvbuffer, BUFFER_LENGTH, 0);if (ret < 0) {//若readbuffer满了,就会返回EAGAINif (errno == EAGAIN || errno == EWOULDBLOCK) { //return -1;} else {}ev.events = EPOLLIN;//ev.data.fd = fd;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);close(fd);free(si);} else if (ret == 0) { //若等于0表示连接断开了// printf("disconnect %d\n", fd);ev.events = EPOLLIN;//ev.data.fd = fd;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);close(fd);free(si);} else {printf("Recv: %s, %d Bytes\n", si->recvbuffer, ret);si->rlength = ret;memcpy(si->sendbuffer, si->recvbuffer, si->rlength);si->slength = si->rlength;struct epoll_event ev;ev.events = EPOLLOUT | EPOLLET;//ev.data.fd = clientfd;si->sockfd = fd;si->callback = send_cb;ev.data.ptr = si;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);}}int accept_cb(int fd, int events, void *arg) {struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int clientfd = accept(fd, (struct sockaddr*)&client_addr, &client_len);if (clientfd <= 0) return -1;char str[INET_ADDRSTRLEN] = {0};printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),ntohs(client_addr.sin_port));struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;//连接建立完之后就打开响应的读事件//ev.data.fd = clientfd;struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = clientfd;si->callback = recv_cb;//如果客户端给服务器发数据,我们就调用这个回调函数ev.data.ptr = si;epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev);return clientfd;
}int init_sock(short port) {//1.创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {return -1;}//2.绑定具体的端口struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {return -2;}//3.监听套接字if (listen(sockfd, 5) < 0) {return -3;}printf ("listen port : %d\n", port);return sockfd;}//EPOLLIN 读事件,EPOLLOUT写事件、EPOLLERR网络出错事件,EPOLLHUP:写端和读端都关闭了,也就是连接关闭了
int main(int argc, char *argv[]) {//1.获取端口参数并创建eventloop,创建监听套接字if (argc < 2) {return -1;}int port = atoi(argv[1]);eventloop = (struct reactor*)malloc(sizeof(struct reactor));// epoll operaeventloop->epfd = epoll_create(1);int i = 0;for (i = 0;i < LISTEN_PORT;i ++) {int sockfd = init_sock(port+i);struct epoll_event ev;ev.events = EPOLLIN;struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = sockfd;si->callback = accept_cb;//监听套接字的回调函数是accepte_cb,监听套接字有消息过来就调用accept_cb函数ev.data.ptr = si;//把listenfd交给epoll管理epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);}while (1) {int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1);if (nready < -1) {break;}int i = 0;for (i = 0;i < nready;i ++) {if (eventloop->events[i].events & EPOLLIN) {//printf("sockitem\n");struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;si->callback(si->sockfd, eventloop->events[i].events, si);}if (eventloop->events[i].events & EPOLLOUT) {struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;si->callback(si->sockfd, eventloop->events[i].events, si);}}}}

4.2 libevent封装reactor的代码

  • 现在把具体的事件用不同的回调函数解耦合

4.2.1 单reactor的封装

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>#include <errno.h>
#include <sys/epoll.h>#include <event.h>
#include <time.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>void socket_read_callback(struct bufferevent *bev,void* arg)
{char msg[4096];//解耦合之后,不在这里处理连接的断开size_t len = bufferevent_read(bev,msg,sizeof(msg)-1);msg[len] = '\0';printf("server read the data %s\n",msg);char reply[4096] = "recvieced msg: ";strcat(reply + strlen(reply),msg);bufferevent_write(bev,reply,strlen(reply));
}void socket_event_callback(struct bufferevent *bev,short events,void* arg)
{if(events & BEV_EVENT_EOF)          //BEV_EVENT_EOF: 1.read=0,2.write=-1&errno=EPIPE,3.epoll的EPOLLHUPprintf("connection closed\n");else if(events & BEV_EVENT_ERROR)   //网络错误printf("some other error\n");bufferevent_free(bev);              //操作相当于close对应的fd
}//连接的建立
void listener_callback(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr sock,int socklen,void* arg)
{printf("accept a client fd:%d\n",fd);struct event_base *base = (struct event_base*)arg;//上下文已经取出了相应的event//创建用户态的读写缓冲区对象bev(意义:由于读一次可能读不完)struct bufferevent *bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);//设置的响应的读事件回调函数,socket_read_callback//socket_event_callback是连接的断开回调函数bufferevent_setcb(bev,socket_read_callback,NULL,socket_event_callback,NULL);bufferevent_enable(bev,EV_READ|EV_PERSIST);         //作用:注册读事件,跟epoll_ctl的作用一样;EV_PERSIST表示注册后不会被移除,不加注册一次后就会被移除
}//定时事件的回调函数
static void
do_timer(int fd,short events,void* arg)
{struct event* timer = (struct event*)arg;timer_t now = time(NULL);printf("do_timer %s",(char*)ctime(&now));struct timeval tv = {1,0};event_add(timer,&tv);
}//编译指令
//gcc evmain.c -o evmain -levent
int main(int argc,char*argv[])
{//1、创建结构体struct sockaddr_in sin;memset(&sin,0,sizeof(struct sockaddr_in));sin.sin_family = AF_INET;sin.sin_port = htons(8989);//2.初始化事件管理器struct event_bash *base = event_bash_new(); //首先初始化一个事件管理器//3.连接的建立,accept流程的处理封装成一个对象//参数:第一个:选择那个事件管理器;第二个:提供一个回调函数,这个相当于accept_cb处理连接的建立;//第三个:LEV_OPT_REUSEABLE,如果不设置的化,端口连接不上(设置端口在服务器重启后可重用);LEV_OPT_CLOSE_ON_FREE:当客户端断开的时候,程序自动帮我们把fd给close掉struct evconnlistener *listener = evconnlistener_new_bind(base,listener_callback,base,LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,10,(struct sockaddr*)&sin,sizeof(struct sockaddr_in));////对于服务端而言有三种事件:1.网络io事件,2.定时事件(比如有些事件需要延迟处理,或是每两秒执行一次),3.信号事件(例如KILL -9)//信号举例:log系统,写日志文件写不进去了,但是调试fd是可用的,原因:日志被重定向了,解决原理:由于日志被重定向了,出现异常的时候,内核当中以信号的方式通知应用程序,应用程序需要捕获这个信号,fd按重定向重新打开一下struct event evtimer;                       //定时事件struct timeval tv = {1,0};                  //参数:第一个是秒,第二个是微秒;总体意思是每秒执行一次函数event_set(&evtimer,-1,0,do_timer,&evtimer); //把任务设置为一个定时事件,do_timer是定时事件的回调函数,第四个参数为上下文参数event_bash_set(base,&evtimer);              //再将具体的event绑定到事件处理器base上面event_add(&evtimer,&tv);                    //注册事件管理器//总结:1秒之后事件管理器会以同步的方式派发出去,调用回调函数//事件循环event_base_dispatch(base);                  //检测事件+事件派发也就是调用响应的callbackevconnlistener_free(listener);event_bash_free(base);return 0;
}
  • 展现效果:libevent每秒运行定时器
  • telnet打开连接效果
  • 连接建立好后,客户端打印hello

4.2.2 memcached 多reactor的封装(多个网络线程中处理)

//略
//memcached.c
//thread.c
  • 总体思路:主线程配合按CPU核心数创建的工作线程,accept接受新的fd后将fd交给工作线程,在之后客户端就和工作线程通信完成连接的建立、断开、消息的到达、消息的发送
  • accept后的fd利用负载均衡roundroubing就放入一个对应线程的队列中,主线程往对应工作线程的管理写数据,工作线程收到管道的数据就去队列中取连接,注册连接断开、消息到达和发送的事件到事件管理器event_base
  • 多个reactor:多个线程都有一个reactor

五、异步请求池的实现

六、libevent接口

6.1 struct event_base* event_base_new 创建事件管理器

初始化 libevent;对应理解 epoll_create

  • struct event_base *event_base_new(void);

6.2 event_new 创建事件

创建事件,初始化event和相应的回调函数

struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)
(evutil_socket_t, short, void *), void *arg)
--arg是传给回调函数的参数

events:监听的事件
#define EV_TIMEOUT 0x01 //超时事件
#define EV_READ 0x02 //读事件
#define EV_WRITE 0x04 //写事件
#define EV_SIGNAL 0x08 //信号事件
#define EV_PERSIST 0x10 //周期性触发,不加上这个,表示只能触发一次
#define EV_ET //边缘触发模式

cb 回调函数,原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd,short events,void *arg);

6.3 event_set 设置事件

void
event_set(struct event *ev, evutil_socket_t fd, short events,
void (*callback)(evutil_socket_t, short, void *), void *arg)

6.3 event_base_set 设置事件管理器和事件的映射关系

建立 event 与 event_base 的映射关系;

int event_base_set(struct event_base *eb, struct event *ev)

6.4 event_add 注册事件,包括时间事件;相当于 epoll_ctl;

int
event_add(struct event *ev, const struct timeval *tv)补充:tv可填NULL,表示永久监听,或填写固定的时间来限时等待

6.5 event_del 注销事件

int
event_del(struct event *ev)

6.6 int event_base_loop 进入事件循环

int
event_base_loop(struct event_base *base, int flags)

flags的取值
#define EVLOOP_ONCE 0x01
只触发一次,如果事件没有呗触发,阻塞等待
#defien EVLOOP_NONBLOCK 0x02
非阻塞方式检测事件是否被触发,不管事件触发与否,都会立即返回
(大多数情况下都会使用另外一个api:int event_base_dispatch)

6.7 void event_base_free(struct event_base*) 释放event_base结构体

6.8 int event_reinit(struct event_base* base) fork子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化

6.9 int event_base_dispatch(struct event_base* base),程序会一直在while循环执行epoll_wait()函数

补充
int event_base_loopexit(struct event_base* base, const struct timeval* tv);
等待一段事件后退出
int event_base_loopbreak(struct event_base* base);
立即退出

注意:event_new 相当于 malloc + event_set + event_base_set

6.10 bufferevent读写数据API(操作evbuffer的不删除缓冲区数据)

  • int bufferevent_write(struct bufferevent* bufev, const void* data, size_t size)
    将data数据写到bufferevent的写缓冲区
  • int bufferevent_write_buffer(struct bufferevent buffev , struct evbuffer buf);
    将数据写到缓冲区的另外一个写法,实际上bufferevent的内部的两个缓冲区结构就是struct evbuffer
  • int bufferevent_read(struct bufferevent* bufev, void* data, size_t size);
    将bufferevent的读缓冲区数据读到data中,同时将独到的1数据从bufferevent的读缓冲区清除
  • int bufferevent_read_buffer(struct bufferevent* bufev , struct evbuffer* buf)
    跟上个函数一样

6.11 bufferevent连接监听器evconnlistener_new_bind和evconnlistener_new(与前面的区别是认为socket已经初始化好,bind完成,甚至也可以做完listen)

  • 作用:创建并绑定套接字,并开启监听
  • 函数

struct evconnlistener* evconnlistener_new_bind(
struct event_base* base, //base根节点
evconnlistener_cb cb, //提取cfd后调用的回调
void* ptr, //传给回调的函数
unsigned flags, //
LEV_OPT_CLOSE_ON_FREE 关闭时自动释放
LEV_OPT_REUSEABLE 端口重用
LEV_OPT_THREADSAFE 分配锁,线程安全
LEV_OPT_LEAVE_SOCKETS_BLOCKING 文件描述符为阻塞的的

int backlog,
const struct sockaddr* sa,
int socklen
)

6.12 evsignal_new

#define evsignal_new(b , x , cb ,arg)
event_new( (b) , (x) ——》x的放的就是具体的信号EV_SIGNAL|EV_PERSIST, (cb) ,(arg) )

  • 例子

signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);

6.13 bufferevent_socket_connect

用户可以在调用bufferevent_socket_new函数时,传一个-1作为socket的文件描述符,然后调用bufferevent_socket_connect函数连接服务器,无需自己写代码调用connect函数连接服务器。
bufferevent_socket_connect函数会调用socket函数申请一个套接字fd,然后把这个fd设置成非阻塞。接着就connect服务器,因为该socket fd是非阻塞的,所以不会等待,而是马上返回,连接这工作交给内核来完成。所以,返回后这个socket还没有真正连接上服务器。那么什么时候连接上呢?内核又是怎么通知通知用户呢?
一般来说,当可以往socket fd可写,那就说明已经连接上了。也就是说这个socket fd变成可写状态,就连接上了。
所以,对于“非阻塞connect”比较流行的做法是:用select或者poll这类多路IO复用函数监听该socket的可写事件。当这个socket触发了可写事件,然后再对这个socket调用getsockopt函数,做进一步的判断即可。


/*
直接连接
*/
int
bufferevent_socket_connect(struct bufferevent *bev,struct sockaddr *sa, int socklen)
{struct bufferevent_private *bufev_p =EVUTIL_UPCAST(bev, struct bufferevent_private, bev);evutil_socket_t fd;int r = 0;int result=-1;int ownfd = 0;_bufferevent_incref_and_lock(bev);if (!bufev_p)goto done;fd = bufferevent_getfd(bev);//初始化设定fd小于0if (fd < 0) {//该bufferevent还没有设置fdif (!sa)goto done;fd = socket(sa->sa_family, SOCK_STREAM, 0);//创建套接字,并将其设定为非阻塞if (fd < 0)goto done;if (evutil_make_socket_nonblocking(fd)<0)//设置为非阻塞goto done;ownfd = 1;}if (sa) {r = evutil_socket_connect(&fd, sa, socklen);//非阻塞调用connect连接服务器,不会等待,而是马上返回,连接工作交给内核来完成。if (r < 0)//小于则错误/*0-EINPROGRESS或EINTR 正在连接1-都在本机,连接成功2-refuse拒绝连接<0 错误       */goto freesock;}//删除旧事件,并将事件和新的fd对应,并加入到epoll监听可写。bufferevent_setfd(bev, fd);if (r == 0) {//返回值等于0,则握手开始,但是还没有连接上,必须监听可写//此时需要监听可写事件,当可写了,并且没有错误的话,就成功连接上了if (! be_socket_enable(bev, EV_WRITE)) {//将可写加入epoll监听bufev_p->connecting = 1;//epoll返回即可写了result = 0;goto done;}} else if (r == 1) {//当服务器和客户机处于同一个主机,connect直接返回可能发生。/* The connect succeeded already. How very BSD of it. */result = 0;bufev_p->connecting = 1;event_active(&bev->ev_write, EV_WRITE, 1);} else {/* The connect failed already.  How very BSD of it. */bufev_p->connection_refused = 1;bufev_p->connecting = 1;result = 0;event_active(&bev->ev_write, EV_WRITE, 1);}goto done;freesock:_bufferevent_run_eventcb(bev, BEV_EVENT_ERROR);if (ownfd)evutil_closesocket(fd);/* do something about the error? */
done:_bufferevent_decref_and_unlock(bev);return result;
}/* XXX we should use an enum here. */
/* 2 for connection refused, 1 for connected, 0 for not yet, -1 for error. */
int
evutil_socket_connect(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen)
{int made_fd = 0;if (*fd_ptr < 0) {if ((*fd_ptr = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)goto err;made_fd = 1;if (evutil_make_socket_nonblocking(*fd_ptr) < 0) {goto err;}}if (connect(*fd_ptr, sa, socklen) < 0) {//调用系统connect函数int e = evutil_socket_geterror(*fd_ptr);//获取socket错误if (EVUTIL_ERR_CONNECT_RETRIABLE(e))//EINTR或EINPROGRESS则正在握手return 0;if (EVUTIL_ERR_CONNECT_REFUSED(e))return 2;goto err;} else {return 1;//返回 >=0 则表示连接成功,UNPv1有详细说明。客户和服务器位于本机则可能立即返回成功。}err:if (made_fd) {evutil_closesocket(*fd_ptr);*fd_ptr = -1;}return -1;
}

6.13 bufferevent_setcb设置读写对应的回调函数

  • 函数原型
void bufferevent_setcb(struct bufferevent *bufev,bufferevent_data_cb readcb, bufferevent_data_cb writecb,bufferevent_event_cb eventcb, void *cbarg)
eg.  bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);

6.14 bufferevent_enable,如果相应事件不置为true,bufferevent是不会读写数据的

  • 函数说明
    启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll。正如文档所说,如果相应事件不置为true,bufferevent是不会读写数据的

  • 函数原型

int bufferevent_enable(struct bufferevent *bufev, short event)
eg.  bufferevent_enable(bev, EV_READ|EV_WRITE);

七、总体流程

普通的events事件的使用方法

①创建base:event_base_new()
②创建节点event_new
③节点注册event_add
④注销事件event_del
⑤释放节点event_free

实操:使用libevent编写tcp服务器流程

①创建套接字:
②绑定
③监听
④创建event_base根节点(从这里开始调用libevent接口)
⑤初始化根节点ifd
⑥注册事件
⑦循环监听
⑧收尾 eventcb

bufferevent事件流程

创建event_base管理器
struct event_base* event_base_new(void);
创建新的节点:对已经存在的socket创建bufferevent事件,可用于后面讲到的链接监听器的回调函数中
函数:

struct bufferevent* bufferevent_socket_new(struct event_base* base, evutil_socket_t fd, int options);

参数说明:

base:对应根节点
fd: 文件描述符
options ->bufferevent的选项
EV_OPT_CLOSE_ON_FREE 释放bufferevent自动关闭底层接口
BEV_OPT_THREADSAFE 能够在多线程下是安全的

设置节点对应文件描述符事件触发的回调函数,设置完就相当于注册读、写事件,但是还需要另外一个API把注册的读写回调函数生效
函数:

void bufferevent_setcb(struct bufferevent* bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void* cbarg
);
参数说明:
eventcb-》异常回调函数,比如说对端断开了
事件:
BEV_EVENT_EOF 对方关闭连接
BEV_EVENT_ERROR 出错
BEV_EVENT_TIMEOUT 超时
BEV_EVENT_CONNECTED 建立连接
void* cbarg -》传给回调函数的参数

使注册的读写回调函数生效
bufferevent_enable(struct bufferevent* bufev, short events);
bufferevent_disable(struct bufferevent* bufev,short events);
events包括:
EV_READ
EV_WRITE
封装底层socket的connect接口,通过调用此函数,将bufferevent事件与通信的socket进行绑定
函数:

int bufferevent_socket_connect(struct bufferevent* bev, struct sockaddr* serv ,int socklen);

参数:

bev 需要提前初始化的bufferevent事件
serv 对端的IP地址、端口、协议的结构指针
sockLen 描述serv的长度

开始事件循环
event_base_dispatch(base);

八、官方测试代码解析

hello_world.c(libevent跨平台服务器代码)

/*aasasdasdasdfsdfsd56165156561asdasdasdThis example program provides a trivial server program that listens for TCPconnections on port 9995.  When they arrive, it writes a short message toeach client connection, and closes each connection once it is flushed.Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>//编译指令
//gcc hello_world.c -leventstatic const char MESSAGE[] = "Hello, World!\n";static const unsigned short 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 *);
static void conn_readcb(struct bufferevent* , void*);int
main(int argc, char **argv)
{//0.初始化一些变量struct event_base *base;struct evconnlistener *listener;struct event *signal_event;struct sockaddr_in sin = {0};//1.检测环境
#ifdef _WIN32WSADATA wsa_data;WSAStartup(0x0201, &wsa_data);
#endif//2.创建event_base管理器base = event_base_new();if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}//3.初始化一些监听套接字的东西memset(&sin,0,sizeof(sin)); //这里没设置s_addr,因为都设置为0了,0是通配地址。所以就不用设置s_addr了sin.sin_family = AF_INET;sin.sin_port = htons(PORT);//4.定义链接监听器//有用户连接上来,执行listener_cb函数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;}//5.设置信号回调函数,回调函数用来中断循环signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);//6.将信号处理事件注册到管理器上面if (!signal_event || event_add(signal_event, NULL)<0) {fprintf(stderr, "Could not create/add a signal event!\n");return 1;}//7.开启事件循环event_base_dispatch(base);//8.回收资源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)
{   //1.初始化数据struct event_base *base = user_data;struct bufferevent *bev;//2.创建一个连接套接字,并将连接套接字放在epoll的红黑树bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);if (!bev) {fprintf(stderr, "Error constructing bufferevent!");event_base_loopbreak(base);return;}//3.设置连接套接字的读、写、错误处理事件的处理函数,并使能一些函数bufferevent_setcb(bev, conn_readcb , conn_writecb, conn_eventcb, NULL);bufferevent_enable(bev, EV_WRITE | EV_READ);//使读\写事件都使能//bufferevent_disable(bev, EV_READ);//4.给对端发送消息 helloworldbufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}//连接套接字写事件回调函数
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{   //获取缓冲区类型也就是获取缓冲区地址struct evbuffer *output = bufferevent_get_output(bev);//判断缓存区是否还有数据,若hello world已经发送出去,那么数据长度等于0,那就调用bufferevent_free释放节点if (evbuffer_get_length(output) == 0) {printf("flushed answer\n");//bufferevent_free(bev);}
}static void
conn_readcb(struct bufferevent* bev,void* user_data)
{char buf[1500] = "";int n = bufferevent_read(bev,buf,sizeof(buf));printf("buf:%s\n");//读了数据再给对端发送过去bufferevent_write(bev,buf,n);//给对端发送数据
}//断开连接触发事件回调函数
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);
}

time-test.c(空)


signal-test.c(空)


linux版本的libevent客户端代码(转移到其他博文,修改为跨平台代码)

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>struct rA
{uint8_t        type;uint8_t        len;uint16_t        calccrc;    //crc32用来校验包体长度是否改变
};struct db2gs
{uint32_t       toatalSize;uint8_t          nodeNum;        //表示有多少个结构体
};

linux版本libevent服务端代码(比起上面那个服务器接受的客户端有限,非跨平台)

跨平台代码传送门

#include <iostream>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>#define redis_reply_string 1
#define redis_reply_array 2
#define redis_reply_integer 3
#define redis_reply_nul 4
#define redis_reply_status 5
#define redis_reply_error 6#define _MAX_CLIENT_ 1024//监听套接字结构体数组
typedef struct FdEventMap
{int fd;                //文件描述符struct event* ev;    //对应事件
};struct rA
{uint8_t        type;uint8_t        len;uint16_t        calccrc;        //crc32用来校验包体长度是否改变
};struct db2gs
{uint32_t       toatalSize;     //表示数据总长度uint8_t            nodeNum;        //表示有多少个结构体char           data[0];       //柔性数组的灵魂
};//----------------------------函数参数
static const int    PORT = 8888;
static char         g_szWriteMsg[256]   = {0};
static char         g_szReadMsg[3000]   = {0};
static int          g_iCnt              = 0;
static void         conn_ //----------------------------
void initEventArray()
{int i;for(i = 0;i<_MAX_CLIENT_;++i){mFdEvents[i].fd = -1;mFdEvents[i].ev = NULL;}
}int addEvent(int fd, struct event* ev)
{int i;for(i = 0;i < _MAX_CLIENT_;++i){if(0 > mFdEvents[i].fd){break;//return -1;}}if(i == _MAX_CLIENT_){printf("too many clients...\n");return -1;}mFdEvent[i].fd = fd;mFdEvent[i].ev = ev;return 0;
}struct event* ghetEventByFd(int fd)
{int i;for( i = 0; i< _MAX_CLIENT_;++i){if(mFdEvents[i].fd == fd){return mFdEvents[i].ev;}}return NULL;}void readcb(evutil_socket_t fd,short events,void* arg)
{CCRC32* p_crc32 = CCRC32::GETInstance();char buf[256] = {0};int ret = recv(fd,buf, sizeof(buf),0);if( 0>=ret){close(fd);event_del(getEventByFd(fd));}   else{int i;for(i = 0;i < ret:++i){buf[i] = toupper(buf[i]);}printf("客户端已经链接,接受对端字符串string:%s\n",buf);//1.先算占多少内存char tmp_str[50] = "strcpy123456";size_t totalSize = sizeof(db2gs) + 3*strlen(tmp_str) + 3*sizeof(rA);db2gs* rp = (db2gs*)malloc(totalStruct);rp->nodeNUm = 3;char* ptmp = (char*)(rp->data);char* tmp  = (char*)(rp->data);size_t p = 0;rA* ra = (rA*)ptmp;for(int16_t nIndex = 0;nIndex < rp->nodeNum;++nIndex){ra = (rA*)ptmp;ra->type  = 3;ra->len     = strlen(tmp_str);ra->calccrc   = p_crc32->Get_CRC((unsigned char* )tmp_str,ra->len);memccpy((void*)ptmp,(void*)ra,sizeof(rA));printf("len:%d,tmp_str:%s,str:%s\n",ra->len,tmp_str,ptmp);ptmp += ra->len;//跳过字符串复制}        //2.打印测试rA* rb = (rA*)tmp;uint8_t      type_2      = rb->type;uint8_t          len_2       = rb->len;uint16_t      calccrc_2   = rb->calccrc;printf("type = %d,len=%d,calccrc=%3\n",type_2,len_2,calccrc_2);      tmp = tmp +len_2;rb   = (rA*)tmp;type_2      = rb->type;len_2        = rb->len;calccrc_2 = rb->calccrc;//3.发送数据send(fd,(void*)rp,toatalStuct,0);free(rp);}}void conncb(evutil_socket_t fd,short events,void* arg)
{printf("客户度已经连接上!\n");struct event_base* base = (struct event_base*)arg;stuct sockaddr_in client;socklen_t lth = sizeof(client);int cfd = accept(fd,(struct sockaddr*)&client,&ith);if( 0 < cfd){//创建事件struct event* readev = event_new(base,cfd,EV_READ|EV_PERSIST,readcb,base);//注册事件event_add(readev,NULL);//添加到数组addEvent(cfd,readev);}
}//编译指令
//g++ **.cpp  -event
int main()
{printf("hello world!\n");//初始化CRC32单例类struct event_base* base = event_base_new();//创建套接字int lfd = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in recv;bzero(&serv,sizeof(serv));serv.sin_family = AF_INET;serv.sin_addr.s_addr = htonl(INADDR_ANY);serv.sin_port = htons(8888);int opt = 1;//设置套接字套用setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,*opt,sizeof(opt));if(0 > bind(lfd,(struct sockaddr*)&serv,sizeof(serv))){perror("bind err";return -1;)}//监听listen(lfd,128);//创建事件设置回调initEventArray();//初始化事件数组//创建事件struct event* connev = event_new(base,lfd,EV_READ|EV_PERSIST,conncb,base);//注册事件event_add(connev,NULL);//循环监听event_base_dispatch(base);//回收内存资源close(lfd);event_base(base);return 0;
}

番外篇15:libevent简单理解(附libevent官方代码解析,和跨平台服务器、客户端链接代码)相关推荐

  1. 笔记:计算机视觉与深度学习-简单的实现一个网络-番外篇

    写在开头 1.课程来源及所有代码来源,后面不再每一步中标明了:pytorch官方网站的Tutorials.和Docs. 2.笔记目的:个人学习+增强记忆+方便回顾 3.时间:2021年4月19日 4. ...

  2. 理解TextView三部曲之番外篇:或许这会是最终的进化

    额,为什么会有番外篇呢..因为新版本上线后,别的同学用我的这个控件,描边显示出问题了-_-! 什么问题呢? 我把问题抽出来,同时把问题放大点,给大家看看(抹眼泪.png)   好嘛,问题不大..就是描 ...

  3. 文本分类入门(番外篇)特征选择与特征权重计算的区别

    文本分类入门(番外篇)特征选择与特征权重计算的区别 在文本分类的过程中,特征(也可以简单的理解为"词")从人类能够理解的形式转换为计算机能够理解的形式时,实际上经过了两步骤的量化- ...

  4. 你所能用到的数据结构之番外篇---逆袭的面向对象(一)

    对于番外篇,我深刻能明白在大多数人眼里就和电视剧的广告一样,说实话,我也不喜欢这种感觉,因为这样会让人觉得是在欺骗消费者啊~~~阿西巴~~~但是我实在发现如果不在这里对面向对象来个入门级的介绍,后面的 ...

  5. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    转载自:https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程:给深度学习入门者的Python快速教程 - 基础篇 给深度学习入门者的Python快速教程 - ...

  6. clang static analyzer源码分析(番外篇):RegionStore以及evalCall()中的conservativeEvalCall

    引子 我们在上一篇文章<clang static analyzer源码分析(番外篇):evalCall()中的inline机制>中提及了clang如何创建CallGraph,如何进行函数i ...

  7. 系统工程(SE)学习笔记(番外篇之一)——Capella使用体会兼谈SE工具

    系统工程(SE)学习笔记(番外篇之一)--Capella使用体会兼谈SE工具 零.Capella简介 壹. Capella的优势 贰.Capella的缺点 叁. 生态环境 肆. 总结 说到SE,就不能 ...

  8. [zt]数学之美番外篇:平凡而又神奇的贝叶斯方法

    数学之美番外篇:平凡而又神奇的贝叶斯方法 Tags: 数学, 机器学习与人工智能, 计算机科学 save it69 saved tags: 贝叶斯 math bayesian algorithm 数学 ...

  9. 转:数学之美番外篇:平凡而又神奇的贝叶斯方法 收藏

    为什么80%的码农都做不了架构师?>>>    转自:http://blog.csdn.net/pongba/archive/2008/09/21/2958094.aspx 数学之美 ...

最新文章

  1. 内存、性能问题分析的利器——valgraind
  2. ie对java的设置字体,css3文字特效和浏览器兼容性
  3. 外包公司做遗留项目有意思么?
  4. 手机是如何实现自动对焦的?
  5. react native 包学不包会系列--认识react native
  6. SQL 结合CASE WHEN 实现二维统计
  7. 【CCF】201703-1分蛋糕
  8. 安卓入门程序《发短信》
  9. 嵌入式操作系统内核原理和开发(头文件调整)
  10. Android项目实战(二十):浅谈ListView悬浮头部展现效果
  11. 6.旋转数组的最小数字
  12. 易语言.用修改注册表的方式来关闭win10自带的杀毒软件
  13. python+OpenCV jpg图片的压缩
  14. DTU和工业网关的区别是什么?怎么选?
  15. jQuery实现一个备忘录
  16. 全面解析大数据解决方案的架构层
  17. Big Sur风格应用图标制作软件:Acon
  18. linux内核的原子操作简述
  19. Android RxJava生命周期管理解决方案整理
  20. greasemonkey_询问操作方法:Chrome中的Greasemonkey,为Media Center布线和自定义Windows 7跳转列表...

热门文章

  1. 熵权法与Apriori算法对较多数据种类数据的处理
  2. 前端小技巧(2)-performance.timing属性介绍
  3. 大白话、最简单——SpringBoot+Mybatis+freemarker整合(二)
  4. 用python操作浏览器的三种方式
  5. 【Verilog】FPGA控制RGB灯WS2812B
  6. 程序员写程序的逻辑思维,和外行人想当然的思维,到底有什么不同
  7. HTML3-视频图像的插入
  8. mysql经典问题之group by和max函数
  9. PI3体验之无线网AP模式设定及热点分享
  10. 路由器网口1一直闪烁正常吗_网口1一直闪烁上不了网(图文)