redis 源代码阅读与学习笔记(三)

相关文件

redis 源代码以 5.0 为准

文件说明
ae_epoll.cepoll api 封装
ae_evport.cevport api 封装
ae_kqueue.ckqueue api 封装
ae_select.cselect api 封装
ae.c异步事件系统,支持网络事件、定时事件
server.cTCP 服务启动;异步事件启动运行等
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 相关知识:

以上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fananchong2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值