Redis中单机数据库的实现 (5)

这里我们先只看建立连接部分, 下面是acceptTcpHandler函数, 即listening tcp socket fd的可读事件回调:

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); // 处理请求 } }

这个函数中只做了两件事:

建立tcp数据连接

调用acceptCommonHandler

下面是acceptCommonHandler

#define MAX_ACCEPTS_PER_CALL 1000 static void acceptCommonHandler(int fd, int flags, char *ip) { client *c; // 创建一个client实例 if ((c = createClient(fd)) == NULL) { // ... return; } // 如果设置了连接上限, 并且当前数据连接数已达上限, 则关闭这个数据连接, 退出, 什么也不做 if (listLength(server.clients) > server.maxclients) { //... return; } // 如果服务端运行在保护模式下, 并且没有登录密码机制, 那么不接受外部tcp请求 if (server.protected_mode && server.bindaddr_count == 0 && server.requirepass == NULL && !(flags & CLIENT_UNIX_SOCKET) && ip != NULL) { if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) { // ... return; } } server.stat_numconnections++; c->flags |= flags; }

这里可以看到, 对每一个数据连接, Redis中将其抽象成了一个 struct client 结构, 所有创建struct client实例的细节被隐藏在createClient函数中, 下面是createClient的实现:

client *createClient(int fd) { client * c = zmalloc(sizeof(client)) if (fd != -1) { anetNonBlock(NULL, fd); // 设置非阻塞 anetEnableTcpNoDelay(NULL, fd); // 设置no delay if (server.tcpkeepalive) anetKeepAlive(NULL, fd, server.tcpkeepalive); // 按需设置keep alive if (aeCreateFileEvent(server.el, fd, AE_READABLE, readQueryFromClient, c) == AE_ERR) { // 注册事件回调 close(fd); zfree(c); return NULL; } } selectDb(c, 0) // 默认选择第一个数据库 uint64_t client_id; atomicGetIncr(server.next_client_id, client_id, 1); // 原子自增 server.next_client_id // 其它字段初始化 c->id = client_id; //... // 把这个client实例添加到 server.clients尾巴上去 if (fd != -1) listAddNodeTail(server.clients,c); return c; } 6 Redis通信协议 6.1 通信方式

客户端与服务器之间的通信方式有两种

一问一答式的(request-response pattern). 由客户端发起请求, 服务端处理逻辑, 然后回发回应

若客户端订阅了服务端的某个channel, 这时通信方式为单向的传输: 服务端会主动向客户端推送消息. 客户端无需发起请求

在单机数据库范畴中, 我们先不讨论Redis的发布订阅模式, 即在本文的讨论范围内, 客户端与服务器的通信始终是一问一答式的. 这种情况下, 还分为两种情况:

客户端每次请求中, 仅包含一个命令. 服务端在回应中, 也仅给出这一个命令的执行结果

客户端每次请求中, 包含多个有序的命令. 服务端在回应中, 按序给出这多个命令的执行结果

后者在官方文档中被称为 pipelining

6.2 通信协议

自上向下描述协议:

协议描述的是数据

数据有五种类型. 分别是短字符串, 长字符串, 错误信息, 数值, 数组. 其中数组是复合类型. 协议中, 数据与数据之间以\r\n两个字节作为定界符

短字符串: +Hello World\r\n 以+开头, 末尾的\r\n是定界符, 所以显然, 短字符串本身不支持表示\r\n, 会和定界符冲突

错误信息: -Error message\r\n 以-开头, 末尾的\r\n是定界符. 显然, 错误信息里也不支持包含\r\n

数值: :9527\r\n 以:开头, ASCII字符表示的数值, 仅支持表示整数

长字符串: $8\r\nfuck you\r\n 先以$开头, 后面跟着字符串的总长度, 以ASCII字符表示. 再跟一个定界符, 接下来的8个字节就是长字符串的内容, 随后再是一个定界符.

数组: 这是一个复合类型, 以*开头, 后面跟着数组的容量, 以ASCII字符表示, 再跟一个定界符. 然后后面跟着多个其它数据. 比如 *2\r\n$3\r\nfoo\r\n+Hello World\r\n表示的是一个容量为2的数组, 第一个数组元素是一个长字符串, 第二个数组元素是一个短字符串.

注意:

一般情况下, 协议体是一个数组

长字符串在表示长度为0的字符串时, 是$0\r\n\r\n

还有一个特殊值, $-1\r\n, 它用来表示语义中的null

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsydf.html