libevent库1.4升级到2.0时无法flush的解决办法(互相踢下线)
来源:互联网 发布:svm算法推荐 编辑:程序博客网 时间:2024/04/30 10:26
libevent的接口兼容性做的还算不错,基本上替换一下就转到新版本了。但是,强制flush数据的时候出了问题。目前的应用场景是,遇到顶号登录这种情形,先用bufferevent_write向客户端发送错误信息,然后再断开socket。用的flush是这样的:
void try_flush(bufferevent *bev) { int size = evbuffer_get_length(bufferevent_get_output(bev)); if (size > 0) { evbuffer_write(bufferevent_get_output(bev), fd); } }
在1.4的时候,这段代码工作良好。但是,在2.0的libevent里,这段代码不能刷出数据。看了下errno和socket_errno,错误码是EINPROGRESS。百思不得其解下,在脚本层将关闭socket放在下一次libevent事件循环里完成,结果就好了。
大致猜测下,应该是libevent为了效率做的优化。因为write是比较昂贵的系统调用,若因为evbuffer_write的反复调用,而引起多次write,会影响性能。翻了一下libevent 2.0的代码,evbuffer_write是调用evbuffer_write_atmost的,对比下1.4 evbuffer_write和2.0的evbuffer_write_atmost:
intevbuffer_write(struct evbuffer *buffer, int fd){ int n;#ifndef WIN32 n = write(fd, buffer->buffer, buffer->off);#else n = send(fd, buffer->buffer, buffer->off, 0);#endif if (n == -1) return (-1); if (n == 0) return (0); evbuffer_drain(buffer, n); return (n);}
intevbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd, ev_ssize_t howmuch){ int n = -1; EVBUFFER_LOCK(buffer); if (buffer->freeze_start) { goto done; } if (howmuch < 0 || (size_t)howmuch > buffer->total_len) howmuch = buffer->total_len; if (howmuch > 0) {#ifdef USE_SENDFILE struct evbuffer_chain *chain = buffer->first; if (chain != NULL && (chain->flags & EVBUFFER_SENDFILE)) n = evbuffer_write_sendfile(buffer, fd, howmuch); else {#endif#ifdef USE_IOVEC_IMPL n = evbuffer_write_iovec(buffer, fd, howmuch);#elif defined(WIN32) /* XXX(nickm) Don't disable this code until we know if * the WSARecv code above works. */ void *p = evbuffer_pullup(buffer, howmuch); n = send(fd, p, howmuch, 0);#else void *p = evbuffer_pullup(buffer, howmuch); n = write(fd, p, howmuch);#endif#ifdef USE_SENDFILE }#endif if (n > 0) evbuffer_drain(buffer, n);done: EVBUFFER_UNLOCK(buffer); return (n);}
2.0有个显眼的freeze_start的检查,正是这个检查,让evbuffer_write的调用直接跳过了发送阶段。于是,在flush的时候加了一个dirty trick,直接调用了evbuffer_unfreeze:
void try_flush(bufferevent *bev) { int size = evbuffer_get_length(bufferevent_get_output(bev)); if (size > 0) { evbuffer_unfreeze(bufferevent_get_output(bev), 1); evbuffer_write(bufferevent_get_output(bev), fd); } }
问题解决
然后跟了下libevent 2.0的文档,里面提到freeze的作用:
You can use evbuffer_freeze() to temporarily suspend drains from or adds to a given evbuffer. This is useful for code that exposes an evbuffer as part of its public API, but wants users to treat it as a pure source or sink.
因为是从1.4移植到2.0的,所以没有理由自己freeze掉的。那么剩下的原因只会是——libevent自己freeze了这个buffer,内部对freeze的调用集中在bufferevent_sock.c和bufferevent_pair.c两个文件里,目前没有使用socketpair,于是目标只有bufferevent_sock.c
bufferevent_sock.c里,一个是bufferevent_socket_new函数调用了freeze,另一个是bufferevent_writecb。根据应用场景分析,目标应该是writecb。分析了一下libevent的流程,遇到socket为可写状态的时候,就会调用bufferevent_writecb,再调用用户设置的writecb。而这个bufferevent_writecb,主要工作就是unfreeze掉buffer,然后调用evbuffer_write_atmost刷出数据到socket里,接着重新freeze该buffer。
这就解释了为什么直接调用evbuffer_write没有作用,因为这个接口似乎就没有打算直接暴露给用户使用。根据这篇文章的分析,libevent对注册事件的检查顺序是超时事件,然后是I/O事件,并将其一一放入链表里,逐个调用回调函数进行处理。回头看应用场景,当一个玩家顶号登录时,首先触发了本轮的IO事件,解包处理逻辑,执行踢人的逻辑处理,向缓冲区写入对客户端的提示信息。注意,本轮对写缓存的修改,并不会立即触发bufferevent_writecb进行发送。因为,其触发的只是用户设置的callback和上一轮留下的deferred callback!正是因为这个原因,本次事件检查循环写入的数据,必须等到下个循环才能被执行。所以,脚本层想要发送完顶号提示信息后,立即断开socket,就会导致socket过早被断开,提示信息无法发送出去了。
update:
其实底层的write还是有可能没发完,evbuffer_write不提供保证。所以应该是去掉读回调,在写回调里判断是否已写完,写完则断开
- libevent库1.4升级到2.0时无法flush的解决办法(互相踢下线)
- libevent库1.4升级到2.0时无法flush的解决办法(互相踢下线)
- 关于WINXP中MP10无法升级到MP11的解决办法
- 升级到JDK9后Eclipse无法启动的解决办法
- 升级到win10之后怎么不能上网了 升级Win10无法上网的解决办法
- jfinal+H5的websocket 实现同一账户在不同地点不同电脑只能登陆一个(互相踢下线)
- xcode升级到6.4之后插件无法使用,无法使用的解决办法
- Windchill10.0升级到10.2版本导致Jersey-1.17无法正常工作的解决办法
- Ubuntu 16.04下无法将pip从8.1.2升级到9.0.1的解决办法
- Xcode 升级到9.0 后无法通过mainBundle获取资源文件的解决办法
- 中国电信3G客户端 无法升级的解决办法
- 关于aspcms2.3.6的升级包无法升级的解决办法
- Ubuntu 升级到12.04失败的解决办法
- 织梦5.6升级到5.7,发文章时无法“把数据保存到数据库主表 `dede_archives` 解决办法
- VC6.0工程升级到VS2010遇到问题及解决办法 【LNK2019】 无法解析的外部符号 __iob
- ios开发之升级到Xcode7之后插件无法使用与不小心点击Skipbundle的解决办法
- matcom安装时无法寻找到matlab.exe的解决办法
- virtualbox按照官网上的方法无法升级的解决办法!
- MIPI DSI协议介绍
- Socket 连接异常 之"由于目标机器积极拒绝,无法连接" 的诊断
- 深入理解Java:注解(Annotation)自定义注解入门
- 如何在Eclipse下查看JDK源代码
- SVN配置方法
- libevent库1.4升级到2.0时无法flush的解决办法(互相踢下线)
- 《从零开始学Swift》学习笔记(Day 56)—— Swift编码规范之命名规范
- Dll的注意问题
- C语言学习之关键字第二讲
- CountDownLatch与CyclicBarrier
- 在Android上实现多进程构架的浏览器(浏览器开发)的讨论
- Unity导入FBX汇总
- A+B Problem(V)(南阳oj844)
- C++随机数重复的问题