bufferevent 设置超时

用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);





  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值