如何在 libevent 中读取超过 4096 字节的数据

bufferevent 是 libevent 中相对高层的封装,较 event 使用起来方便很多。

之前有一个需求,需要从服务端读取数据进行操作,为了防止数据过大,在 bufferevent 的 read_callback 中循环调用 bufferevent_read,期望多次通过调用来读完所有的数据。

很显然,这个方法不行,第二次调用 bufferevent_read 会被阻塞,不符合预期,不能够像调用 read(2) 那样来使用。

实际上,bufferevent 内有可读数据并且大于水位 watermask 才会调用 read_callback,在 read_callback 只能调用一次 bufferevent_read 来读出缓冲区内的数据。

当一次 bufferevent_read 不能全部读取完数据怎么办,网上有人通过骚操作去修改 EVBUFFER_MAX_READ 来调大单次读取的值,然后再进行编译。不可取的行为!!!

buffervent 中的 watermask 是触发 read_callback 的关键,我们只要 bufferevent 内的数据大于设置的 watermask 即可,这样再次触发直接在 read_callback 内一次性读完。

watermask 如何设置

网络服务对数据的处理,肯定是要分包进行处理的,关闭 TCP_NODELAY 选项又会对性能造成影响。一般的解决方案是增加一个包头

bytes:          4         1          4
+----------------+----+----------------+
| MAGIC |Type| Len |
+----------------+----+----------------+
  • MAGIC 为标志魔数,4字节
  • Type 为包类型,1字节
  • Len 为 Header + Payload 长度,4字节

用代码来实现这个头部的也非常简单

static const uint32_t kMessageHeaderLen = 9;
static const uint32_t kMessageHeaderMagic = 0x00114514; enum MessageType : uint8_t {
kMessageTypeNULL = 0,
// ...
}; struct Message {
uint32_t magic;
MessageType type;
uint32_t len; Message() : type(kMessageTypeNULL) {} Message(MessageType type, uint32_t len) : magic(kMessageHeaderMagic), type(type), len(len) {} Message(char data[kMessageHeaderLen]) { decode(data); } void decode(const char data[kMessageHeaderLen]) {
magic = ntohl(*(uint32_t *)data);
type = *(MessageType *)(data + sizeof(magic));
len = ntohl(*(uint32_t *)(data + sizeof(magic) + sizeof(type))) < kMessageHeaderLen
? 0
: ntohl(*(uint32_t *)(data + sizeof(magic) + sizeof(type))) - kMessageHeaderLen;
} void encode(char data[kMessageHeaderLen]) {
*(uint32_t *)data = htonl(magic);
*(uint8_t *)(data + sizeof(magic)) = type;
*(uint32_t *)(data + sizeof(magic) + sizeof(type)) = htonl(len + kMessageHeaderLen);
}
};

watermask 就是这个包头中的 Len,在代码中的值为 Message::len + sizeof(Message).

如何利用 watermask 读取大于 4096 大小的数据

在第一次的 read_callback 内,先读取一个包头,将整个包的大小解析出来

  1. 如果 evbuffer 中的数据大小大于等于 Len 时,直接将所有的数据读取出来,并且要将 watermask 设置为 0(下次读取不受影响)
  2. 如果 evbuffer 中的数据大小小于 Len 时,所有还有数据没有从内核缓冲区读取到 bufferevent 内的 evbuffer 中,这个时候设置水位为 Len,在下次 read_callback 调用的时候,所有的数据都在 evbuffer 中。

代码如下:

static void read_callback(struct bufferevent *bev, void *arg) {
struct evbuffer *evbuf = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(evbuf);
if (len < 9)
return; char head[9];
evbuffer_copyout(evbuf, head, sizeof(head));
Message msg(head);
if (msg.magic != kMessageHeaderMagic) {
evbuffer_drain(evbuf, len);
return;
} if (msg.len + 9 <= len) {
std::vector<char> buf(msg.len, 0);
evbuffer_remove(evbuf, head, sizeof(head));
evbuffer_remove(evbuf, buf.data(), buf.capacity());
bufferevent_setwatermark(bev, EV_READ, 0, 0);
// handle data...
} else if (msg.len + 9 > len) {
bufferevent_setwatermark(bev, EV_READ, msg.len + 9, 0);
}
}

其它

虽然 libevent 每次读取 4096 个字节的确性能一般,在做代理的情况下更明显,毕竟 read/epoll_ctl 的次数都要更多一些。

之前碰到上面那个问题,搜索了很多都是修改源码的 EVBUFFER_MAX_READ 来解决这种饮鸩止渴的方案,想想就不靠谱,再怎么修改都容易超过这个限制。

如果是为了提升性能,直接使用 event 来直接操作 fd,效果可能更好一些。

如何在 libevent 中读取超过 4096 字节的数据的更多相关文章

  1. java:从指定问价中读取80个字节写入指定文件中

    import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; public class F ...

  2. How to read video frames in hadoop?如何在Hadoop中读取视频帧?

    To process specialized file formats (such as video) in Hadoop, you'd have to write a custom InputFor ...

  3. 如何在Framework中读取bundle中的Res

    前因: 因为公司上架前后的原因,外围的平台层部分提前上线,而我做的功能部分需要晚一些上线,是单独的一个工程在其他仓库开发. 我的资源文件放在Bundle中.合到主工程中,资源文件不用改,直接拖进去.倒 ...

  4. 如何在springboot中读取自己创建的.properties配置的值

    在实体类里面加上 @PropertySource("classpath:/robot_config.properties") robot_config.properties // ...

  5. 用python+openpyxl从表格中读取测试用例的多条数据,然后将执行结果写入表格中

    # -*- coding: utf-8 -*- from selenium import webdriver from openpyxl import load_workbook class mylo ...

  6. android中读取SD卡上的数据

    通过Context的openFileInput或者openFileOutput打开的文件输入输出流是操作应用程序的数据文件夹里的文件,这样存储的大小比较有限,为了更好的存取应用程序的大文件数据,应用程 ...

  7. 如何在python中读写和存储matlab的数据文件(*.mat)

    使用sicpy.io即可.sicpy.io提供了两个函数loadmat和savemat,非常方便. 以前也有一些开源的库(pymat和pymat2等)来做这个事, 不过自从有了numpy和scipy以 ...

  8. libevent中数据缓冲区buffer分析

    很多时候为了应对数据IO的"慢"或者其他原因都需要使用数据缓冲区.对于数据缓冲,我们不陌生,但是对于如何实现这个缓冲区,相信很多时候大家都没有考虑过.今天就通过分析libevent ...

  9. 接口测试中读取excel中的请求数据含有中文问题,UnicodeEncodeError: 'latin-1' codec can't encode character '\u5c0f' in position

    错误信息:UnicodeEncodeError: 'latin-1' codec can't encode character '\u5c0f' in position 31: Body ('小') ...

  10. 如何在Sql Server中读取最近一段时间的记录,比如取最近3天的或最近3个月的记录。

    如何在Sql Server中读取最近一段时间的记录,比如取最近3天的或最近3个月的记录. 主要用到DATEADD函数,下面是详细语句 取最近3天 select * from 表名where rq> ...

随机推荐

  1. [转帖]SQL Server数据库重建索引、更新统计信息

    https://vip.kingdee.com/article/183932?productLineId=8 SQL Server数据库有时由于长期未做索引重建,导致SQL执行效率下降,当表的索引碎片 ...

  2. [转帖]configure: error: cannot guess build type;you must specify one

    该问题一般出现在国产平台,从错误描述来看,意思是:无法猜测build类型,你必须指定一个. 解决办法: 1. 在系统/usr路径下搜索 config.guess 和 config.sub 这两个文件. ...

  3. [转帖]Linux中awk命令正确的求最大值、最小值、平均值、总和

    https://blog.csdn.net/fireblue1990/article/details/51622416 test.txt文件内容: 9 11 35 21 42 118 求最大值: aw ...

  4. 【转帖】一道面试题:JVM老年代空间担保机制

    面试问题 昨天面试的时候,面试官问的问题: 什么是老年代空间担保机制?担保的过程是什么? 老年代空间担保机制是谁给谁担保? 为什么要有老年代空间担保机制?或者说空间担保机制的目的是什么? 如果没有老年 ...

  5. [转帖]jmeter压力测试

    使用jmeter 进行并发压力测试. 首先需要安装好jmeter,下面以widows操作平台为例: 1.确保电脑安装并配置好java环境:具体怎么下载和配置请自行百度: 2.登录jmeter官网htt ...

  6. [转帖]TCP/IP RFC

    TCP/IP RFC-阿里云开发者社区 TCP/IP 标准是在一系列称为 RFC 的文档中发布的.RFC 是目前仍在发展的描述 TCP/IP 和 Internet 内部工作的一系列报告.协议的提议以及 ...

  7. docker 镜像导出和导入(适用于内网无法拉镜像的问题)

    1.在外网将镜像从指定的仓库拉下来 docker pull consul 现在已将consul镜像拉到了可连外网的服务器  2.将镜像把包到指定的tar文件中 docker save consul:l ...

  8. YiGo学习(一)YiGo介绍

    YiGo是一种开发语言,是一种面向业务人员进行管理信息系统开发的特定领域语言,属于第五代计算机语言.它可以在图形化界面上进行选择.拖拽等动作进行管理业务建模,通过建立对系统需求的描述模型由计算机自动生 ...

  9. 苹果打破12年惯例:iPad一整年未更新

    1月2日消息,据媒体报道,自2010年首次亮相以来,苹果一直保持着每年至少发布一款新型号的传统. 但是在过去的2023年,苹果没有发布iPad,2023年苹果发布的唯一与iPad相关的产品是USB-C ...

  10. 【二叉树】二叉树的深度优先遍历DFS(前中后序遍历)和广度优先遍历BFS(层序遍历)详解【力扣144,94,145,102】【超详细的保姆级别教学】

    [二叉树]二叉树的深度优先遍历(前中后序遍历)和广度优先遍历(层序遍历)详解[超详细的保姆级别教学] 先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常 ...