bufferevent 设置超时

本文探讨了在bufferevent中实现心跳控制的具体方法,包括如何设置读写超时及处理超时事件,以及如何在客户端和服务端之间正确地进行心跳交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用bufferevent 有一段时间了,自认为还蛮熟悉的。后来因为一项业务,需要把心跳的频率控制转到服务端来。

我们考虑两种情况,一是服务端只响应心跳,不做断开操作,断开操作由客户端收不到响应而发起;二是客户端定期发送心跳,服务端响应心跳,若一定时间未有心跳则断开客户端。或许还有两种的结合,两边都判断超时都断开。

最开始我们选择的是第一种场景,只收心跳且服务端不响应心跳,这样可以减少服务端的压力,但是却发现客户端已经显示连接断开了,但是服务端还处于连接状态,这个时候新的connect又进入了服务器。原因是在广域网中,客户端到服务端这一侧发生了丢包重传,客户端的收到莫名fin消息从而已发了断开,且一直处于close_wait状态。

可能大家要笑我傻逼,谁让你搞这样的东西。其实你还会发现客户端也有bug,它并没有调用close 进行关闭。




好了,哥哥我乖乖使用经典还不行吗?由服务端发起心跳,客户端响应,服务端可以随意升级心跳时间,比升级客户端容易多。由于使用的是bufferevent的架构,就想在bufferevent上加。但是一直没有找到合适的方法,于是乎就变成了这样




后来看bufferevent的源码,其实还是有超时的选项

/** @name Bufferevent event codes

    These flags are passed as arguments to a bufferevent's event callback.

    @{
*/
#define BEV_EVENT_READING	0x01	/**< error encountered while reading */
#define BEV_EVENT_WRITING	0x02	/**< error encountered while writing */
#define BEV_EVENT_EOF		0x10	/**< eof file reached */
#define BEV_EVENT_ERROR		0x20	/**< unrecoverable error encountered */
#define BEV_EVENT_TIMEOUT	0x40	/**< user-specified timeout reached */
#define BEV_EVENT_CONNECTED	0x80	/**< connect operation finished. */
/**@}*/

BEV_EVENT_TIMEOUT	0x40	
那怎么使用呢?


#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>

#include <event2/buffer.h>
#include <event.h>
#include <time.h>
#include "src/msg.h"

void Readfun (bufferevent* ev, void* arg);
void Eventfun (bufferevent* ev, short flag, void* arg);
void login (bufferevent*evClient);

int main ()
{
    struct event_base* base = event_base_new (); 
    int fd = socket(AF_INET, SOCK_STREAM, 0); 
    struct bufferevent* evClient = bufferevent_socket_new (base,fd, BEV_OPT_CLOSE_ON_FREE);

    struct sockaddr_in server;
    memset((uint8_t *)&server, 0, sizeof(server));
    server.sin_family = PF_INET;
    server.sin_port = htons(8901);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    bufferevent_socket_connect (evClient, (struct sockaddr *) &server, sizeof(server));

    bufferevent_setcb (evClient, Readfun, NULL, Eventfun, evClient);
    bufferevent_setwatermark (evClient, EV_READ, sizeof(msg_head_t), sizeof(msg_head_t));
    bufferevent_enable (evClient, EV_READ);
    struct timeval tv = {1,0};
    bufferevent_set_timeouts (evClient, &tv, NULL);

    event_base_loop (base, 0); 
}

void Readfun (bufferevent* evClient, void* arg)
{
    msg_head_t msg;
    size_t size = bufferevent_read (evClient, &msg, sizeof(msg));
    
    msg.len = htonl(sizeof(msg_head_t));
    msg.cmd = htons(CMD_STAT_RES);
    msg.seq = 0;
    
    bufferevent_write(evClient, &msg, sizeof(msg));
    
    fprintf (stderr, "im read %zu\n", size);
    /* 若需要修改读取的自己水平位,可以调用如下代码*/
    //    bufferevent_setwatermark (evClient, EV_READ, 8, 8);
    //    bufferevent_enable (evClient, EV_READ);
    
    
}

void Eventfun (bufferevent* ev, short flag, void* arg)
{
    fprintf (stderr, "i am event, flag 0x%hu, time %lu\n", flag, time(NULL));
    if (flag&BEV_EVENT_CONNECTED) {
        /* 业务操作,当连接成功是回调*/
        login (ev);
    }
    if (flag&BEV_EVENT_TIMEOUT) {
        /* 注意,若触发了timeout事件,那么read事件会被disable,
         * 此时若想继续运行,需要重新enable read事件
         * */
        struct bufferevent* evClient = (struct bufferevent*)arg;
        bufferevent_setwatermark (evClient, EV_READ, sizeof(msg_head_t), sizeof(msg_head_t));
        bufferevent_enable (evClient, EV_READ);
    }
}

void login (bufferevent *evClient)
{
}

运行结果如下:



总结:

1. 通过调用 bufferevent_set_timeouts 设置定时器

2. 定时器触发时会清除read/write 事件

3. 当读/写事件触发时,对应的timer会被重置,即重新计时。


源码拾遗:

我们来看看libevent的实现:

/**
  Set the read and write timeout for a bufferevent.

  A bufferevent's timeout will fire the first time that the indicated
  amount of time has elapsed since a successful read or write operation,
  during which the bufferevent was trying to read or write.

从最后一次成功读取或写入操作开始计时,当指定的时间间隔消逝后,
timeout事件便会触发一次,且在此期间读写事件仍然存在。

  (In other words, if reading or writing is disabled, or if the
  bufferevent's read or write operation has been suspended because
  there's no data to write, or not enough banwidth, or so on, the
  timeout isn't active.  The timeout only becomes active when we we're
  willing to actually read or write.)

换一句话说,如果读写事件因为没有足够的可读数据或写缓冲区等其他原因而挂起,
即阻塞了,timeout事件也是不会触发的。
timeout事件只有在我们等待读写事件触发的时候才会触发。

 Calling bufferevent_enable or setting a timeout for a bufferevent
  whose timeout is already pending resets its timeout.

若一个bufferevent的timeout事件已经触发了,正在等待执行,
此时调用bufferevent_enable 或者重新设置timeout,
都会将原来待执行的timeout清除。

  If the timeout elapses, the corresponding operation (EV_READ or
  EV_WRITE) becomes disabled until you re-enable it again.  The
  bufferevent's event callback is called with the
  BEV_EVENT_TIMEOUT|BEV_EVENT_READING or
  BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING.

当一个timeout事件触发后,他的读写事件将会被disable掉,如果想读写事件继续,
则需要重新调用bufferevent_enable接口。

  @param bufev the bufferevent to be modified
  @param timeout_read the read timeout, or NULL
  @param timeout_write the write timeout, or NULL
 */
int bufferevent_set_timeouts(struct bufferevent *bufev,
    const struct timeval *timeout_read, const struct timeval *timeout_write);





`bufferevent` 是 `libevent` 库中的一个核心组件,用于简化网络数据的读写操作。它提供了一个高级接口,封装了底层的 I/O 操作,并支持多种传输方式(如套接字、文件描述符等)。通过 `bufferevent`,开发者可以更容易地处理数据流,而无需直接操作低级的 I/O 函数。 --- ### 1. `bufferevent` 的工作原理 `bufferevent` 的主要功能是管理输入输出缓冲区,并通过事件驱动的方式通知应用程序何时可以读取或写入数据。以下是其工作原理的关键点: #### (1) 缓冲区管理 - **输入缓冲区**:当数据到达时,`bufferevent` 会将数据存储到输入缓冲区中。 - **输出缓冲区**:当应用程序需要发送数据时,数据会被写入输出缓冲区,然后由 `bufferevent` 负责实际的写入操作。 #### (2) 回调机制 - **读回调**:当输入缓冲区中有数据可读时,`bufferevent` 会触发用户定义的读回调函数。 - **写回调**:当所有数据从输出缓冲区成功写入时,`bufferevent` 会触发用户定义的写回调函数。 - **事件回调**:当发生错误(如连接关闭或超时)时,`bufferevent` 会触发事件回调函数。 #### (3) 自动重试和错误处理 - 如果底层 I/O 操作失败(如网络中断),`bufferevent` 会尝试自动重试或通知应用程序进行处理。 #### (4) 支持多种传输方式 - `bufferevent` 可以与套接字、文件描述符或其他 I/O 对象配合使用。 --- ### 2. 示例代码 以下是一个简单的示例,展示如何使用 `bufferevent` 进行网络通信: ```c #include <event2/event.h> #include <event2/bufferevent.h> #include <stdio.h> #include <stdlib.h> // 读回调函数 void read_cb(struct bufferevent *bev, void *ctx) { char buf[1024]; int len = bufferevent_read(bev, buf, sizeof(buf) - 1); if (len > 0) { buf[len] = '\0'; printf("Received: %s", buf); } } // 写回调函数 void write_cb(struct bufferevent *bev, void *ctx) { printf("All data has been written.\n"); } // 事件回调函数 void event_cb(struct bufferevent *bev, short events, void *ctx) { if (events & BEV_EVENT_EOF) { printf("Connection closed.\n"); } else if (events & BEV_EVENT_ERROR) { printf("Error occurred.\n"); } bufferevent_free(bev); // 释放资源 } int main() { struct event_base *base = event_base_new(); if (!base) { fprintf(stderr, "Could not initialize libevent!\n"); return 1; } // 创建监听套接字 struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(7777); evutil_socket_t listener = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_reusable(listener); if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } if (listen(listener, 10) < 0) { perror("listen"); return 1; } // 设置 accept 回调函数 struct evconnlistener *listener_obj = evconnlistener_new(base, [](evutil_socket_t fd, struct sockaddr *sa, int socklen, void *ctx) -> void { struct bufferevent *bev = bufferevent_socket_new((struct event_base *)ctx, fd, BEV_OPT_CLOSE_ON_FREE); if (!bev) { perror("Could not create bufferevent"); return; } bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); bufferevent_enable(bev, EV_READ | EV_WRITE); }, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, NULL); if (!listener_obj) { perror("Could not create listener"); return 1; } // 启动事件循环 event_base_dispatch(base); // 清理资源 evconnlistener_free(listener_obj); event_base_free(base); return 0; } ``` --- ### 3. 代码解释 #### (1) 初始化事件基础对象 ```c struct event_base *base = event_base_new(); ``` - 创建一个事件基础对象,用于管理事件循环。 #### (2) 创建监听套接字 ```c evutil_socket_t listener = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_reusable(listener); if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } if (listen(listener, 10) < 0) { perror("listen"); return 1; } ``` - 创建一个 TCP 监听套接字,并绑定到本地地址和端口 `7777`。 #### (3) 设置 `bufferevent` 和回调函数 ```c struct 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 | EV_WRITE); ``` - 为每个新连接创建一个 `bufferevent` 对象。 - 设置读、写和事件回调函数。 - 启用读写事件监听。 #### (4) 启动事件循环 ```c event_base_dispatch(base); ``` - 启动事件循环,等待并处理事件。 --- ### 4. 总结 `bufferevent` 提供了一种高效且简单的方式来管理网络 I/O 操作。它通过缓冲区管理和回调机制,使得开发者可以专注于业务逻辑,而无需关心底层的 I/O 实现细节。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值