相关文件
redis 源代码以 5.0 为准
文件 | 说明 |
---|---|
ae_epoll.c | epoll api 封装 |
ae_evport.c | evport api 封装 |
ae_kqueue.c | kqueue api 封装 |
ae_select.c | select api 封装 |
ae.c | 异步事件系统,支持网络事件、定时事件 |
server.c | TCP 服务启动;异步事件启动运行等 |
networking.c | 网络会话相关处理,涉及如何接收消息、如何发送消息等 |
config.h | 通过编译器内置宏,确定使用哪个网络库 api |
支持多种网络库
-
config.h
/* Test for polling API */ #ifdef __linux__ #define HAVE_EPOLL 1 #endif #if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) #define HAVE_KQUEUE 1 #endif #ifdef __sun #include <sys/feature_tests.h> #ifdef _DTRACE_VERSION #define HAVE_EVPORT 1 #endif #endif
-
适配接口
static int aeApiCreate(aeEventLoop *eventLoop); static int aeApiResize(aeEventLoop *eventLoop, int setsize); static void aeApiFree(aeEventLoop *eventLoop); static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask); static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask); static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp); static char *aeApiName(void);
ae_epoll.c 、 ae_evport.c 、 ae_kqueue.c 、 ae_select.c 均实现以上接口
-
编译
/* Include the best multiplexing layer supported by this system. * The following should be ordered by performances, descending. */ #ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif #endif
ae.c 直接根据宏定义, include 对应平台代码,同时 Mackfile 中不包含去编译 ae_epoll.c 、 ae_evport.c 、 ae_kqueue.c 、 ae_select.c
处理流程
a*.c 代码代码很独立简单,不做介绍。
这里记录下相关流程
-
启动
int main(int argc, char **argv) { // 无关代码略 initServer(); // 无关代码略 aeSetBeforeSleepProc(server.el,beforeSleep); aeSetAfterSleepProc(server.el,afterSleep); aeMain(server.el); aeDeleteEventLoop(server.el); return 0; }
initServer();
内部创建 server.el 异步事件系统;监听 TCP 端口等aeMain(server.el);
启动异步事件系统
-
initServer 初始化过程
void initServer(void) { // 无关代码略 server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR); if (server.el == NULL) { serverLog(LL_WARNING, "Failed creating the event loop. Error message: '%s'", strerror(errno)); exit(1); } // 无关代码略 /* Open the TCP listening socket for the user commands. */ if (server.port != 0 && listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) exit(1); // 无关代码略 /* Create an event handler for accepting new connections in TCP and Unix * domain sockets. */ for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) { serverPanic( "Unrecoverable error creating server.ipfd file event."); } } // 无关代码略 } void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd, max = MAX_ACCEPTS_PER_CALL; char cip[NET_IP_STR_LEN]; UNUSED(el); UNUSED(mask); UNUSED(privdata); while(max--) { cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); if (cfd == ANET_ERR) { if (errno != EWOULDBLOCK) serverLog(LL_WARNING, "Accepting client connection: %s", server.neterr); return; } serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport); acceptCommonHandler(cfd,0,cip); } } static void acceptCommonHandler(int fd, int flags, char *ip) { client *c; if ((c = createClient(fd)) == NULL) { serverLog(LL_WARNING, "Error registering fd event for the new client: %s (fd=%d)", strerror(errno),fd); close(fd); /* May be already closed, just ignore errors */ return; } // 无关代码略 }
- 客户端连接进来,会触发
acceptTcpHandler
createClient
创建网络会话对象,内部开始接受读事件
- 客户端连接进来,会触发
-
读数据
client *createClient(int fd) { client *c = zmalloc(sizeof(client)); /* passing -1 as fd it is possible to create a non connected client. * This is useful since all the commands needs to be executed * in the context of a client. When commands are executed in other * contexts (for instance a Lua script) we need a non connected client. */ if (fd != -1) { anetNonBlock(NULL,fd); anetEnableTcpNoDelay(NULL,fd); if (server.tcpkeepalive) anetKeepAlive(NULL,fd,server.tcpkeepalive); if (aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c) == AE_ERR) { close(fd); zfree(c); return NULL; } } // 无关代码略 }
- 读事件会触发,
readQueryFromClient
- 内部会处理具体逻辑等,回复先写入写缓存
- 读事件会触发,
-
写数据
/* Add the object 'obj' string representation to the client output buffer. */ void addReply(client *c, robj *obj) { if (prepareClientToWrite(c) != C_OK) return; if (sdsEncodedObject(obj)) { if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK) _addReplyStringToList(c,obj->ptr,sdslen(obj->ptr)); } else if (obj->encoding == OBJ_ENCODING_INT) { /* For integer encoded strings we just convert it into a string * using our optimized function, and attach the resulting string * to the output buffer. */ char buf[32]; size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr); if (_addReplyToBuffer(c,buf,len) != C_OK) _addReplyStringToList(c,buf,len); } else { serverPanic("Wrong obj->encoding in addReply()"); } } int _addReplyToBuffer(client *c, const char *s, size_t len) { size_t available = sizeof(c->buf)-c->bufpos; if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return C_OK; /* If there already are entries in the reply list, we cannot * add anything more to the static buffer. */ if (listLength(c->reply) > 0) return C_ERR; /* Check that the buffer has enough space available for this string. */ if (len > available) return C_ERR; memcpy(c->buf+c->bufpos,s,len); c->bufpos+=len; return C_OK; } /* Write data in output buffers to client. Return C_OK if the client * is still valid after the call, C_ERR if it was freed. */ int writeToClient(int fd, client *c, int handler_installed) { // 无关代码略 } /* This function gets called every time Redis is entering the * main loop of the event driven library, that is, before to sleep * for ready file descriptors. */ void beforeSleep(struct aeEventLoop *eventLoop) { // 无关代码略 }
addReply
会把数据写入缓存c->buf
- 还记得
main
函数中的aeSetBeforeSleepProc(server.el,beforeSleep);
吗?异步事件系统每次wait返回时,先去写数据到网络,调用writeToClient
其他
看代码时,对 epoll 不熟悉的话,一定会困惑与 epoll 的 LT 模式与 ET 模式
以下时,阅读时百度的一些文章,有助理解 epoll 相关知识:
- https://blog.csdn.net/dongfuye/article/details/84770173
- https://blog.csdn.net/eyucham/article/details/86502117
以上