heartbeat和keepalive

本文详细解析了libhv库中设置心跳和保活机制的接口及其作用。心跳机制用于检测连接状态,防止因各种原因导致的连接异常,而保活机制则在长时间无通信后关闭连接。心跳和保活的实现主要通过定时器,当检测到读写操作时会重置定时器。在服务端和客户端的accept和connect回调中,可以设置这两个机制。在实际应用中,如HttpServer,会在连接建立后启用keepalive机制,确保连接的有效性。
摘要由CSDN通过智能技术生成

libhv提供了设置心跳和保活机制的接口

HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_fn fn);
HV_EXPORT void hio_set_keepalive_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_KEEPALIVE_TIMEOUT));

在分析源码之前,先说下心跳机制的重要意义,首先,通过检测心跳可以处理以下这些情况(摘抄自陈硕大神的《Linux 多线程服务端编程》):

  • 如果操作系统崩溃导致机器重启,没有机会发送FIN分节
  • 服务器硬件故障导致机器重启,也没有机会发送FIN分节
  • 并发连接数很高时,操作系统或进程如果重启,可能没有机会断开全部连接。换句话说,FIN分节可能出现丢包,但这时没有机会重试
  • 网络故障,连接双方得知这个情况的唯一方案是检测心跳超时

第二,心跳除了说明应用程序还活着,更重要的是表明应用程序还能正常工作。

当然了,心跳也可以发送与业务相关的信息,不仅仅用来判断连接状态。

--------------------------------------------下面开始源码分析--------------------------------------------------------

上面两个对外提供的接口很简单,只是设置io的相关成员变量

void hio_set_keepalive_timeout(hio_t* io, int timeout_ms) {
    io->keepalive_timeout = timeout_ms;
}

void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_fn fn) {
    io->heartbeat_interval = interval_ms;
    io->heartbeat_fn = fn;
}

实际生效是在其他地方设置的,第一个是在__accept_cb接口,即服务端accept成功后,调用注册回调函数的位置:

static void __accept_cb(hio_t* io) {

    if (io->accept_cb) {
        io->accept_cb(io);
    }

    if (io->keepalive_timeout > 0) {
        io->keepalive_timer = htimer_add(io->loop, __keepalive_timeout_cb, io->keepalive_timeout, 1);
        io->keepalive_timer->privdata = io;
    }

    if (io->heartbeat_interval > 0) {
        io->heartbeat_timer = htimer_add(io->loop, __heartbeat_timer_cb, io->heartbeat_interval, INFINITE);
        io->heartbeat_timer->privdata = io;
    }
}

上面的accept_cb就是用户注册的回调函数,在执行完accept_cb后,确认是否设置了keepalive和heartbeat。所以对于服务端而言,如果要设置keepalive或者heartbeat,在注册的accept回调函数中调用上面的两个接口就可以了。

第二个是在__connect_cb,即客户端connect成功后,调用注册回调函数的位置:

static void __connect_cb(hio_t* io) {

    if (io->connect_timer) {
        htimer_del(io->connect_timer);
        io->connect_timer = NULL;
        io->connect_timeout = 0;
    }

    if (io->connect_cb) {
        io->connect_cb(io);
    }

    if (io->keepalive_timeout > 0) {
        io->keepalive_timer = htimer_add(io->loop, __keepalive_timeout_cb, io->keepalive_timeout, 1);
        io->keepalive_timer->privdata = io;
    }

    if (io->heartbeat_interval > 0) {
        io->heartbeat_timer = htimer_add(io->loop, __heartbeat_timer_cb, io->heartbeat_interval, INFINITE);
        io->heartbeat_timer->privdata = io;
    }
}

上面的connect_cb就是用户注册的回调函数,所以客户端想要设置keepalive或者heartbeat,可以在注册的connect回调函数中调用上面的两个接口。

心跳的实现比较简单,设置一个无限次的定时器,每隔heartbeat_interval时间调用__heartbeat_timer_cb:

static void __heartbeat_timer_cb(htimer_t* timer) {
    hio_t* io = (hio_t*)timer->privdata;
    if (io && io->heartbeat_fn) {
        io->heartbeat_fn(io);
    }
}

__heartbeat_timer_cb执行用户注册的回调函数

keepalive相对复杂一些,当keepalive设置的定时器到期时,会调用__keepalive_timeout_cb,而该接口则会关闭连接:

static void __keepalive_timeout_cb(htimer_t* timer) {
    hio_t* io = (hio_t*)timer->privdata;
    if (io) {
        char localaddrstr[SOCKADDR_STRLEN] = {0};
        char peeraddrstr[SOCKADDR_STRLEN] = {0};
        hlogw("keepalive timeout [%s] <=> [%s]",
                SOCKADDR_STR(io->localaddr, localaddrstr),
                SOCKADDR_STR(io->peeraddr, peeraddrstr));
        io->error = ETIMEDOUT;
        hio_close(io);
    }
}

为了不触发keepalive,就必须在定时器到期前重置定时器,重置定时器的位置有两个,第一个是在读数据时调用__read_cb

static void __read_cb(hio_t* io, void* buf, int readbytes) {
    // printd("> %.*s\n", readbytes, buf);
    if (io->keepalive_timer) {
        htimer_reset(io->keepalive_timer);
    }

    if (io->read_cb) {
        // printd("read_cb------\n");
        io->read_cb(io, buf, readbytes);
        // printd("read_cb======\n");
    }
}

__read_cb是在读到数据后,调用用户注册的读回调函数read_cb。在调用读回调前,如果keepalive存在,重置定时器。

第二个是在写数据时,调用__write_cb

static void __write_cb(hio_t* io, const void* buf, int writebytes) {
    // printd("< %.*s\n", writebytes, buf);
    if (io->keepalive_timer) {
        htimer_reset(io->keepalive_timer);
    }

    if (io->write_cb) {
        // printd("write_cb------\n");
        io->write_cb(io, buf, writebytes);
        // printd("write_cb======\n");
    }
}

__write_cb是在写数据后,调用用户注册的写回调write_cb。在调用写回调前,如果keepalive存在,重置定时器。

根据上面的描述,可以看出,这里的keepalive就是在服务端和客户端之间既没有读也没有写一段时间后,断开连接,在libhv的HttpServer.cpp文件中,有对于keepalive机制的使用:

static void on_accept(hio_t* io) {
    printd("on_accept connfd=%d\n", hio_fd(io));

    hio_setcb_close(io, on_close);
    hio_setcb_read(io, on_recv);
    hio_read(io);
    hio_set_keepalive_timeout(io, HIO_DEFAULT_KEEPALIVE_TIMEOUT);
    // new HttpHandler
    // delete on_close
    HttpHandler* handler = new HttpHandler;
    handler->service = (HttpService*)hevent_userdata(io);
    handler->files = &s_filecache;
    sockaddr_ip((sockaddr_u*)hio_peeraddr(io), handler->ip, sizeof(handler->ip));
    handler->port = sockaddr_port((sockaddr_u*)hio_peeraddr(io));
    hevent_set_userdata(io, handler);
}

在accept的回调中,设置了keepalive,当客户端与服务端HIO_DEFAULT_KEEPALIVE_TIMEOUT时间没有通信后,服务端会断开客户端的连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值