redis-server网络通信模块---钩子函数

回调函数与钩子函数

1、什么是回调函数?

     简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

2、什么是钩子(hook)函数?

     钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。

3、如何区分?

函数钩取事件:函数主动找事件=>钩子函数

事件派发给函数:事件调用函数=>回调函数

打个形象的比喻:书店、你、你小表弟、书店美女店员

书店暂时没有你要的书,咋办呢?

1)你无耻的派了你小表弟在书店24小时蹲守,有人送书来,你表弟就去看下,一旦来了,你表弟就告诉你,并且帮你买下来

2)你留下你的号码给美女店员,让她有书就通知你

第一种:你就是js,你表弟就是你派出去的监听器,监听函数就是让你表弟买下了这本书的指令,这个过程没有美女店员的事

第二种:你依然是js,美女店员是事件,你的号码就是函数,把号码留给店员的过程就是把函数注入到事件当中的过程,美女打电话通知你的过程就是回调,所以你注入的函数就是回调函数,这个过程没有你表弟的事

dom通过事件通知js的过程即是回调,对应的函数就是回调函数

js通过监听函数得知事件的过程即是钩取,对应的函数就是钩子函数

钩子函数和回调函数都是事件处理函数,根本上,他们都是为了捕获消息而生的,但是钩子函数在捕获消息的第一时间就会执行,而回调函数是在整个捕获过程结束时,最后一个被执行的。


一、beforesleep “钩子函数” -- 统一的应答处理

while 事件循环:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

beforesleep 对象是一个回调函数,在 redis-server 初始化时已经设置好了。

while 事件循环每一轮循环都会调用这个 beforeSleep 函数。在这个 beforeSleep 函数中有一个 handleClientsWithPendingWrites 调用:

void beforeSleep(struct aeEventLoop *eventLoop) {
    //省略无关代码...

    /* Handle writes with pending output buffers. */
    handleClientsWithPendingWrites();

    //省略无关代码...
}

handleClientsWithPendingWrites()集中处理所有客户端的回复。具体地:

首先从全局 server 对象的 clients_pending_write 字段( 存储 client 对象的链表 )挨个取出有数据要发送的 client 对象,然后调用 writeToClient 函数尝试将 client 中存储的应答数据(每个已连接的client都有维护一个输出缓冲区)发出去。

(1)如果数据能够全部发完,发完以后则会移除对应的 fd 上的可写事件( 如果添加了 );如果当前 client 设置了 CLIENT_CLOSE_AFTER_REPLY 标志,则发送完数据立即释放这个 client 对象。

(2)可能由于网络或者客户端的原因,redis-server 某个客户端的数据发送不出去,或者只有部分可以发出去。不管哪种情况,数据这一次发不完。这个时候就需要监听可写事件了。注册可写事件 AE_WRITABLE 的回调函数是 sendReplyToClient 。也就是说,当下一次某个触发可写事件时,调用的就是 sendReplyToClient 函数。

 总结:

如果有数据要发送给某个 client ,不需要专门注册可写事件等触发可写事件再发送。通常的做法是在应答数据产生的地方直接发送,如果是因为对端 Tcp 窗口太小引起的发送不完,则将剩余的数据存储至某个缓冲区并注册监听可写事件,等下次触发可写事件后再尝试发送,一直到数据全部发送完毕后移除可写事件。

redis-server 数据的发送逻辑与这个稍微有点差别,就是将数据发送的时机放到了 EventLoop 的某个时间点上( 这里是在 ProcessEvents 之前 ),其他的与上面完全一样。

之所以不注册监听可写事件,等可写事件触发再发送数据,原因是通常情况下,网络通信的两端数据一般都是正常收发的,不会出现某一端由于 Tcp 窗口太小而使另外一端发不出去的情况。如果注册监听可写事件,那么这个事件会频繁触发,而触发时不一定有数据需要发送,这样不仅浪费系统资源,同时也浪费服务器程序宝贵的 CPU 时间片。


二、aftersleep 钩子函数 - 处理其他

通常情形下,在一个 EventLoop 中除了有定时器、IO Multiplexing 和 IO 事件处理逻辑外,可以根据需求自定义一些函数,这类函数我们称之为“钩子函数”。钩子函数可以位于 Loop 的任何位置,前面我们介绍的 beforesleep 函数就是在事件处理之前自定义的钩子函数( 位于定时器时间检测逻辑之前 )。

在 redis-server 中,在 IO Multiplexing 调用与 IO 事件处理逻辑之间也有一个自定义的钩子函数叫 aftersleep :

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    //无关代码省略...
    /* IO Multiplexing */
    numevents = aeApiPoll(eventLoop, tvp);

    /* After sleep callback. */
    if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
        eventLoop->aftersleep(eventLoop);
    
    /* process IO event. */
    for (j = 0; j < numevents; j++) {
        //无关代码省略...
    }    
}

三、redis-server网络通信模块

参考:

https://gitbook.cn/gitchat/column/5c0e149eedba1b683458fd5f/topic/5c0e2c27edba1b683459204d

JavaScript:理解事件、事件处理函数、钩子函数、回调函数

钩子(HOOK)函数教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值