libhv学习笔记2:从hloop_test入手

从本文开始,由sample入手,逐渐理解libhv的源码。
如有理解错误,欢迎批评指正。

main()函数

int main() {
    // memcheck atexit
    HV_MEMCHECK;

    hloop_t* loop = hloop_new(0);

    // test idle and priority
    for (int i = HEVENT_LOWEST_PRIORITY; i <= HEVENT_HIGHEST_PRIORITY; ++i) {
        hidle_t* idle = hidle_add(loop, on_idle, 10);
        hevent_set_priority(idle, i);
    }

    // // test timer timeout
    for (int i = 1; i <= 10; ++i) {
        htimer_t* timer = htimer_add(loop, on_timer, i*1000, 3);
        hevent_set_userdata(timer, (void*)(intptr_t)i);
    }

    // // test timer period
    int minute = time(NULL)%3600/60;
    htimer_add_period(loop, cron_hourly, minute+1, -1, -1, -1, -1, INFINITE);

    // test network_logger
    htimer_add(loop, timer_write_log, 1000, INFINITE);
    logger_set_handler(hlog, mylogger);
    hlog_set_file("loop.log");
#ifndef _MSC_VER
    logger_enable_color(hlog, 1);
#endif
    nlog_listen(loop, DEFAULT_LOG_PORT);

    // test nonblock stdin
    printf("input 'quit' to quit loop\n");
    char buf[64];
    hread(loop, 0, buf, sizeof(buf), on_stdin);

   // test custom_events
    for (int i = 0; i < 10; ++i) {
        hevent_t ev;
        memset(&ev, 0, sizeof(ev));
        ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
        ev.cb = on_custom_events;
        ev.userdata = (void*)(long)i;
        hloop_post_event(loop, &ev);
    }

    hloop_run(loop);
    hloop_free(&loop);
    return 0;
}

代码的结构和注释非常的清晰,其中包含了多个事件的类型:
1.空闲事件
2.定时器超时事件
3.周期性的超时事件
4.监听listen事件
5.键盘自定义事件
6.用户自定义事件

先看一下代码的开头。

hloop_t* loop = hloop_new(0);

这里创建并初始化了libhv的事件循环。看一下这里面具体做了什么。

hloop_t* hloop_new(int flags) {
    hloop_t* loop;
    HV_ALLOC_SIZEOF(loop);
    hloop_init(loop);
    loop->flags |= flags;
    return loop;
}

首先看一下这个flags的取值范围

#define HLOOP_FLAG_RUN_ONCE                     0x00000001 //事件循环体只执行一次
#define HLOOP_FLAG_AUTO_FREE                    0x00000002 //执行完自动释放资源
#define HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS   0x00000004 //当没有事件时,退出事件循环

具体使用,我们后面再看。
接下来看一下hloop_init具体做了什么

static void hloop_init(hloop_t* loop) {
#ifdef OS_WIN
    static int s_wsa_initialized = 0;
    if (s_wsa_initialized == 0) {
        s_wsa_initialized = 1;
        WSADATA wsadata;
        WSAStartup(MAKEWORD(2,2), &wsadata);
    }
#endif
#ifdef SIGPIPE
    // NOTE: if not ignore SIGPIPE, write twice when peer close will lead to exit process by SIGPIPE.
    signal(SIGPIPE, SIG_IGN);
#endif

    loop->status = HLOOP_STATUS_STOP;				//初始化状态为 停止状态
    loop->pid = hv_getpid();						//获取当前进程号
    loop->tid = hv_gettid();						//获取当前线程号

    // idles
    list_init(&loop->idles);						//初始化空闲事件的链表

    // timers
    heap_init(&loop->timers, timers_compare);		//初始化超时事件的堆

    // ios
    io_array_init(&loop->ios, IO_ARRAY_INIT_SIZE);	//初始化IO事件的数组

    // readbuf
    loop->readbuf.len = HLOOP_READ_BUFSIZE;			//分配读缓冲区内存
    HV_ALLOC(loop->readbuf.base, loop->readbuf.len);

    // iowatcher
    iowatcher_init(loop);							//初始化IO监控

    // custom_events
    hmutex_init(&loop->custom_events_mutex);
    event_queue_init(&loop->custom_events, CUSTOM_EVENT_QUEUE_INIT_SIZE);
    loop->sockpair[0] = loop->sockpair[1] = -1;    //创建一对连接的socket
    if (Socketpair(AF_INET, SOCK_STREAM, 0, loop->sockpair) != 0) {
        hloge("socketpair create failed!");
    }

    // NOTE: init start_time here, because htimer_add use it.
    loop->start_ms = gettimeofday_ms();				//获取当前事件ms
    loop->start_hrtime = loop->cur_hrtime = gethrtime_us(); //获取当前事件us
}

从代码上看,hloop_init针对loop做了一系列初始化工作,所以不得不去看一下hloop_t的定义。

typedef struct hloop_s      hloop_t;
struct hloop_s {
    uint32_t    flags;									//标记
    hloop_status_e status;								//状态
    uint64_t    start_ms;       // ms
    uint64_t    start_hrtime;   // us
    uint64_t    end_hrtime;								//结束的时间
    uint64_t    cur_hrtime;								//当前时间
    uint64_t    loop_cnt;								//循环的次数
    long        pid;									//进程号
    long        tid;									//线程号
    void*       userdata;								//用户数据
//private:
    // events
    uint32_t                    nactives;				//活动的事件的数量
    uint32_t                    npendings;				//将要被处理的事件的数量
    // pendings: with priority as array.index
    hevent_t*                   pendings[HEVENT_PRIORITY_SIZE];		//将要被处理的事件的数组
    // idles													    //数组的长度为HEVENT_PRIORITY_SIZE(10)个
    																//这里按事件的优先级,放入到不同链表中,执行时,由高到低执行
    struct list_head            idles;								//空闲事件链表
    uint32_t                    nidles;								//空闲事件的数量
    // timers
    struct heap                 timers;								//超时事件堆
    uint32_t                    ntimers;							//超时事件的数量
    // ios: with fd as array.index
    struct io_array             ios;								//IO事件数组
    uint32_t                    nios;								//IO事件数量
    // one loop per thread, so one readbuf per loop is OK.
    hbuf_t                      readbuf;							//读缓冲区,这里作者有解释:因为一个线程只有一个事件循环,所有只要分配一个读缓冲区就可以了
    void*                       iowatcher;							//io监视器
    // custom_events
    int                         sockpair[2];						//本地成对连接的socket
    event_queue                 custom_events;						//用户自定义事件队列
    hmutex_t                    custom_events_mutex;				//用户事件锁
};

从上面代码里可以看出来,建立本地连接的socket是为了执行用户自定义事件,这里比较有意思,我们后面具体再看如何使用的。

下面继续看sample,hloop_test.c代码

// test idle and priority
    for (int i = HEVENT_LOWEST_PRIORITY; i <= HEVENT_HIGHEST_PRIORITY; ++i) {
        hidle_t* idle = hidle_add(loop, on_idle, 10);
        hevent_set_priority(idle, i);
    }

    // // test timer timeout
    for (int i = 1; i <= 10; ++i) {
        htimer_t* timer = htimer_add(loop, on_timer, i*1000, 3);
        hevent_set_userdata(timer, (void*)(intptr_t)i);
    }

    // // test timer period
    int minute = time(NULL)%3600/60;
    htimer_add_period(loop, cron_hourly, minute+1, -1, -1, -1, -1, INFINITE);
  // test nonblock stdin
    printf("input 'quit' to quit loop\n");
    char buf[64];
    hread(loop, 0, buf, sizeof(buf), on_stdin);

   // test custom_events
    for (int i = 0; i < 10; ++i) {
        hevent_t ev;
        memset(&ev, 0, sizeof(ev));
        ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
        ev.cb = on_custom_events;
        ev.userdata = (void*)(long)i;
        hloop_post_event(loop, &ev);
    }

上面是注册各种事件,先看最简单的空闲事件

hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat) {
    hidle_t* idle;
    HV_ALLOC_SIZEOF(idle);
    idle->event_type = HEVENT_TYPE_IDLE;
    idle->priority = HEVENT_LOWEST_PRIORITY;
    idle->repeat = repeat;
    list_add(&idle->node, &loop->idles);						//将空间事件加入到空闲事件链表中
    EVENT_ADD(loop, idle, cb);
    loop->nidles++;
    return idle;
}

我们可以看到,上面代码将空闲事件加入到了loop的空闲列表中。(其他内容先不深究)

超时事件

htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, uint32_t timeout, uint32_t repeat) {
    if (timeout == 0)   return NULL;
    htimeout_t* timer;
    HV_ALLOC_SIZEOF(timer);
    timer->event_type = HEVENT_TYPE_TIMEOUT;
    timer->priority = HEVENT_HIGHEST_PRIORITY;
    timer->repeat = repeat;
    timer->timeout = timeout;
    hloop_update_time(loop);
    timer->next_timeout = hloop_now_hrtime(loop) + timeout*1000;
    heap_insert(&loop->timers, &timer->node);			//超时事件加入堆
    EVENT_ADD(loop, timer, cb);
    loop->ntimers++;
    return (htimer_t*)timer;
}

同样,将超时事件加入loop的堆中

htimer_t* htimer_add_period(hloop_t* loop, htimer_cb cb,
                int8_t minute,  int8_t hour, int8_t day,
                int8_t week, int8_t month, uint32_t repeat) {
    if (minute > 59 || hour > 23 || day > 31 || week > 6 || month > 12) {
        return NULL;
    }
    hperiod_t* timer;
    HV_ALLOC_SIZEOF(timer);
    timer->event_type = HEVENT_TYPE_PERIOD;
    timer->priority = HEVENT_HIGH_PRIORITY;
    timer->repeat = repeat;
    timer->minute = minute;
    timer->hour   = hour;
    timer->day    = day;
    timer->month  = month;
    timer->week   = week;
    timer->next_timeout = (uint64_t)cron_next_timeout(minute, hour, day, week, month) * 1000000;//将周期时间转为下一次的超时时间
    heap_insert(&loop->timers, &timer->node);
    EVENT_ADD(loop, timer, cb);
    loop->ntimers++;
    return (htimer_t*)timer;
}

listen监听事件

hio_t* nlog_listen(hloop_t* loop, int port) {
    s_logger.loop = loop;
   //这里实例化了一个servet的socket,并把其listen事件加入到IO事件数组中,当有客户端连接时,调用on_accept回调函数
    s_logger.listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
   
    list_init(&s_logger.clients);
    hmutex_init(&s_mutex);
    return s_logger.listenio;
}

键盘事件

hio_t* hread(hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb) {
    hio_t* io = hio_get(loop, fd);
    assert(io != NULL);
    io->readbuf.base = (char*)buf;
    io->readbuf.len = len;
    if (read_cb) {
        io->read_cb = read_cb;
    }
    hio_read(io);
    return io;
}

其实就是一个io事件,后面再具体分析
下面是用户自定义事件

    for (int i = 0; i < 10; ++i) {
        hevent_t ev;
        memset(&ev, 0, sizeof(ev));
        //定义用户类型,从HEVENT_TYPE_CUSTOM 开始往上定义
        ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
        ev.cb = on_custom_events;
        ev.userdata = (void*)(long)i;
        //发送用户事件
        hloop_post_event(loop, &ev);
    }

添加了这么多的事件,libhv是怎样执行的呢?
下面就是真正开始libhv事件循环的核心

int hloop_run(hloop_t* loop) {
    loop->pid = hv_getpid();
    loop->tid = hv_gettid();

    // intern events
    //这里,如果定义了本地相互连接的socket,那么就为读socket注册一个读事件,缓冲区为loop初始化时定义的缓冲区
    int intern_events = 0;
    if (loop->sockpair[0] != -1 && loop->sockpair[1] != -1) {
        hread(loop, loop->sockpair[SOCKPAIR_READ_INDEX], loop->readbuf.base, loop->readbuf.len, sockpair_read_cb);
        ++intern_events;
    }
#ifdef DEBUG
    htimer_add(loop, hloop_stat_timer_cb, HLOOP_STAT_TIMEOUT, INFINITE);
    ++intern_events;
#endif

    loop->status = HLOOP_STATUS_RUNNING;
    while (loop->status != HLOOP_STATUS_STOP) {				//检查loop的状态
        if (loop->status == HLOOP_STATUS_PAUSE) {
            msleep(HLOOP_PAUSE_TIME);						//如果是暂停状态,就休眠一段时间
            hloop_update_time(loop);
            continue;
        }
        ++loop->loop_cnt;
        //如果没有激活的事件,且loop的标志为HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS	就退出事件循环							
        if (loop->nactives <= intern_events && loop->flags & HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS) {
            break;
        }
        //核心:处理事件
        hloop_process_events(loop);
        //如果只执行一次的话,就退出事件循环
        if (loop->flags & HLOOP_FLAG_RUN_ONCE) {
            break;
        }
    }
    loop->status = HLOOP_STATUS_STOP;
    loop->end_hrtime = gethrtime_us();
	//退出事件循环时,清理释放loop的资源
    if (loop->flags & HLOOP_FLAG_AUTO_FREE) {
        hloop_cleanup(loop);
        HV_FREE(loop);
    }
    return 0;
}

在这里终于看到了loop的flags的作用

 if (loop->nactives <= intern_events && loop->flags & HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS) {
            break;
        }
if (loop->flags & HLOOP_FLAG_RUN_ONCE) {
            break;
        }
if (loop->flags & HLOOP_FLAG_AUTO_FREE) {
        hloop_cleanup(loop);
        HV_FREE(loop);
    }

最后,我们看一下libhv是如何处理事件的

// hloop_process_ios -> hloop_process_timers -> hloop_process_idles -> hloop_process_pendings
//作者注释,libhv处理事件的顺序是IO事件->超时事件->空闲事件 => 要执行的事件 
static int hloop_process_events(hloop_t* loop) {
    // ios -> timers -> idles
    int nios, ntimers, nidles;
    nios = ntimers = nidles = 0;

    // calc blocktime
    int32_t blocktime = HLOOP_MAX_BLOCK_TIME;
    if (loop->timers.root) {
        hloop_update_time(loop);
        //从超时事件堆中,找到最近要执行的超时事件
        uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
        //计算与当前时间的时间差
        int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
        //如果已经超时了,立马执行
        if (blocktime_us <= 0) goto process_timers;
        //计算可以阻塞的时间(ms)
        blocktime = blocktime_us / 1000;
        ++blocktime;
        blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
    }

    //检查是否有IO事件
    if (loop->nios) {
        nios = hloop_process_ios(loop, blocktime);
    }
    else {
        msleep(blocktime);
    }
    hloop_update_time(loop);
    // wakeup by hloop_stop
    if (loop->status == HLOOP_STATUS_STOP) {
        return 0;
    }

process_timers:
	//检查是否有超时事件
    if (loop->ntimers) {
        ntimers = hloop_process_timers(loop);
    }
    
	//查看有没有需要处理的事件,如果没有则查看有没有空间事件
    int npendings = loop->npendings;
    if (npendings == 0) {
        if (loop->nidles) {
            nidles= hloop_process_idles(loop);
        }
    }
    //执行需要处理的事件(需要处理的事件其实就是当前需要处理的IO事件,超时事件,空间事件)
    int ncbs = hloop_process_pendings(loop);
    // printd("blocktime=%d nios=%d/%u ntimers=%d/%u nidles=%d/%u nactives=%d npendings=%d ncbs=%d\n",
    //         blocktime, nios, loop->nios, ntimers, loop->ntimers, nidles, loop->nidles,
    //         loop->nactives, npendings, ncbs);
    return ncbs;
}

通过上面的源码理解分析,大致明白了libhv的事件循环的过程,总结一下流程就是:
在这里插入图片描述

下一次详细分析hloop_process_events函数中的内容

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
libhv是一个基于C++开发的轻量级网络库,用于处理HTTP请求。可以使用libhv来获取图片数据,并将其转换为base64编码。 以下是使用libhv库获取图片数据并转换为base64的示例代码: ```cpp #include <iostream> #include <fstream> #include <cstring> #include "hloop.h" #include "hsocket.h" #include "hbase64.h" void on_http_request(hloop_t* loop, hsocket_t* client, http_message_t* req) { // 从请求中获取图片URL const char* url = req->uri.full; // 发送HTTP请求获取图片数据 http_client_t* http_client = http_client_new(loop); http_request_t* http_req = http_request_new(url, HTTP_GET, NULL); http_req->on_response = [http_req](http_response_t* res) { if (res->status_code == 200) { // 将获取到的图片数据转换为base64编码 std::string base64_data = base64_encode(res->body.c_str(), res->body.size()); // 保存base64编码结果到文件或进行其他处理 std::ofstream file("image_base64.txt"); file << base64_data; file.close(); // 打印base64编码结果 std::cout << "Base64 data: " << base64_data << std::endl; } else { std::cerr << "Failed to download image: " << res->status_code << std::endl; } http_request_delete(http_req); }; http_client_send(http_client, http_req); } int main() { hloop_t loop; hloop_init(&loop); // 创建HTTP服务器 http_server_t* http_server = http_server_new(&loop); http_server->on_request = on_http_request; // 启动HTTP服务器 const char* host = "0.0.0.0"; int port = 8000; if (http_server_start(http_server, host, port) == 0) { std::cout << "Server running at http://" << host << ":" << port << std::endl; } else { std::cerr << "Failed to start server" << std::endl; return -1; } // 运行事件循环 hloop_run(&loop); // 清理资源 http_server_free(http_server); hloop_free(&loop); return 0; } ``` 上述代码创建了一个基于libhv的HTTP服务器,当收到HTTP请求时,会获取请求中的图片URL,并使用libhv内置的HTTP客户端发送请求来获取图片数据。然后,将获取到的图片数据转换为base64编码,并保存到文件中。 请确保你已经安装了libhv库,并在编译时链接相应的库文件。编译时需要添加"-lhv"参数,例如: ``` g++ main.cpp -o main -lhv ``` 这样就可以编译并运行上述代码来实现使用libhv库获取图片数据并转换为base64了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值