2024年6月

服务搭建

我所编写的
docker-compose.yml
如下,成功运行后将源码目录移动至
/data/opengrok/src
,重启容器使得opengrok快速更新索引

services:
  opengrok:
    container_name: opengrok
    # 1.6版本在使用中还算稳定
    image: opengrok/docker:1.6.9
    init: true
    ports:
      - "$WEB_PORT:8080/tcp"
      # REST_PORT在关掉了远程同步后实际没有效果
      - "127.0.0.1:5000:5000/tcp"
    environment:
      # 索引刷新的间隔时间
      SYNC_PERIOD_MINUTES: 10
      # REST_PORT在关掉了远程同步后实际没有效果
      REST_PORT: 5000
      # 我关掉了git远程同步和历史搜索 根据硬件性能调整 --thread 1 --memory 512 
      INDEXER_OPT: '--verbose --thread 1 --memory 512 --renamedHistory off --leadingWildCards on --remote off'
      # 根据硬件性能调整 WORKERS: 1
      WORKERS: 1
      NOMIRROR: 'TRUE'
      CHECK_INDEX: 'TRUE'
    volumes:
       # 将源码目录移动至 /data/opengrok/src 重启容器使得opengrok快速更新索引
       - '/data/opengrok/src/:/opengrok/src/'
       - '/data/opengrok/etc/:/opengrok/etc/'
       - '/data/opengrok/data/:/opengrok/data/'
修改字体

修改网页代码的字体显示

默认的字体是Serif,想修改为Console

# 进入容器并查找容器中war位置
docker exec -it $CONTAIN_ID /bin/bash
# 修改的目标war文件是 /opengrok/lib/source.war
# 可以根据重启容器后 war文件的修改时间判断war原始文件
find / -name "*.war"
# 修改
vim source/list.jsp
# 解压缩war
unzip source.war -d source
# 重新打包war
jar -cvfM0 source.war -C source .

修改对比

--- list.jsp.bak        2024-03-05 19:17:24.287951123 +0800
+++ list.jsp    2024-03-05 21:27:27.000000000 +0800
@@ -293,7 +293,8 @@
         if (xrefFile != null) {
 %>
 <div id="src" data-navigate-window-enabled="<%= navigateWindowEnabled %>">
-    <pre><%
+    <pre style="font-family:Consolas;!important;">
+<%
             boolean compressed = xrefFile.getName().endsWith(".gz");
             Util.dumpXref(out, xrefFile, compressed, request.getContextPath());
     %></pre>

重启容器后新字体生效

在人生的重要时刻,我站在了毕业的门槛上,望着前方的道路,心中涌动着对未来的无限憧憬与些许忐忑。面前,两条道路蜿蜒伸展:一是继续在职场中寻求稳定,一是勇敢地走出一条属于自己的创新之路。尽管面临年龄和现实的挑战,我仍旧选择勇往直前,用技术这把钥匙,开启新的人生篇章。

回首过去,我深知时间宝贵,精力有限。因此,在繁忙的工作和不断的创意激荡中,我必须谨慎地挑选那些能够让我全身心投入的项目。一直以来,我怀揣着用技术造福人类的梦想,虽然听起来有些遥远,但这确实是我内心深处的真实声音。

多年的职业经历让我更加明确自己的方向。在参与的多个项目中,我始终坚持实用性和价值。比如,为了帮助父亲养鱼务农,我开发了农场小助手,让繁琐的农活变得更加便捷和智能;为了丰富小朋友的娱乐生活,我打造了MyTV,不仅让家长能够控制观看内容,还融入了学习和亲子互动的功能;为了解决新小区居民的生活问题,我创建了翡丽在线,成为他们生活的百科全书;针对企业管理问题,我推出了产销存管理平台,帮助企业实现科学化的库存管理。

然而,这些项目虽然各具特色,但都面临着盈利模式的挑战。如何在保证实用性的同时,找到可持续的商业模式,成为我思考的重点。其中,我对农场小助手最为感兴趣,它的实用性极高,但硬件定制的道路充满挑战;MyTV目前主要面向朋友使用,商业化尚需时日;翡丽在线计划寻找合作伙伴共同运营;产销存管理平台已经得到了市场的初步认可,但仍需进一步扩大影响力;IoTBrowser则是我未来重点投入的对象,它有望通过技术革新,降低开发成本,为更多企业带来价值。

站在新的起点上,我将带着这些宝贵的经验和梦想,继续前行。无论前方道路如何曲折,我都将坚定信念,用技术书写属于自己的人生篇章。

IoTBrowser V2.0:引领物联网时代的全新浏览器


以上文案经过文心一言美化确实高大上了,但是又感觉不接地气,各位觉得这个小编怎么样?

全文速览

  • python的不同缓存组件的使用场景和使用样例
  • cachetools的使用

项目背景

代码检查项目,需要存储每一步检查的中间结果,最终把结果汇总并写入文件中

在中间结果的存储中

  • 可以使用context进行上下文的传递,但是整体对代码改动比较大,违背了开闭原则
  • 也可以利用缓存存储,处理完成之后再统一读缓存并写入文件

在权衡了不同方案后,我决定采用缓存来存储中间结果。接下来,我将探讨 Python 中可用缓存组件。

python缓存分类

决定选择缓存,那么python中都有哪些类型的缓存呢?

1. 使用内存缓存(如
functools.lru_cache

这是最简单的一种缓存方法,适用于小规模的数据缓存。使用
functools.lru_cache
可以对函数结果进行缓存。

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

2. 使用本地文件缓存(如
diskcache

如果缓存的数据较大,或者需要跨进程共享缓存,可以使用文件系统缓存库,例如
diskcache

import diskcache as dc

cache = dc.Cache('/tmp/mycache')

@cache.memoize(expire=3600)
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

3. 使用分布式缓存(如 Redis)

对于需要跨多个应用实例共享缓存的数据,可以使用 Redis 这样的分布式缓存系统。

import redis
import pickle

r = redis.StrictRedis(host='localhost', port=6379, db=0)

def expensive_function(param1, param2):
    key = f"{param1}_{param2}"
    cached_result = r.get(key)
    if cached_result:
        return pickle.loads(cached_result)
    
    result = # 进行一些耗时的操作
    r.set(key, pickle.dumps(result), ex=3600)  # 设置缓存过期时间为1小时
    return result

总结

如果只是简单的小规模缓存,
lru_cache
足够;如果需要持久化或分布式缓存,可以考虑使用
diskcache
或 Redis;如果使用了 Web 框架,使用框架自带的缓存功能会更方便。

python内存缓存分类

兼顾速度和成本以及实现的复杂度,最终决定使用内存缓存,在 Python 中,内存缓存组件有许多选择,每种都有其特定的优点和适用场景。以下是一些常见的内存缓存组件:

1.
functools.lru_cache

lru_cache
是 Python 标准库中的一个装饰器,用于缓存函数的返回结果,基于最近最少使用(LRU)策略。

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

2.
cachetools

cachetools
是一个第三方库,提供了多种缓存策略,包括 LRU、LFU、TTL(基于时间的缓存)等。

from cachetools import LRUCache, cached

cache = LRUCache(maxsize=100)

@cached(cache)
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

3.
django.core.cache

如果使用 Django 框架,Django 自带了缓存框架,支持多种缓存后端,包括内存缓存。


settings.py
中配置内存缓存:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

4.
Flask-Caching

如果使用 Flask 框架,
Flask-Caching
插件可以方便地实现内存缓存。

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})

@app.route('/expensive')
@cache.cached(timeout=60)
def expensive_function():
    # 进行一些耗时的操作
    return result

5.
requests_cache

requests_cache
是一个专门用于缓存 HTTP 请求的库,支持多种缓存后端,包括内存缓存。

import requests
import requests_cache

requests_cache.install_cache('demo_cache', backend='memory', expire_after=3600)

response = requests.get('https://api.example.com/data')

6.
dogpile.cache

dogpile.cache
是一个更高级的缓存库,提供了灵活的缓存后端和缓存失效策略。

from dogpile.cache import make_region

region = make_region().configure(
    'dogpile.cache.memory',
    expiration_time=3600
)

@region.cache_on_arguments()
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

7.
joblib.Memory

joblib.Memory
常用于科学计算和数据处理领域,用于缓存函数的计算结果。

from joblib import Memory

memory = Memory(location='/tmp/joblib_cache', verbose=0)

@memory.cache
def expensive_function(param1, param2):
    # 进行一些耗时的操作
    return result

总结

根据具体需求和使用场景选择合适的内存缓存组件。对于简单的缓存需求,可以使用
functools.lru_cache

cachetools
。对于 Web 应用,
django.core.cache

Flask-Caching
是不错的选择。对于 HTTP 请求缓存,可以使用
requests_cache
。对于科学计算,
joblib.Memory
是一个好选择。

cachetools使用

我的项目是一个命令行执行的项目,综合考量最终决定选择cachetools

  1. 安装
    cachetools
pip install cachetools
  1. 实现缓存工具类
from cachetools import LRUCache
from cachetools import Cache
from siada.cr.logger.logger import logger


class CacheUtils:
    """
    缓存工具类
    """

    def __init__(self, cache: Cache = None):
        self.cache = cache if cache else LRUCache(maxsize=100)

    def get_value(self, cache_key: str):
        value = self.cache.get(cache_key, None)
        if value is not None:
            logger.info(f"Cache hit for key: {cache_key}")
        else:
            logger.info(f"Cache miss for key: {cache_key}")
        return value

    def set_key_value(self, cache_key: str, value):
        self.cache[cache_key] = value
        logger.info(f"Set cache key: {cache_key} with value: {value}")

    def set_key_list(self, cache_key: str, value):
        v = self.cache.get(cache_key, None)
        if v is not None:
            v.append(value)
        else:
            self.cache[cache_key] = [value]

    def clear_cache(self):
        self.cache.clear()


# TODO 如果后续生成过程改为多线程并发,需考虑数据竞争问题
cache = CacheUtils()


更多惊喜

我还将定期分享:

  • 最新互联网资讯
    :让你时刻掌握行业动态。

  • AI前沿新闻
    :紧跟技术潮流,不断提升自我。

  • 技术分享与职业发展
    :助你在职业生涯中走得更远、更稳。

  • 程序员生活趣事
    :让你在忙碌的工作之余找到共鸣与乐趣。

关注回复【1024】惊喜等你来拿!

点击查看惊喜

敬请关注【程序员世杰】

点击关注程序员世杰

Evbuffers:缓冲 IO 的实用程序功能

简介

Libevent 的 evbuffer 功能实现了一个字节队列, 优化了将数据添加到末尾并从前面删除数据。

Evbuffers 通常可用于执行“缓冲区” 缓冲网络 IO 的一部分。它们不提供以下功能 安排 IO 或在 IO 准备就绪时触发 IO:就是这样 Buffer事件确实如此。

本章中的函数在 event2/buffer.h 中声明,除非 另有说明。

创建或释放 evbuffer

接口

struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);

这些函数应该比较清楚: evbuffer_new() 分配 并返回一个新的空 evbuffer,evbuffer_free() 删除一个和 它的所有内容。

这些函数从 Libevent 0.8 开始就已经存在了。

Evbuffers 和线程安全

接口

int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);

默认情况下,从多个线程访问 evbuffer 是不安全的 。如果需要多个线程同时访问,可以调用 evbuffer_enable_locking() 在 evbuffer 上。如果其
lock
参数为 NULL,则 Libevent使用evthread_set_lock_creation_callback提供创建函数分配新锁 。否则 它使用参数作为锁。

evbuffer_lock() 和 evbuffer_unlock() 函数获取和 分别释放 EVBUFFER 上的锁。您可以使用它们来 使一组操作成为原子操作。如果尚未启用锁定 evbuffer,这些函数不执行任何操作。

(请注意,您不需要调用 evbuffer_lock() 和 evbuffer_unlock() 围绕
单个
操作:如果锁定是 在 EVSuBuffer 上启用,各个操作已经是原子操作。 您只需要手动锁定 evbuffer 时,您才需要手动锁定 evbuffer 一个需要在没有另一个线程对接的情况下执行的操作。

这些函数都是在 Libevent 2.0.1-alpha 中引入的。

检查 evbuffer

接口

size_t evbuffer_get_length(const struct evbuffer *buf);

此函数返回存储在 evbuffer 中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

接口

size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

此函数返回连续存储在evbuffer前面的字节数。evbuffer中的字节可以存储在多个单独的存储器块中;此函数返回当前存储在第一个块中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

向 evbuffer 添加数据:基础知识

接口

int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);

此函数将
数据
中的
datlen
字节追加到
buf
的末尾。成功时返回 0,失败时返回 -1。

接口

int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);

这些函数将格式化数据追加到
buf
的末尾。格式 参数和其他剩余参数的处理方式就好像由 C 处理一样 库函数分别为“printf”和“vprintf”。主要工作内容 返回追加的字节数。

接口

int evbuffer_expand(struct evbuffer *buf, size_t datlen);

此函数更改缓冲区中的最后一个内存块,或添加一个新的内存块,使缓冲区现在足够大,可以在没有任何进一步分配的情况下包含datlen字节。

例子

/* Here are two ways to add "Hello world 2.0.1" to a buffer. */
/* Directly: */
evbuffer_add(buf, "Hello world 2.0.1", 17);

/* Via printf: */
evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);

evbuffer_add() 和 evbuffer_add_printf() 函数是在 Libevent 0.8;evbuffer_expand() 在 Libevent 0.9 中,并且 evbuffer_add_vprintf() 首次出现在 Libevent 1.1 中。

将数据从一个 evbuffer 移动到另一个 evbuffer

为了提高效率,Libevent 优化了从 一个 evbuffer 到另一个 evbuffer。

接口

int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);
int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
    size_t datlen);

evbuffer_add_buffer() 函数将 src的所有数据 移动到dst末尾。成功时返回 0,失败时返回 -1。

evbuffer_remove_buffer() 函数精确地移动
datlen
字节 从 src到 dst 的末尾,尽可能少地复制。如果 要移动的字节数少于
datlen
,它会移动所有字节。 它返回移动的字节数。

我们在 Libevent 0.8 中引入了 evbuffer_add_buffer(); evbuffer_remove_buffer() 是 Libevent 2.0.1-alpha 中的新功能。

将数据添加到 evbuffer 的前面

接口

int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);
int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);

这些函数的行为为 evbuffer_add() 和 evbuffer_add_buffer() 除了
它们将数据
移动到 目标缓冲区。

这些函数应谨慎使用,切勿在 evbuffer 上使用 与 BufferEvent 共享。它们是 Libevent 2.0.1-alpha 中的新功能。

重新排列 evbuffer 的内部布局

有时,您希望查看evbuffer前面的前N个字节的数据,并将其视为一个连续的字节数组。要做到这一点,必须首先确保缓冲区的前面确实是连续的。。

接口

unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);

evbuffer_pullup() 函数将
buf
的第一个
大小
字节“线性化”,根据需要复制或移动它们以确保它们都是 连续并占用相同的内存块。如果
大小
是 负数,该函数将整个缓冲区线性化。如果
大小
是 大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup() 返回指向第一个的指针 BUF 中的字节。

调用大大小的 evbuffer_pullup() 可能会很慢,因为 它可能需要复制整个缓冲区的内容。

#include <event2/buffer.h>
#include <event2/util.h>

#include <string.h>

int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
{
    /* Let's parse the start of a SOCKS4 request!  The format is easy:
     * 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
     * destip. */
    unsigned char *mem;

    mem = evbuffer_pullup(buf, 8);

    if (mem == NULL) {
        /* Not enough data in the buffer */
        return 0;
    } else if (mem[0] != 4 || mem[1] != 1) {
        /* Unrecognized protocol or command */
        return -1;
    } else {
        memcpy(port, mem+2, 2);
        memcpy(addr, mem+4, 4);
        *port = ntohs(*port);
        *addr = ntohl(*addr);
        /* Actually remove the data from the buffer now that we know we
           like it. */
        evbuffer_drain(buf, 8);
        return 1;
    }
}

注意

调用 evbuffer_pullup(),其大小等于 返回的值 evbuffer_get_contiguous_space() 不会导致任何数据被 复制或移动。

evbuffer_pullup() 函数是 Libevent 2.0.1-alpha 中的新功能: 以前版本的 Libevent 始终保持 evbuffer 数据连续, 不计成本。

从 evbuffer 中删除数据

接口

int evbuffer_drain(struct evbuffer *buf, size_t len);
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);

evbuffer_remove() 函数将
buf
前面的第一个
datlen
字节复制并移动到
data
的内存中。如果有 少于
DATLEN
字节数,该函数将复制所有字节 有。失败时返回值为 -1,否则为 复制的字节数。

evbuffer_drain() 函数的行为为 evbuffer_remove(),除了 它不会复制数据:它只是将其从前面删除 缓冲区。成功时返回 0,失败时返回 -1。

Libevent 0.8 引入了 evbuffer_drain();evbuffer_remove() 出现在 libevent 0.9.

从 evbuffer 中复制数据

有时,您希望在缓冲区的开头获取数据的副本,而不删除它。例如,您可能想要查看完整的记录某种已经到达,没有删除任何数据(如 evbuffer_remove会做的),或在内部重新排列缓冲区(如 evbuffer_pullup() 就可以了。

接口

ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t datlen);
ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf,
     const struct evbuffer_ptr *pos,
     void *data_out, size_t datlen);

evbuffer_copyout() 的行为与 evbuffer_remove() 类似,但不从缓冲区中删除所有数据。也就是说,它将
buf
前面的第一个
datlen
字节复制到
data
的内存中。如果有 少于
DATLEN
字节数,该函数将复制所有字节 有。失败时返回值为 -1,否则为 复制的字节数。

evbuffer_copyout_from() 函数的行为类似于 evbuffer_copyout(),但 它不是从缓冲区的前面复制字节,而是复制它们 从 pos 中提供的位置开始。请参阅“在 evbuffer“,以获取有关evbuffer_ptr结构的信息。

如果从缓冲区复制数据的速度太慢,请改用 evbuffer_peek()。

#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <stdlib.h>

int get_record(struct evbuffer *buf, size_t *size_out, char **record_out)
{
    /* Let's assume that we're speaking some protocol where records
       contain a 4-byte size field in network order, followed by that
       number of bytes.  We will return 1 and set the 'out' fields if we
       have a whole record, return 0 if the record isn't here yet, and
       -1 on error.  */
    size_t buffer_len = evbuffer_get_length(buf);
    ev_uint32_t record_len;
    char *record;

    if (buffer_len < 4)
       return 0; /* The size field hasn't arrived. */

   /* We use evbuffer_copyout here so that the size field will stay on
       the buffer for now. */
    evbuffer_copyout(buf, &record_len, 4);
    /* Convert len_buf into host order. */
    record_len = ntohl(record_len);
    if (buffer_len < record_len + 4)
        return 0; /* The record hasn't arrived */

    /* Okay, _now_ we can remove the record. */
    record = malloc(record_len);
    if (record == NULL)
        return -1;

    evbuffer_drain(buf, 4);
    evbuffer_remove(buf, record, record_len);

    *record_out = record;
    *size_out = record_len;
    return 1;
}

evbuffer_copyout() 函数首次出现在 Libevent 2.0.5-alpha 中; evbuffer_copyout_from() 是在 Libevent 2.1.1-alpha 中添加的。

面向行的输入

接口

enum evbuffer_eol_style {
        EVBUFFER_EOL_ANY,
        EVBUFFER_EOL_CRLF,
        EVBUFFER_EOL_CRLF_STRICT,
        EVBUFFER_EOL_LF,
        EVBUFFER_EOL_NUL
};
char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
    enum evbuffer_eol_style eol_style);

许多 Internet 协议使用基于行的格式。evbuffer_readln() 函数从 evbuffer 的前面提取一行并返回它 在新分配的 NUL终止字符串中。如果不是
n_read_out
NULL,**n_read_out* 设置为字符串中的字节数 返回。如果没有要读取的整行,则函数返回 零。行终止符不包含在复制的字符串中。

evbuffer_readln() 函数理解 4 种行终止格式:

  • EVBUFFER_EOL_LF

    行尾是单个换行符。(这也是 称为“\n”。它是 ASCII 值为 0x0A。

  • EVBUFFER_EOL_CRLF_STRICT

    一行的末尾是单回车,后跟一个 单行换行。(这也称为“\r\n”。ASCII 值 是0x0D 0x0A)。

  • EVBUFFER_EOL_CRLF

    该行的末尾是可选的回车符,后跟 换行。(换言之,它要么是“\r\n”,要么是“\n”。 此格式在分析基于文本的 Internet 时很有用 协议,因为标准通常规定“\r\n” 线路终止器,但不合格的客户有时会说只是 “\n”。

  • EVBUFFER_EOL_ANY

    行尾是任意数量的回车符的任意序列 和换行符。这种格式不是很有用;它 主要是为了向后兼容而存在的。

  • EVBUFFER_EOL_NUL

    行尾是值为 0 的单个字节,即 一个 ASCII NUL。

(请注意,如果您使用 event_set_mem_functions() 来覆盖 默认 malloc,evbuffer_readln 返回的字符串将是 由您指定的 malloc-replacement 分配。

char *request_line;
size_t len;

request_line = evbuffer_readln(buf, &len, EVBUFFER_EOL_CRLF);
if (!request_line) {
    /* The first line has not arrived yet. */
} else {
    if (!strncmp(request_line, "HTTP/1.0 ", 9)) {
        /* HTTP 1.0 detected ... */
    }
    free(request_line);
}

evbuffer_readln() 接口在 Libevent 1.4.14-stable 和 后。EVBUFFER_EOL_NUL是在 Libevent 2.1.1-alpha 中添加的。

在 evbuffer 中搜索

evbuffer_ptr结构指向 evbuffer 中的一个位置, 并包含可用于遍历 EVBUFFER 的数据。

接口

struct evbuffer_ptr {
        ev_ssize_t pos;
        struct {
                /* internal fields */
        } _internal;
};

pos
字段是唯一的公共字段;其他的不应该 由用户代码使用。它将 evbuffer 中的位置指示为 从一开始就偏移。

接口

struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer,
    const char *what, size_t len, const struct evbuffer_ptr *start);
struct evbuffer_ptr evbuffer_search_range(struct evbuffer *buffer,
    const char *what, size_t len, const struct evbuffer_ptr *start,
    const struct evbuffer_ptr *end);
struct evbuffer_ptr evbuffer_search_eol(struct evbuffer *buffer,
    struct evbuffer_ptr *start, size_t *eol_len_out,
    enum evbuffer_eol_style eol_style);

evbuffer_search() 函数扫描缓冲区以查找
出现 len-character
字符串
what
。它返回evbuffer_ptr一个包含 字符串的位置,如果未找到字符串,则为 -1。如果提供了
start
参数,则它是搜索的位置 应该开始;否则,搜索将从字符串的开头开始。

evbuffer_search_range() 函数的行为与evbuffer_search相同,不同之处在于它只考虑
what
在start和end字段之间的数据。

evbuffer_search_eol() 类似于evbuffer_readlen,探测行结束符,只是该函数并不复制该行。该函数返回evbuffer_ptr结构,其中的pos指明了行结束符的起始地址。如果eol_len_out不是NULL,则其被置为EOL字符串的长度。

接口

enum evbuffer_ptr_how {
        EVBUFFER_PTR_SET,
        EVBUFFER_PTR_ADD
};
int evbuffer_ptr_set(struct evbuffer *buffer, struct evbuffer_ptr *pos,
    size_t position, enum evbuffer_ptr_how how);

evbuffer_ptr_set 函数操作
evbuffer_ptr缓冲区

的 pos
。如果
how
是EVBUFFER_PTR_SET, 指针将移动到
缓冲区内
的绝对位置。 如果
how
是EVBUFFER_PTR_ADD,指针将向前移动
position
字节。此函数在成功时返回 0,在失败时返回 -1。

#include <event2/buffer.h>
#include <string.h>

/* Count the total occurrences of 'str' in 'buf'. */
int count_instances(struct evbuffer *buf, const char *str)
{
    size_t len = strlen(str);
    int total = 0;
    struct evbuffer_ptr p;

    if (!len)
        /* Don't try to count the occurrences of a 0-length string. */
        return -1;

    evbuffer_ptr_set(buf, &p, 0, EVBUFFER_PTR_SET);

    while (1) {
         p = evbuffer_search(buf, str, len, &p);
         if (p.pos < 0)
             break;
         total++;
         evbuffer_ptr_set(buf, &p, 1, EVBUFFER_PTR_ADD);
    }

    return total;
}

警告

任何修改 evbuffer 或其布局的调用都会使所有未执行的 evbuffer _ ptr 值失效,并使其不安全。

这些接口是 Libevent 2.0.1-alpha 中的新功能。

在不复制数据的情况下检查数据

有时,您希望在 evbuffer 中读取数据而不将其复制出来(如 evbuffer_copyout() ),并且没有重新排列 evbuffer 的内部 内存(如 evbuffer_pullup() )。有时您可能希望在 evbuffer 的中间。

您可以通过以下方式执行此操作:

接口

struct evbuffer_iovec {
        void *iov_base;
        size_t iov_len;
};

//返回值未需要的数据块(evbuffer_iovec)的数量
int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len,
    struct evbuffer_ptr *start_at,
    struct evbuffer_iovec *vec_out, int n_vec);


当你调用 evbuffer_peek() 时,你给它一个 evbuffer_iovec 数组
vec_out
结构。数组的长度为
n_vec
。它设置了这些 结构,以便每个结构都包含指向 evbuffer 块的指针 内部 RAM (
iov_base
),以及其中设置的内存长度 块。

如果
len
小于 0,则 evbuffer_peek() 尝试填充所有 您给它evbuffer_iovec结构。否则,它会填充它们,直到 要么它们都被使用,要么至少
len
字节是可见的。如果 函数可以给你所有你要求的数据,它返回 evbuffer_iovec它实际使用的结构。否则,它将返回 它需要的数字才能满足您的要求。


ptr
为 NULL 时,evbuffer_peek() 从缓冲区的开头开始。 否则,它从
ptr
中给出的指针开始。

例子

{
    /* Let's look at the first two chunks of buf, and write them to stderr. */
    int n, i;
    struct evbuffer_iovec v[2];
    n = evbuffer_peek(buf, -1, NULL, v, 2);
    for (i=0; i<n; ++i) { /* There might be less than two chunks available. */
        fwrite(v[i].iov_base, 1, v[i].iov_len, stderr);
    }
}

{
    /* Let's send the first 4906 bytes to stdout via write. */
    int n, i, r;
    struct evbuffer_iovec *v;
    size_t written = 0;

    /* determine how many chunks we need. */
    n = evbuffer_peek(buf, 4096, NULL, NULL, 0);
    /* Allocate space for the chunks.  This would be a good time to use
       alloca() if you have it. */
    v = malloc(sizeof(struct evbuffer_iovec)*n);
    /* Actually fill up v. */
    n = evbuffer_peek(buf, 4096, NULL, v, n);
    for (i=0; i<n; ++i) {
        size_t len = v[i].iov_len;
        if (written + len > 4096)
            len = 4096 - written;
        r = write(1 /* stdout */, v[i].iov_base, len);
        if (r<=0)
            break;
        /* We keep track of the bytes written separately; if we don't,
           we may write more than 4096 bytes if the last chunk puts
           us over the limit. */
        written += len;
    }
    free(v);
}

{
    /* Let's get the first 16K of data after the first occurrence of the
       string "start\n", and pass it to a consume() function. */
    struct evbuffer_ptr ptr;
    struct evbuffer_iovec v[1];
    const char s[] = "start\n";
    int n_written;

    ptr = evbuffer_search(buf, s, strlen(s), NULL);
    if (ptr.pos == -1)
        return; /* no start string found. */

    /* Advance the pointer past the start string. */
    if (evbuffer_ptr_set(buf, &ptr, strlen(s), EVBUFFER_PTR_ADD) < 0)
        return; /* off the end of the string. */

    while (n_written < 16*1024) {
        /* Peek at a single chunk. */
        if (evbuffer_peek(buf, -1, &ptr, v, 1) < 1)
            break;
        /* Pass the data to some user-defined consume function */
        consume(v[0].iov_base, v[0].iov_len);
        n_written += v[0].iov_len;

        /* Advance the pointer so we see the next chunk next time. */
        if (evbuffer_ptr_set(buf, &ptr, v[0].iov_len, EVBUFFER_PTR_ADD)<0)
            break;
    }
}

笔记

  • 修改evbuffer_iovec指向的数据可能会导致 未定义的行为。
  • 如果调用任何修改 evbuffer 的函数,则指针 evbuffer_peek() 产量可能无效。
  • 如果您的 evbuffer 可以在多个线程中使用,请确保锁定 在调用 evbuffer_peek() 之前,先用 evbuffer_lock() 解锁它 使用 evbuffer_peek() 为您提供的范围后。

此函数是 Libevent 2.0.2-alpha 中的新功能。

直接将数据添加到 evbuffer

有时您想直接在 evbuffer 中插入数据信息,而不使用 首先将其写入字符数组,然后将其复制到 evbuffer_add()。您可以使用一对高级函数来执行 这:evbuffer_reserve_space() 和 evbuffer_commit_space()。 与 evbuffer_peek() 一样,这些函数使用 evbuffer_iovec 结构,以提供对 EVbuffer 内部内存的直接访问。

接口

int evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
    struct evbuffer_iovec *vec, int n_vecs);
int evbuffer_commit_space(struct evbuffer *buf,
    struct evbuffer_iovec *vec, int n_vecs);

evbuffer_reserve_space() 函数为您提供指向内部空间的指针 evbuffer。它会根据需要扩展缓冲区,以提供最小
大小
的字节。指向这些范围及其长度的指针将是 存储在您使用
VEC
传入的向量数组中;
n_vec
是 此数组的长度。

n_vec
的值必须至少为 1。如果您只提供一个 vector,则 Libevent 将确保您拥有所有连续空间 您在单个范围中请求,但可能需要重新排列 缓冲或浪费内存以执行此操作。为了获得更好的性能, 提供至少 2 个向量。该函数返回提供的数量 它需要的矢量,用于您请求的空间。

写入这些向量的数据不是缓冲区的一部分 直到你调用 evbuffer_commit_space(),它实际上使数据 你把计数写成在缓冲区中。如果要提交更少的空间 比您要求的要少,您可以iov_len减少任何 evbuffer_iovec给你的结构。您也可以减少传回 向量比你得到的要多。evbuffer_commit_space() 函数 成功时返回 0,失败时返回 -1。

注意事项

  • 调用任何重新排列 evbuffer 或向其添加数据的函数 evbuffer 将使您从中获得的指针失效 evbuffer_reserve_space()。
  • 在当前的实现中,evbuffer_reserve_space() 从不使用 超过两个向量,无论用户提供多少。这可能 在将来的版本中进行更改。
  • 可以安全地调用 evbuffer_reserve_space() 任意次数。
  • 如果您的 evbuffer 可以在多个线程中使用,请确保锁定 在调用 evbuffer_reserve_space() 之前,它与 evbuffer_lock() 一起使用 提交后解锁。

/* Suppose we want to fill a buffer with 2048 bytes of output from a
   generate_data() function, without copying. */
struct evbuffer_iovec v[2];
int n, i;
size_t n_to_add = 2048;

/* Reserve 2048 bytes.*/
n = evbuffer_reserve_space(buf, n_to_add, v, 2);
if (n<=0)
   return; /* Unable to reserve the space for some reason. */

for (i=0; i<n && n_to_add > 0; ++i) {
   size_t len = v[i].iov_len;
   if (len > n_to_add) /* Don't write more than n_to_add bytes. */
      len = n_to_add;
   if (generate_data(v[i].iov_base, len) < 0) {
      /* If there was a problem during data generation, we can just stop
         here; no data will be committed to the buffer. */
      return;
   }
   /* Set iov_len to the number of bytes we actually wrote, so we
      don't commit too much. */
   v[i].iov_len = len;
   n_to_add -= len;
}

/* We commit the space here.  Note that we give it 'i' (the number of
   vectors we actually used) rather than 'n' (the number of vectors we
   had available. */
if (evbuffer_commit_space(buf, v, i) < 0)
   return; /* Error committing */

坏例子

/* Here are some mistakes you can make with evbuffer_reserve().
   DO NOT IMITATE THIS CODE. */
struct evbuffer_iovec v[2];

{
  /* Do not use the pointers from evbuffer_reserve_space() after
     calling any functions that modify the buffer. */
  evbuffer_reserve_space(buf, 1024, v, 2);
  evbuffer_add(buf, "X", 1);
  /* WRONG: This next line won't work if evbuffer_add needed to rearrange
     the buffer's contents.  It might even crash your program. Instead,
     you add the data before calling evbuffer_reserve_space. */
  memset(v[0].iov_base, 'Y', v[0].iov_len-1);
  evbuffer_commit_space(buf, v, 1);
}

{
  /* Do not modify the iov_base pointers. */
  const char *data = "Here is some data";
  evbuffer_reserve_space(buf, strlen(data), v, 1);
  /* WRONG: The next line will not do what you want.  Instead, you
     should _copy_ the contents of data into v[0].iov_base. */
  v[0].iov_base = (char*) data;
  v[0].iov_len = strlen(data);
  /* In this case, evbuffer_commit_space might give an error if you're
     lucky */
  evbuffer_commit_space(buf, v, 1);
}

自 Libevent 以来,这些函数一直存在于其现有接口中 2.0.2-阿尔法。

带 evbuffers 的网络 IO

Libevent 中 evbuffers 最常见的用例是网络 IO。 用于在 evbuffer 上执行网络 IO 的接口为:

接口

int evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd);
int evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
        ev_ssize_t howmuch);
int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch);

evbuffer_read() 函数从 套接字
fd

buffer
末尾。它返回读取的字节数 成功,EOF 为 0,错误为 -1。请注意,该错误可能 指示非阻塞操作不会成功;你需要 检查 EAGAIN(或 Windows 上的 WSAEWOULDBLOCK)的错误代码。 如果
多少
是负数,evbuffer_read() 会尝试猜测多少 读取自身。

evbuffer_write_atmost() 函数尝试将
buffer
前面
howmuch字节
写入套接字
fd
。它返回一个 成功时写入的字节数,失败时写入的字节数为 -1。与 evbuffer_read(),您需要检查错误代码,看看 error 是真实的,或者只是表示非阻塞 IO 不能 立即完成。如果你给
howmuch
一个负值, 我们尝试写入缓冲区的全部内容。

调用 evbuffer_write() 与调用带有负数
的 howmuch
参数evbuffer_write_atmost()函数功能相同:它将尝试尽可能多地刷新缓冲区。

在 Unix 上,这些函数应该适用于任何文件描述符 支持读写。在 Windows 上,仅支持套接字。

请注意,当您使用 bufferevents 时,您不需要调用 这些 IO 功能;BufferEvents 代码会为您完成此操作。

evbuffer_write_atmost() 函数是在 Libevent 2.0.1-alpha 中引入的。

Evbuffers 和回调

evbuffers 的用户经常想知道何时将数据添加到或 从 evbuffer 中删除。为了支持这一点,Libevent 提供了一个 通用 EVPuamp 回调机制。

接口

struct evbuffer_cb_info {
        size_t orig_size;
        size_t n_added;
        size_t n_deleted;
};

typedef void (*evbuffer_cb_func)(struct evbuffer *buffer,
    const struct evbuffer_cb_info *info, void *arg);

每当添加或删除数据时,都会调用 evbuffer 回调 从 evbuffer 。它接收缓冲区,指向 evbuffer_cb_info结构和用户提供的参数。这 evbuffer_cb_info结构的 orig_size 字段记录多少字节 在其大小改变之前,缓冲区上有;它n_added领域 记录向缓冲区添加了多少字节及其n_deleted 字段记录删除了多少字节。

接口

struct evbuffer_cb_entry;
struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer,
    evbuffer_cb_func cb, void *cbarg);

evbuffer_add_cb() 函数向 evbuffer 添加回调,并且 返回一个不透明的指针,稍后可用于引用此指针 特定的回调实例。
cb
参数是 将被调用,
而 cbarg
是用户提供的要传递的指针 到函数。

您可以在单个 evbuffer 上设置多个回调。添加 新回调不会删除旧回调。

#include <event2/buffer.h>
#include <stdio.h>
#include <stdlib.h>

/* Here's a callback that remembers how many bytes we have drained in
   total from the buffer, and prints a dot every time we hit a
   megabyte. */
struct total_processed {
    size_t n;
};
void count_megabytes_cb(struct evbuffer *buffer,
    const struct evbuffer_cb_info *info, void *arg)
{
    struct total_processed *tp = arg;
    size_t old_n = tp->n;
    int megabytes, i;
    tp->n += info->n_deleted;
    megabytes = ((tp->n) >> 20) - (old_n >> 20);
    for (i=0; i<megabytes; ++i)
        putc('.', stdout);
}

void operation_with_counted_bytes(void)
{
    struct total_processed *tp = malloc(sizeof(*tp));
    struct evbuffer *buf = evbuffer_new();
    tp->n = 0;
    evbuffer_add_cb(buf, count_megabytes_cb, tp);

    /* Use the evbuffer for a while.  When we're done: */
    evbuffer_free(buf);
    free(tp);
}

顺便说一句,释放非空 evbuffer 不算作 从中耗尽数据,并且释放 EVBUFFER 不会释放 用户为其回调提供的数据指针。

如果您不希望回调在缓冲区上永久处于活动状态,则 可以
删除
它(使其永远消失)或禁用它(将其转动) 关闭一会儿):

接口

int evbuffer_remove_cb_entry(struct evbuffer *buffer,
    struct evbuffer_cb_entry *ent);
int evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb_func cb,
    void *cbarg);

#define EVBUFFER_CB_ENABLED 1
int evbuffer_cb_set_flags(struct evbuffer *buffer,
                          struct evbuffer_cb_entry *cb,
                          ev_uint32_t flags);
int evbuffer_cb_clear_flags(struct evbuffer *buffer,
                          struct evbuffer_cb_entry *cb,
                          ev_uint32_t flags);

您可以按以下时间收到的evbuffer_cb_entry删除回调 您添加了它,或者通过您使用的回调和指针添加了它。这 evbuffer_remove_cb() 函数在成功时返回 0,在失败时返回 -1。

evbuffer_cb_set_flags() 函数和 evbuffer_cb_clear_flags() 函数在给定回调上设置或清除给定的标志 分别。目前,仅支持一个用户可见的标志:
EVBUFFER_CB_ENABLED
。默认情况下设置该标志。当它是 清除后,对 evbuffer 的修改不会导致此回调 被调用。

接口

int evbuffer_defer_callbacks(struct evbuffer *buffer, struct event_base *base);

与 bufferevent 回调一样,您可以使 evbuffer 回调不 当 EVBUFFER 发生更改时,立即运行,而是作为给定事件库的事件循环的一部分延迟运行。 如果您有多个 evbuffers 的回调,这可能会有所帮助 可能导致数据相互添加和删除,以及 你要避免砸碎堆栈。

如果 evbuffer 的回调被延迟,那么当它们最终被推迟时 调用时,它们可以汇总多个操作的结果。

与 bufferevents 一样,evbuffers 在内部进行引用计数,因此 释放 evbuffer 是安全的,即使它有延迟的回调 尚未执行。

整个回调系统在 Libevent 2.0.1-alpha 中是新的。这 evbuffer_cb_(set|clear)_flags() 函数已经存在,它们的 自 2.0.2-alpha 以来的现有接口。

使用基于 evbuffer 的 IO 避免数据复制

真正快速的网络编程通常需要尽可能少的数据 尽可能复制。Libevent 提供了一些机制来提供帮助 有了这个。

接口

typedef void (*evbuffer_ref_cleanup_cb)(const void *data,
    size_t datalen, void *extra);

int evbuffer_add_reference(struct evbuffer *outbuf,
    const void *data, size_t datlen,
    evbuffer_ref_cleanup_cb cleanupfn, void *extra);

此函数通过以下方式将一段数据添加到 evbuffer 的末尾 参考。不执行任何复制:相反,evbuffer 只存储一个 指向
存储在 Data
中的
datlen
字节的指针。因此, 只要 EVBUFFER 正在使用指针,指针就必须保持有效。 当 evbuffer 不再需要数据时,它会调用提供的 “cleanupfn”函数,带有提供的“data”指针,“datlen”值, 和“额外”指针作为参数。 此函数在成功时返回 0,在失败时返回 -1。

#include <event2/buffer.h>
#include <stdlib.h>
#include <string.h>

/* In this example, we have a bunch of evbuffers that we want to use to
   spool a one-megabyte resource out to the network.  We do this
   without keeping any more copies of the resource in memory than
   necessary. */

#define HUGE_RESOURCE_SIZE (1024*1024)
struct huge_resource {
    /* We keep a count of the references that exist to this structure,
       so that we know when we can free it. */
    int reference_count;
    char data[HUGE_RESOURCE_SIZE];
};

struct huge_resource *new_resource(void) {
    struct huge_resource *hr = malloc(sizeof(struct huge_resource));
    hr->reference_count = 1;
    /* Here we should fill hr->data with something.  In real life,
       we'd probably load something or do a complex calculation.
       Here, we'll just fill it with EEs. */
    memset(hr->data, 0xEE, sizeof(hr->data));
    return hr;
}

void free_resource(struct huge_resource *hr) {
    --hr->reference_count;
    if (hr->reference_count == 0)
        free(hr);
}

static void cleanup(const void *data, size_t len, void *arg) {
    free_resource(arg);
}

/* This is the function that actually adds the resource to the
   buffer. */
void spool_resource_to_evbuffer(struct evbuffer *buf,
    struct huge_resource *hr)
{
    ++hr->reference_count;
    evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE,
        cleanup, hr);
}

evbuffer_add_reference() 函数已存在 接口从 2.0.2-alpha 开始。

将文件添加到 evbuffer

某些操作系统提供了将文件写入网络的方法 而无需将数据复制到用户空间。您可以访问这些 机制(如果可用),具有简单的界面:

接口

int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
    size_t length);

evbuffer_add_file() 函数假定它有一个打开的文件 描述符(不是套接字,这一次!可用于的
FD
读数。它将文件的
长度
字节(从位置
偏移
量开始)添加到
输出
末尾。成功时返回 0,或返回 -1 失败。

警告

在 Libevent 2.0.x 中,唯一可靠的数据 添加这种方式是用 evbuffer_write
() 将其发送到网络, 用 evbuffer_drain() 排空它,或用 evbuffer_
_buffer()。您无法可靠地从缓冲区中提取它 用 evbuffer_remove(),用 evbuffer_pullup() 线性化,依此类推 上。Libevent 2.1.x 尝试修复此限制。

如果您的操作系统支持 splice() 或 sendfile(),则 Libevent 调用时会使用它直接将数据从
FD
发送到网络 evbuffer_write(),则完全没有将数据复制到用户RAM。如果 splice/sendfile 不存在,但你有 mmap(),Libevent 会 mmap 文件和你的内核有望发现它永远不需要 将数据复制到用户空间。否则,Libevent 将只读取 数据从磁盘到RAM。

从 evbuffer,或者当 evbuffer 被释放时。如果这不是你想要的,或者如果 您想要对文件进行更精细的控制,请参阅file_segment 功能如下。

此函数是在 Libevent 2.0.1-alpha 中引入的。

对文件段进行细粒度控制

evbuffer_add_file() 接口对于添加相同的文件效率低下 不止一次,因为它拥有文件的所有权。

接口

struct evbuffer_file_segment;

struct evbuffer_file_segment *evbuffer_file_segment_new(
        int fd, ev_off_t offset, ev_off_t length, unsigned flags);
void evbuffer_file_segment_free(struct evbuffer_file_segment *seg);
int evbuffer_add_file_segment(struct evbuffer *buf,
    struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length);

evbuffer_file_segment_new() 函数创建并返回一个新的 evbuffer_file_segment 对象来表示基础文件的一部分 存储在从
偏移
量开始并包含
长度
字节的
FD
中。上 错误,则返回 NULL。

文件段使用 sendfile、splice、mmap、CreateFileMapping、 或 malloc()-and-read(),视情况而定。它们是使用最多的 轻量级支撑机构,并过渡到重量较重的机构 根据需要。(例如,如果您的操作系统支持 sendfile 和 mmap,则文件 segment 只能使用 sendfile 实现,直到您尝试实际 检查其内容。在这一点上,它需要 mmap()ed.)您可以 使用以下标志控制文件段的细粒度行为:

  • EVBUF_FS_CLOSE_ON_FREE

    如果设置了此标志,则释放文件段 evbuffer_file_segment_free() 将关闭基础文件。

  • EVBUF_FS_DISABLE_MMAP

    如果设置了此标志,则file_segment将永远不会使用映射内存 style 后端 (CreateFileMapping, mmap) 来表示,即使这样 要适当。

  • EVBUF_FS_DISABLE_SENDFILE

    如果设置了此标志,则file_segment将永远不会使用 sendfile 样式 此文件的后端(sendfile、splice),即使会 要适当。

  • EVBUF_FS_DISABLE_LOCKING

    如果设置了此标志,则不会为文件段分配任何锁:它 以任何可以被多人看到的方式使用它都是不安全的 线程。

一旦你有了evbuffer_file_segment,你可以将部分或全部添加到一个 evbuffer 使用 evbuffer_add_file_segment()。此处的
offset
参数是指文件段内的偏移量,而不是偏移量 在文件本身中。

当您不想再使用文件段时,可以使用 evbuffer_file_segment_free()。实际存储不会释放,直到没有 evbuffer 不再包含对文件段片段的引用。

接口

typedef void (*evbuffer_file_segment_cleanup_cb)(
    struct evbuffer_file_segment const *seg, int flags, void *arg);

void evbuffer_file_segment_add_cleanup_cb(struct evbuffer_file_segment *seg,
        evbuffer_file_segment_cleanup_cb cb, void *arg);

您可以将回调函数添加到将在以下情况下调用的文件段 对文件段的最终引用已发布,并且文件 段即将被释放。此回调
不得
尝试恢复 文件段,将其添加到任何缓冲区,依此类推。

这些文件段函数最早出现在 Libevent 2.1.1-alpha 中; evbuffer_file_segment_add_cleanup_cb() 是在 2.1.2-alpha 中添加的。

通过引用将一个 evbuffer 添加到另一个 evbuffer

您还可以通过引用将一个 evbuffer 添加到另一个 evbuffer:而不是删除 一个缓冲区的内容并将它们添加到另一个缓冲区,您得到一个 evbuffer 对另一个的引用,它的行为就好像你已经复制了所有的 字节。

接口

int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
    struct evbuffer *inbuf);

evbuffer_add_buffer_reference() 函数的行为就像您复制了一样 从
outbuf

inbuf
的所有数据,但不执行任何不必要的操作 副本。如果成功,则返回 0,失败时返回 -1。

请注意,对
inbuf
内容的后续更改不会反映
在 outbuf
中:此函数通过以下方式添加 evbuffer 的当前内容 引用,而不是 EVbuffer 本身。

另请注意,您不能嵌套缓冲区引用:已 作为一个evbuffer_add_buffer_reference的
输出
,不能成为另一个呼叫的
输出

此函数是在 Libevent 2.1.1-alpha 中引入的。

使 evbuffer 仅能添加或删除

接口

int evbuffer_freeze(struct evbuffer *buf, int at_front);
int evbuffer_unfreeze(struct evbuffer *buf, int at_front);

您可以使用这些函数暂时禁用对 evbuffer 头部和尾部的修改。bufferevent 代码使用它们 内部以防止意外修改前部 输出缓冲区或输入缓冲区的末尾。

evbuffer_freeze() 函数是在 Libevent 中引入的 2.0.1-阿尔法。

过时的 evbuffer 函数

evbuffer 接口在 Libevent 2.0 中发生了很大变化。在此之前, 每个 evbuffers 都作为连续的 RAM 块实现,它 使访问效率非常低下。

event.h 标头用于公开 struct evbuffer 的内部结构。 这些不再可用;他们在 1.4 和 2.0 适用于依赖它们工作的任何代码。

要访问 evbuffer 中的字节数,有一个 EVBUFFER_LENGTH() 宏。实际数据可通过 EVBUFFER_DATA()。这些都可以在 event2/buffer_compat.h 中使用。 但要注意:EVBUFFER_DATA(b) 是 evbuffer_pullup(b, -1),这可能非常昂贵。

其他一些已弃用的接口包括:

已弃用的接口

char *evbuffer_readline(struct evbuffer *buffer);
unsigned char *evbuffer_find(struct evbuffer *buffer,
    const unsigned char *what, size_t len);

evbuffer_readline() 函数的工作方式与当前函数类似 evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY)。

evbuffer_find() 函数将搜索第一次出现的 缓冲区中的字符串,并返回指向它的指针。与 evbuffer_search(),它只能找到第一个字符串。留下来 与使用此函数的旧代码兼容,它现在线性化 整个缓冲区,直到找到的字符串的末尾。

回调接口也不同:

已弃用的接口

typedef void (*evbuffer_cb)(struct evbuffer *buffer,
    size_t old_len, size_t new_len, void *arg);
void evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg);

一个 evbuffer 一次只能设置一个回调,因此设置一个 new callback 将禁用之前的回调,并将 NULL 的回调是禁用回调的首选方法。

该函数不是获得evbuffer_cb_info_structure,而是 使用 evbuffer 的旧长度和新长度调用。因此,如果old_len 大于 new_len,数据被耗尽。如果new_len更大 比old_len,添加了数据。无法推迟回调, 因此,添加和删除从未被批处理到单个回调中 调用。

此处的过时函数在 event2/buffer_compat.h 中仍然可用。

源文档地址:
https://libevent.org/libevent-book/Ref7_evbuffer.html

一、目的

Android源码太庞大,涉及几十个G、上千万个文件,普通的IDE都无法完全加载全部代码,更不用谈搜索、索引,这对于Android源码的学习、调试非常不利。
OpenGrok是一个源码搜索及交叉引用查询引擎,OpenGrok的引入可以帮助我们更好地在浩如烟海的源码里找到自己需要的那坨代码。

二、环境

  1. 系统:Ubuntu 14.04
  2. CPU:13th Gen Intel(R) Core(TM) i5-13500

三、相关概念

3.1 OpenGrok

OpenGrok为一个方便快速的源码搜索及交叉引用查询引擎。它以Java编写,可用于源码搜索、交叉引用查询、以及源码树定位。它支持多种编码语言和多种代码版本控制引擎系统。

3.2 CTags

CTags是一个在Linux系统中广泛使用的工具,它可以帮助程序员更有效地浏览和管理大型代码库。CTags能够创建一个索引文件,其中包含代码库中的函数、类、结构体等关键词的位置信息。

3.3 Tomcat

Tomcat是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,由Apache软件基金会(Apache Software Foundation)的Jakarta项目开发。 它是一个Servlet容器,同时也是JSP(Java Server Pages)的一个容器,支持最新的Servlet和JSP规范。Tomcat的设计目标是提供一个可靠且易于使用的Web应用服务器,特别适用于中小型系统和并发访问用户不是很多的场合。由于其技术先进、性能稳定且免费,Tomcat深受Java爱好者的喜爱,并得到了部分软件开发商的认可,成为比较流行的Web应用服务器之一。

四、OpenGrok搭建

4.1 安装jdk

sudo apt install openjdk-11-jdk

4.2 安装ctags依赖

sudo apt install autoconf automake

4.3 安装universal-ctags

4.3.1 下载universal-ctags

git clone https://github.com/universal-ctags/ctags.git

4.3.2 编译&&安装universal-ctags

cd ctags
./autogen.sh 
./configure
make
sudo make install

4.4 安装Tomcat

4.4.1 下载&&解压Tomcat

# http://tomcat.apache.org/
tar -xvf apache-tomcat-10.1.24.tar.gz 

4.4.2 启动Tomcat

sh apache-tomcat-10.1.24/bin/startup.sh

4.4.3 验证Tomcat

访问:
http://127.0.0.1:8080/

4.5 安装OpenGrok

# https://github.com/oracle/opengrok
# https://github.com/oracle/opengrok/releases
tar -xvf opengrok-1.13.7.tar.gz 

4.6 基于Tomcat搭建OpnGrok服务

  1. 拷贝OpenGrok的源项目到Tomcat下。
    Tomcat启动后会自动解压source.war,可以通过浏览器访问:
    http://127.0.0.1:8080/source/
cp opengrok-1.13.7/lib/source.war apache-tomcat-10.1.24/webapps/ 
  1. 配置相关环境变量。
sudo vi ~/.bashrc
# opengrok
export JAVA_HOME=/usr
export OPENGROK_TOMCAT_BASE=/media/2TB/lzq/openGrok/apache-tomcat-10.1.24
export CATALINA_HOME=/media/2TB/lzq/openGrok/apache-tomcat-10.1.24
export OPENGROK_APP_SERVER=Tomcat
export OPENGROK_INSTANCE_BASE=/media/2TB/lzq/openGrok/opengrok-1.13.7
export LANG=zh_CN.UTF-8
  1. 使能环境变量
source ~/.bashrc
  1. 创建相关目录
 mkdir opengrok-1.13.7/etc opengrok-1.13.7/src opengrok-1.13.7/data  opengrok-1.13.7/history
  1. 将待索引代码拷贝到src目录下。
    建议创建一个软链接,不然多一份代码占用空间。
cp -r xxx opengrok-1.13.7/src/ 
  1. 生成索引
    (之后每次在src目录下有更新需要重新索引)。在opengrok-1.13.7/data/下会有相关索引信息,也是很占空间,此过程费时,这台拖拉机索引一次用了6天(粗略估算了一下,一分钟仅能扫描100个文件)。
java -jar opengrok-1.13.7/lib/opengrok.jar -P -S -v -s opengrok-1.13.7/src -d opengrok-1.13.7/data -H --repository  opengrok-1.13.7/history -W opengrok-1.13.7/etc/configuration.xml
  1. 配置configuration.xml路径。
    索引生成后,会生成opengrok-1.13.7/etc/configuration.xml文件,需要让tomcat下的项目引用该配置。
# apache-tomcat-10.1.24/webapps/source/WEB-INF/web.xml
<context-param>
    <description>Full path to the configuration file where OpenGrok can read its configuration</description>
    <param-name>CONFIGURATION</param-name>
    <param-value>/media/2TB/lzq/openGrok/opengrok-1.13.7/etc/configuration.xml</param-value>
</context-param>
  1. 验证功能。
    启动tomcat,同时浏览器访问http://127.0.0.1:8080/source/, 大功告成。

五、相关问题

5.1 TomCat访问source异常404?

一开始我下载apache-tomcat-9.0.89,然后http://127.0.0.1:8080/source/, 一直出现404。经历一系列各种乱七八糟的调试后,始终起不来。
后面更新apache-tomcat-10.1.24,完美。原因不明。

5.2 OpenGrok没有projects选择项?

我最开始在opengrok-1.13.7/src/下创建了UIS7870文件夹,然后生成索引的目标目录是指向:opengrok-1.13.7/src/,导致projects仅有UIS7870,故不显示(具体可参考configuration.xml & apache-tomcat-10.1.24/webapps/source/menu.jspf);
后续将目标索引目录修改成opengrok-1.13.7/src/UI7870/即可。

5.3 如何修改Tomcat端口?

  • 将server端口修改为8006
  • 将HTTP端口修改为8085
  • 将AJP端口修改为8010
# sudo cat apache-tomcat-10.1.24/conf/server.xml
<Server port="8006" shutdown="SHUTDOWN">
 
<Connector port="8085" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
 
<Connector protocol="AJP/1.3"
               address="::1"
               port="8010"
               redirectPort="8443" />

六、参考资料

  1. OpenGrok搭建
    https://blog.csdn.net/weixin_39577288/article/details/120565619
  2. OpenGrok介绍
    https://www.jianshu.com/p/3371d3b213d2