Valkey源码剖析(5):文件事件的表示

Valkey中的文件事件是网络套接字操作的抽象,主要用于监听并处理套接字的可读和可写状态:当被监听套接字的读操作或者写操作可以执行时,服务器就认为该套接字对应的文件事件“已就绪”,之后服务器就会调用与套接字相关联的事件处理回调函数来处理相应的读写事件。

简单来说,当Valkey服务器准备好接收某个客户端的命令请求时,该客户端对应套接字的文件事件中的读事件就会就绪;而当某个客户端准备好接收Valkey服务器发送的命令回复时,该客户端对应套接字的文件事件中的写事件就会就绪。

Valkey服务器使用ae.h/aeFileEvent结构表示套接字正在被监听的文件事件,以下是该结构的完整定义:

typedef struct aeFileEvent {
    // 掩码(用于标识想要监听的事件)
    int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
    // 事件处理函数(一个用于处理读事件,一个用于处理写事件)
    aeFileProc *rfileProc;
    aeFileProc *wfileProc;
    // 客户端数据
    void *clientData;
} aeFileEvent;

结构中的mask属性记录了服务器想要监听的具体事件(写还是读?)以及事件就绪时负责处理这两种事件的回调函数。

与此对应,当有被监听套接字的文件事件就绪时,服务器将使用ae.h/aeFiredEvent结构进行表示,以下是该结构的完整定义:

typedef struct aeFiredEvent {
    // 已就绪的文件描述符
    int fd;
    // 已就绪的事件
    int mask;
} aeFiredEvent;

Valkey服务器在启动的过程中,会在调用ae.c/aeCreateEventLoop()函数的时候,传入服务器允许连接的最大客户端数量:

void initServer(void) {
    // ...

    // 将最大可连接客户端数量传入函数
    server.el = aeCreateEventLoop(server.maxclients + CONFIG_FDSET_INCR);

    // ...
}

aeCreateEventLoop()函数则会根据这一数量创建相应数量的aeFileEvent结构和aeFiredEvent结构:

aeEventLoop *aeCreateEventLoop(int setsize) {
    //...

    eventLoop->events = zmalloc(sizeof(aeFileEvent) * setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent) * setsize);

    // ...
}

注意,虽然aeEventLoop结构的events数组和fired数组拥有相同的大小,但它们的作用和用法并不相同:

  • events数组的每个元素跟客户端套接字的文件描述符都是一对一的关系,比如events[100]元素记录的就是服务器正在监听的文件描述符为100的套接字的文件事件信息。

  • 与此不同,fired数组的元素跟客户端套接字的文件描述符并非一对一关系,比如fired[100]元素记录的就是服务器在休眠期间文件事件就绪了的第100个套接字,而这个套接字可以是所有已连接客户端的任何一个,这也是aeFiredEvent结构需要记录已就绪套接字文件描述符的原因。

具体的细节在之后描述事件处理过程的文章中会进一步叙述。

好的,关于文件事件的介绍就到此结束,接下来的文章将介绍时间事件。

黄健宏
2025.12.27