【libevent】libevent简介
1、Libevent
1.1 简介
Libevent 是一个用C语言编写的、轻量级的开源高性能事件驱动网络库。
基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求,于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。
libevent就是对这些高效IO的封装,提供统一的API,简化开发。
1.2 特点
libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络库。Libevent有几个显著的特点:
(1)事件驱动(event-driven),高性能;
(2)轻量级,专注于网络,不如 ACE 那么臃肿庞大;
(3)跨平台,支持 Windows、Linux、*BSD和 Mac Os;
(4)支持多种I/O多路复用技术,epoll、poll、dev/poll、select和kqueue等;
(5)支持I/O,定时器和信号等事件;
(6)采用Reactor模式;
libevent实现高并发的关键在于其基于事件驱动的异步模型。主要实现方式包括:
- 事件驱动: libevent使用事件循环机制,通过监听和响应事件来实现非阻塞IO操作。当有事件发生时,libevent会调用相应的回调函数进行处理,从而避免了传统同步阻塞IO模型中的线程阻塞和资源浪费。
- 多路复用: libevent利用操作系统提供的多路复用机制(如epoll、kqueue、select等),将多个IO操作事件集中管理,实现在单线程或少量线程上处理大量并发连接的能力。这样可以避免每个连接都需要一个线程的资源消耗,从而提高了系统的并发性能。
- 缓冲区管理: libevent的bufferevent模块提供了高效的缓冲区管理,可以在网络IO中缓冲数据,以减少系统调用次数和数据拷贝,从而提升性能。
- 非阻塞IO: libevent使用非阻塞IO操作,通过设置套接字为非阻塞模式,使得IO操作不会阻塞整个进程或线程,提高了处理并发连接的效率。
1.3 下载安装
这里下载的是libevent-2.1.11-stable.tar.gz
tar -zxvf libevent-2.1.11-stable.tar.gz cd libevent-2.1.11-stable/ ./configure --prefix=`pwd`/install #检查安装环境,指定安装路径,生成makefile make make install
1.4 测试
在sample文件下有libevent提供的测试程序
#服务器端 cd libevent-2.1.11-stable/sample ./hello-world #客户端(另开一个窗口) netcat 127.0.0.1 9995 #收到hello world
2、事件驱动
2.1 简介
事件驱动(Event-Driven)是一种编程范式,系统中的各种操作是通过事件的发生来驱动的。事件驱动编程模型通过事件监听器(Listener)和事件处理器(Handler)来响应和处理各种事件。
事件驱动模型的核心是通过事件(Event)来触发操作。事件可以是用户的操作(如鼠标点击、键盘输入),也可以是系统内部的状态变化(如定时器事件、网络数据到达)。事件驱动编程通过事件循环(Event Loop)不断地等待和处理事件,从而实现系统的响应和交互。
2.2 组成部分
- 事件:事件是系统中的一种信号,表示某种操作或状态的发生。事件可以携带额外的信息,如鼠标位置、键盘按键值、网络数据等。
- 事件源(Event Source):事件源是事件的产生者,可以是用户输入设备、网络连接、定时器等。
- 事件监听器(Event Listener):事件监听器是一个回调函数或方法,用于监听和捕获特定类型的事件。当事件发生时,事件监听器会被触发并调用相应的事件处理器。
- 事件处理器(Event Handler):事件处理器是实际处理事件的逻辑,包含具体的业务操作。
- 事件循环(Event Loop):事件循环是事件驱动模型的核心,负责不断地等待和分发事件。事件循环通常是一个无限循环,通过检查事件队列或事件多路复用器来获取事件,并调用相应的事件处理器。
2.3 工作流程
- 事件注册:应用程序将事件和对应的事件监听器注册到事件源。
- 事件产生:当事件源产生事件时,事件被放入事件队列中等待处理
- 事件分发:事件循环不断地从事件队列中取出事件,将事件分发给相应的事件监听器。
- 事件处理:事件监听器调用事件处理器,执行相应的业务逻辑。
- 事件循环继续:事件处理完成后,事件循环继续等待和处理下一个事件。
3、Reactor模式
3.1 简介
Reactor模式是一种高效的事件驱动设计模式,广泛应用于高性能服务器和网络应用中。它通过将事件的检测和分发与实际的事件处理逻辑分离,提供了一种高效的多任务并发处理方法。
3.2 事件处理机制
普通函数调用的机制:
- 函数调用:调用一个函数,传递必要的参数。
- 执行函数:程序执行进入函数体,按顺序执行函数内的代码。
- 返回结果:函数执行完毕后返回结果,程序继续执行调用处之后的代码。
- 阻塞执行:在函数执行期间,调用线程会被阻塞,直到函数返回结果。
而事件驱动机制不同,程序不是主动的调用某个API完成处理,恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”
3.3 组成部分
描述符(handle):由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。
事件多路复用器(Event Demultiplexer):使用操作系统提供的多路复用机制(如select、poll、epoll、kqueue)来监听多个事件源。程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上; 当有事件到达时,event demultiplexer会通知程序处理事件。
反应器 Reactor:事件管理的接口,内部使用event demultiplexer注册、注销事件,并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。(libevent中的event_base结构体)
事件处理器(Event Handler):提供了一组接口(回调函数),每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。
3.4 工作流程
4、基本使用
4.1 事件处理流程
- 应用程序准备并初始化event,设置事件类型和回调函数
- 向libevent添加事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于Signal和I/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表
- 程序调用event_base_dispatch()系列函数进入无限循环,等待事件
5、源码分析
5.1 event_base
libevent默认情况下是单线程的(可以配置成多线程),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来托管给它的一系列event,可以和操作系统的进程管理类比,当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数,直到这个函数执行完,再返回其他事件。
event_base结构体就是Reactor框架中的反应器Reactor。
结构体声明位于event-internal.h文件中。
//创建一个event_base struct event_base *base = event_base_new(); assert(base != NULL);
event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有事件发生,然后去处理这些事件。
启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。
//启动事件循环 event_base_dispatch(base);
使用完 event_base 之后,使用 event_base_free()进行释放。
void event_base_free(struct event_base *base);
5.2 事件event
event是整个libevent的核心。event就是Reactor框架中的事件处理程序组件。它提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理。
5.2.1. 事件类型
libevent 支持以下几种主要的事件类型:
- I/O 事件:用于监视文件描述符(如套接字)的可读、可写状态。
- 定时事件:在指定的时间间隔之后触发。
- 信号事件:用于处理 UNIX 信号。
调用 libevent 函数设置事件并且关联到 event_base 之后,事件进入“已初始化(initialized)”状态。此时可以将事件添加到 event_base 中,这使之进入“未决(pending)”状态。
在未决状态下,如果触发事件的条件发生,则事件进入“激活(active)”状态,事件回调函数将被执行。如果配置为“持久的(persistent)”,事件将保持为未决状态。否则,执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的 ; 添加操作可以让非未决事件再次成为未决的。
5.2.2 创建event
每个事件对应一个struct event,struct event使用event_new来创建和绑定,使用event_add来启用:
//创建并绑定一个event struct event *event; //参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数 event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base); //参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置) event_add(event, NULL);
libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)
(1) EV_TIMEOUT: 超时
(2) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
(3) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
(4) EV_SIGNAL: POSIX信号量
(5) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
(6) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET
5.2.3 管理event
每次当有事件event转变为就绪状态时,libevent就会把它移入到active event list[priority]中,其中priority是event的优先级。
libevent会根据自己的调度策略选择就绪事件,调用其callback()函数执行事件处理;并根据就绪的句柄和事件类型填充callback函数的参数
5.2.4 检查事件状态
/*确定给定的事件是否是未决的或者激活的。如果是,而且 what 参 数设置了 EV_READ、EV_WRITE、EV_SIGNAL 或者 EV_TIMEOUT 等标志,则函数会返回事件当前为之未决或者激活的所有标志 。如果提供了 tv_out 参数,并且 what 参数中设置了 EV_TIMEOUT 标志,而事件当前正因超时事件而未决或者激活,则 tv_out 会返回事件的超时值。*/ int event_pending(const struct event *ev, short what, struct timeval *tv_out); //事件配置的文件描述符 evutil_socket_t event_get_fd(const struct event *ev); //事件配置的 event_base struct event_base *event_get_base(const struct event *ev); //返回事件的标志(EV_READ、EV_WRITE 等) short event_get_events(const struct event *ev); //返回事件的回调函数及其参数指针 event_callback_fn event_get_callback(const struct event *ev); void *event_get_callback_arg(const struct event *ev);
5.2.5 一次触发事件
如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的 , 则可以使用 event_base_once()。
int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
- 除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()相同。
- 安排的事件将以默认的优先级加入到 event_base并执行。回调被执行后,libevent内部将 会释放 event 结构。
- 不能删除或者手动激活使用 event_base_once ()插入的事件,如果要取消事件, 应该使用 event_new()或者 event_assign()。
5.2.6 事件状态的转换
5.3 事件主循环
在libevent中,事件主循环的作用就是执行一个循环,在循环中监听事件以及超时的事件并且将这些激活的事件进行处理。libevent提供了对用户开放了两种执行事件主循环的函数:
int event_base_dispatch(struct event_base *); int event_base_loop(struct event_base *, int);
在event_base_dispatch函数中,实际上调用的是event_base_loop(event_base, 0)。
event_base_loop()函数的定义:
int event_base_loop(struct event_base *base, int flags){ const struct eventop *evsel = base->evsel; void *evbase = base->evbase; struct timeval tv; struct timeval *tv_p; int res, done; // 清空时间缓存 base->tv_cache.tv_sec = 0; // evsignal_base是全局变量,在处理signal时,用于指名signal所属的event_base实例 if (base->sig.ev_signal_added) evsignal_base = base; done = 0; while (!done) { // 事件主循环 // 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记 // 调用event_base_loopbreak()设置event_break标记 if (base->event_gotterm) { base->event_gotterm = 0; break; } if (base->event_break) { base->event_break = 0; break; } // 校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间 // 在timeout_correct函数里,比较last wait time和当前时间,如果当前时间< last wait time // 表明时间有问题,这是需要更新timer_heap中所有定时事件的超时时间。 timeout_correct(base, &tv); // 根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间 tv_p = &tv; if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { timeout_next(base, &tv_p); } else { // 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待 // 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理 evutil_timerclear(&tv); } // 如果当前没有注册事件,就退出 if (!event_haveevents(base)) { event_debug(("%s: no events registered.", __func__)); return (1); } // 更新last wait time,并清空time cache gettime(base, &base->event_tv); base->tv_cache.tv_sec = 0; // 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等; // 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中 res = evsel->dispatch(base, evbase, tv_p); if (res == -1) return (-1); // 将time cache赋值为当前系统时间 gettime(base, &base->tv_cache); // 检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中 timeout_process(base); // 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理 // 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表, // 然后处理链表中的所有就绪事件; // 因此低优先级的就绪事件可能得不到及时处理; if (base->event_count_active) { event_process_active(base); if (!base->event_count_active && (flags & EVLOOP_ONCE)) done = 1; } else if (flags & EVLOOP_NONBLOCK) done = 1; } // 循环结束,清空时间缓存 base->tv_cache.tv_sec = 0; event_debug(("%s: asked to terminate loop.", __func__)); return (0); }
5.4 bufferevent
5.4.1 简介
bufferevent专门为封装成带有缓冲区的socket套接字。一个bufferevent包含了一个底层传输的fd(通常为socket),一个输入buffer和一个输出buffer。每个 bufferevent 有两个与数据相关的回调:读取回调函数和写入回调函数。 bufferevent中回调函数触发的条件与普通的event回调函数触发的条件不同( 普通的事件在底层传输端口已经就绪,可读或可写时就会执行回调函数,而 bufferevent 是在读取或写入了一定量的数据后才会调用回调函数)。
struct bufferevent结构体定义:
//bufferevent_struct.h文件 struct bufferevent { struct event_base *ev_base; //执行ev_base,最后需要将socket fd添加进ev_base。 //操作结构体,成员有一些函数指针。类似struct eventop结构体 const struct bufferevent_ops *be_ops; struct event ev_read;//读事件event struct event ev_write;//写事件event struct evbuffer *input;//读缓冲区 struct evbuffer *output; //写缓冲区 struct event_watermark wm_read;//读水位 struct event_watermark wm_write;//写水位 bufferevent_data_cb readcb;//可读时的回调函数指针 bufferevent_data_cb writecb;//可写时的回调函数指针 bufferevent_event_cb errorcb;//错误发生时的回调函数指针 void *cbarg;//回调函数的参数 struct timeval timeout_read;//读事件event的超时值 struct timeval timeout_write;//写事件event的超时值 /** Events that are currently enabled: currently EV_READ and EV_WRITE are supported. */ short enabled; };
5.4.2 水位线
我们刚刚说bufferevent两个数据相关的回调:一个读取回调和一个写入回调,bufferevent 是在读取或写入了一定量的数据后才会调用回调函数。通过调整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。
每个bufferevent有四个水位:
读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。
读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。
写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。
写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。
5.4.3 evbuffer
bufferevent采用evbuffer作为输入输出缓存。evbuffer像是一个字节队列,在队列的末尾写入数据,在队列的头部读取数据。evbuffer具体实现则是一个链表,链表中的每个节点都是一块连续的内存块,往evbuffer写数据时(调用evbuffer_add/evbuffer_add_printf等函数),evbuffer内部动态创建链表节点,并紧凑的写入数据(一个节点写满后,再写另外一个节点);从evbuffer中删除数据时(调用evbuffer_remove/evbuffer_drain),从链表头部节点开始读取,当一个节点的数据被全部读取后删除该节点,如果未读取完,则用标示记录数据已读取(删除)的位置。对于这种头部有数据被标示为读取(删除)的节点,再次写入数据时,可能会进行调整,即将数据部分整体往前拷贝移动,然后再继续写入数据。
关于evbuffer和bufferevent的详细介绍可以参考:http://t.csdnimg.cn/rit2P
5.4.4 bufferevent 选项标志
- BEV_OPT_CLOSE_ON_FREE :释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等。
- BEV_OPT_THREADSAFE :自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent。
- BEV_OPT_DEFER_CALLBACKS :设置这个标志时, bufferevent 延迟所有回调,如上所述。
- BEV_OPT_UNLOCK_CALLBACKS :默认情况下,如果设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。
5.4.5 使用
5.4.5.4 创建bufferevent
struct bufferevent *bufferevent_socket_new( struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);
5.4.5.5 连接
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。
如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
5.5 evbuffer
5.5.1 简介
Libevent提供了evbuffer用于处理缓冲网络IO的缓冲部分,为后续bufferevent的工作做准备。
缓冲区由evbuffer和evbuffer_chain组成,其中evbuffer_chain
是真正存储数据的一块内存,通过链表将一个一个的evbuffer_chain
连接在一起,组成内存池,可以存放很多的缓冲数据。。而通过evbuffer
结构体就可以管理这个内存池链表了。
struct evbuffer结构体定义如下:
//evbuffer-internal.h文件 struct evbuffer_chain; struct evbuffer { struct evbuffer_chain *first; struct evbuffer_chain *last; //这是一个二级指针。使用*last_with_datap时,指向的是链表中最后一个有数据的evbuffer_chain。 //所以last_with_datap存储的是倒数第二个evbuffer_chain的next成员地址。 //一开始buffer->last_with_datap = &buffer->first;此时first为NULL。所以当链表没有节点时 //*last_with_datap为NULL。当只有一个节点时*last_with_datap就是first。 struct evbuffer_chain **last_with_datap; size_t total_len;//链表中所有chain的总字节数 ... }; struct evbuffer_chain { struct evbuffer_chain *next; size_t buffer_len;//buffer的大小 //错开不使用的空间。该成员的值一般等于0 ev_off_t misalign; //evbuffer_chain已存数据的字节数 //所以要从buffer + misalign + off的位置开始写入数据 size_t off; ... unsigned char *buffer; };
5.5.2 使用
5.5.2.1 创建和释放evbuffer
struct evbuffer *evbuffer_new(void); void evbuffer_free(struct evbuffer *buf);
5.5.2.2 线程安全
int evbuffer_enable_locking(struct evbuffer *buf, void *lock); void evbuffer_lock(struct evbuffer *buf); void evbuffer_unlock(struct evbuffer *buf);
在多个线程中同时访问 evbuffer 是不安全的。如果需要这样的访问,可以调 用 evbuffer_enable_locking() 。如果 lock 参数为 NULL , libevent 会使用evthread_set_lock_creation_callback 提供的锁创建函数创建一个锁 。否则,libevent 将 lock 参数用作锁。
evbuffer_lock()和 evbuffer_unlock()函数分别请求和释放 evbuffer 上的锁。可以使用这两个 函数让一系列操作是原子的。
5.5.2.3 添加数据
//添加 data 处的 datalen 字节到 buf 的末尾,成功时返回0,失败时返回-1。 int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen); //添加格式化的数据到 buf 末尾。 //格式参数和其他参数的处理分别与 C 库函数 printf 和 vprintf 相同。函数返回添加的字节数。 int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...) int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
5.6 监听器evconnlisterner
5.6.1 简介
基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,并且一些细节可能考虑不全,为此Libevent提供了econnlistener 机制,简化了整个监听的流程,用户仅仅需要在对应回调函数里面处理已完成连接的套接字即可。
struct evconnlistener结构体定义如下:
struct evconnlistener { const struct evconnlistener_ops *ops; //操作函数 void *lock; //锁变量,用于线程安全 evconnlistener_cb cb; //用户的回调函数 evconnlistener_errorcb errorcb; //发生错误时的回调函数 void *user_data; //回调函数的参数,当回调函数执行时候,通过形参传入回调函数内部 unsigned flags; //属性标志 ,例如socket套接字属性,可以是阻塞,非阻塞,reuse等。 short refcnt; //引用计数 unsigned enabled : 1; //位域为1.即只需一个比特位来存储这个成员 }; struct evconnlistener_event { struct evconnlistener base; struct event listener; //内部event,插入到event_base,完成监听 };
5.6.2 创建和释放
5.6.2.1 函数原型
struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd); struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen); //释放 void evconnlistener_free(struct evconnlistener *lev);
两个函数的不同在于如何建立监听套接字。evconnlistener_new()函数假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。如果要 libevent 分配和绑定套接字,可以调用 evconnlistener_new_bind() ,传输要绑定到的地址和地址长度。
5.6.2.2 监听器回调
typedef void (*evconnlistener_cb)(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);
接收到新连接会调用提供的回调函数 。
listener
参数是接收连接的连接监听器 。
sock
参数是 新接收的套接字。
addr
和 len
参数是接收连接的地址和地址长度。
ptr
是调 用 evconnlistener_new() 时用户提供的指针。
6、 示例代码
6.1 客户端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <fcntl.h> #include <signal.h> #include <event2/event.h> #include <event2/bufferevent.h> #include <event2/util.h> #include <arpa/inet.h> #define IP "127.0.0.1" #define PORT 8888 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("Connection error:%s\n", strerror(errno)); } } void send_data(evutil_socket_t fd, short events, void *arg) { struct bufferevent *bev = (struct bufferevent *)arg; char buf[32] = "Hello, world!"; printf("%s\n", buf); bufferevent_write(bev, buf, strlen(buf)); } int main (int argc, char **argv) { struct sockaddr_in addr; int len = sizeof(addr); struct event_base *base = NULL; struct bufferevent *bev = NULL; struct timeval tv; struct event *ev = NULL; base = event_base_new(); if( !base ) { printf("event_base_new() failure\n"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); inet_aton(IP, &addr.sin_addr); bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); if( !bev ) { printf("Could not create bufferevent\n"); event_base_free(base); return -2; } bufferevent_setcb(bev, NULL, NULL, event_callback, NULL); bufferevent_enable(bev, EV_READ | EV_WRITE); bufferevent_socket_connect(bev, (struct sockaddr *)&addr, len); tv.tv_sec = 3; tv.tv_usec = 0; ev = event_new(base, -1, EV_PERSIST, send_data, bev); event_add(ev, &tv); event_base_dispatch(base); event_free(ev); bufferevent_free(bev); event_base_free(base); return 0; }
6.2服务器端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <fcntl.h> #include <signal.h> #include <event2/event.h> #include <event2/bufferevent.h> #include <event2/util.h> #include <event2/listener.h> #include <arpa/inet.h> #define PORT 8888 void read_cb(struct bufferevent *bev, void *arg) { char buf[32]; int len; while( (len = bufferevent_read(bev, buf, sizeof(buf))) > 0 ) { buf[len] = '\0'; printf("%s\n", buf); } } 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("Connection error:%s\n", strerror(errno)); } } void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *arg) { struct event_base *base = (struct event_base *)arg; struct bufferevent *bev = NULL; bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); if( !bev ) { printf("bufferevent_socket_new() failure:%s\n", strerror(errno)); close(fd); return ; } bufferevent_setcb(bev, read_cb, NULL, event_callback, NULL); bufferevent_enable(bev, EV_READ | EV_WRITE); } int main (int argc, char **argv) { struct sockaddr_in addr; struct event_base *base = NULL; struct evconnlistener *listener = NULL; base = event_base_new(); if( !base ) { printf("event_base_new() failure\n"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&addr, sizeof(addr)); if( !listener ) { printf("Can't create a listener\n"); return -2; } event_base_dispatch(base); evconnlistener_free(listener); event_base_free(base); return 0; }
本文作者:梨子Li
本文链接:https://www.cnblogs.com/LiBlog--/p/18327543
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步