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

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。