libevent库1.4升级到2.0时无法flush的解决办法
libevent的接口兼容性做的还算不错,基本上替换一下就转到新版本了。但是,强制flush数据的时候出了问题。目前的应用场景是,遇到顶号登录这种情形,先用bufferevent_write向客户端发送错误信息,然后再断开socket。用的flush是这样的:
void try_flush(bufferevent *bev) {
int size = evbuffer_get_length(bufferevent_get_output(bev));
if (size > ) {
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:
int
evbuffer_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, );
#endif
if (n == -)
return (-);
if (n == )
return ();
evbuffer_drain(buffer, n); return (n);
}
int
evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
ev_ssize_t howmuch)
{
int n = -; EVBUFFER_LOCK(buffer); if (buffer->freeze_start) {
goto done;
} if (howmuch < || (size_t)howmuch > buffer->total_len)
howmuch = buffer->total_len; if (howmuch > ) {
#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, );
#else
void *p = evbuffer_pullup(buffer, howmuch);
n = write(fd, p, howmuch);
#endif
#ifdef USE_SENDFILE
}
#endif if (n > )
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 > ) {
evbuffer_unfreeze(bufferevent_get_output(bev), );
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的解决办法的更多相关文章
- JavaScript中的ParseInt("08")和“09”返回0的原因分析及解决办法
今天在程序中出现一个bugger ,调试了好久,最后才发现,原来是这个问题. 做了一个实验: alert(parseInt("01")),当这个里面的值为01====>07时 ...
- Linux 出现telnet: 127.0.0.1: Connection refused错误解决办法
Linux 出现telnet: connect to address 127.0.0.1: Connection refused错误解决办法 没有xinetd服务: 1./etc/init.d目录中放 ...
- Redis 3.0版本启动时出现警告的解决办法
原文:http://m.blog.csdn.net/article/details?id=50864933 Redis 3.0.7版本启动时出现警告的解决办法 发表于2016/3/12 12:52:4 ...
- 转:Selenium2.0 click()不生效的解决办法
除了http://573301735.com/?p=5126讲的,昨天又发现一个让我1个小时生不如死的问题,就是使用两个不同的配置文件来初始化driver,findelement方法获取到的坐标居然不 ...
- Centos7从3.10升级内核到4.9后无法启动解决办法:mpt[23]sas驱动问题
Centos7升级内核后无法启动解决办法:mpt[23]sas驱动问题 前言 这个问题存在有一段时间了,之前做的centos7的ISO,在进行内核的升级以后就存在这个问题: 系统盘在板载sata口上是 ...
- DotNetCore.1.0.1-VS2015Tools.Preview2.0.3 相关问题及解决办法
本月16号,MS发布了 .NET Core 1.1.作为一个用贯MS产品的小盆友,我第一时间就把相关的安装包下载下来了,然后果断安装(入坑). 我猜你来看这篇博客可能遇到了和我一样的问题. 问题0:正 ...
- ubuntu python3.5升级3.6后打不开终端的解决办法
ubuntu python3.5升级3.6后打不开终端了. 解决办法如下: 1.Ctrl+Alt+F1进入命令行终端,我的电脑按Ctrl+Alt+F1没反应,按住Ctrl+Alt然后从F1到F5一个个 ...
- Ubuntu16.04编译Android6.0/cm13.0教程及相关错误解决办法
一.必备工作 1.安装依赖库 sudo apt--dev libesd0-dev git-core gnupg flex bison gperf build-essential zip curl zl ...
- The specified framework 'Microsoft.NETCore.App', version '1.0.1' was not found 解决办法
环境:Centos 7 已经下载安装.NET Core 1.1 Microsoft .NET Core Shared Framework Host Version : Build : 928f77c4 ...
随机推荐
- iOS开发数据库篇—SQL
iOS开发数据库篇—SQL 一.SQL语句 如果要在程序运行过程中操作数据库中的数据,那得先学会使用SQL语句 1.什么是SQL SQL(structured query language):结构化查 ...
- EasyUi 分页 和 表格数据加载
这里说明的是将说有数据先返回到前端再由前端去分页,性能可能没有先在后台分好页再返回给前端高 但如果操作不涉及大数据的话也没什么大问题,具体问题具体分析 要使用分页控件首先要声明初始化一下: //设置分 ...
- HDU4511 AC自动机+dijkstra
题意: Description 终于放寒假了,小明要和女朋友一起去看电影.这天,女朋友想给小明一个考验,在小明正准备出发的时候,女朋友告诉他,她在电影院等他,小明过来的路线必须满足给定的规则: 1. ...
- (六)makefile编程
最简单的makefile: all: gcc server.c -o ser gcc client.c -o cli clear: rm ser cli *.o -rf #rm -rf表示删除文件 ...
- 理解RxJava线程模型
RxJava作为目前一款超火的框架,它便捷的线程切换一直被人们津津乐道,本文从源码的角度,来对RxJava的线程模型做一次深入理解.(注:本文的多处代码都并非原本的RxJava的源码,而是用来说明逻辑 ...
- hdu 5358 First One
题目链接:hdu 5358 思路不难理解,就是个尺取法而已,floor(log2X) + 1 就是求 X 的二进制表示的位数,对于题目来说这个值最多只是 30+,从这里入手开始枚举,运用尺取法可以达到 ...
- 如何正确的将J2ee项目部署到Tomcat
如何正确的将J2ee项目部署到Tomcat 1.打开配置文件(我的如下:C:\Program Files\Apache Software Foundation\Tomcat 7.0\conf\serv ...
- pfile 与 spfile
启动方式与顺序: 启动顺序:dbs 下的 init --> dbs 下的 spfile 如果 pfile 中没有指定 spfile 参数,那么数据库以 pfile 方式启动 如果 pfile 中 ...
- CSS 3 动画2D
动画分为两种,1,逐帧动画 组成动画的每一个画面就是一帧 2,关键帧动画,确定关键帧电脑会自动过度 动画中如果遇到不兼容的问题也是要加前缀 @-webkit-keyframes规则 @-webk ...
- MySQL数据库管理用户权限
http://blog.itpub.net/7607759/viewspace-675079/ 2.2 授予权限 前面提到了grant命令,grant的语法看起来可是相当复杂的呐: GRANT pri ...