Redis 监听端口
还记得上一篇我们讲的 initServer() 中网络监听的调用吗?
1 /* Open the TCP listening socket for the user commands. */
2 if (server.port != 0 &&
3 listenToPort(server.port,&server.ipfd) == C_ERR) {
4 serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", server.port);
5 exit(1);
6 }
7 if (server.tls_port != 0 &&
8 listenToPort(server.tls_port,&server.tlsfd) == C_ERR) {
9 serverLog(LL_WARNING, "Failed listening on port %u (TLS), aborting.", server.tls_port);
10 exit(1);
11 }
12
13 /* Open the listening Unix domain socket. */
14 if (server.unixsocket != NULL) {
15 unlink(server.unixsocket); /* don't care if this fails */
16 server.sofd = anetUnixServer(server.neterr,server.unixsocket,
17 server.unixsocketperm, server.tcp_backlog);
18 if (server.sofd == ANET_ERR) {
19 serverLog(LL_WARNING, "Opening Unix socket: %s", server.neterr);
20 exit(1);
21 }
22 anetNonBlock(NULL,server.sofd);
23 anetCloexec(server.sofd);
24 }
正是这段代码完成了Redis TCP socket的监听操作。我们从这里开始来分析一下Redis如何接受外部连接请求以及如何处理网络事件。
以下是监听指定端口的代码:
1/* Initialize a set of file descriptors to listen to the specified 'port'
2 * binding the addresses specified in the Redis server configuration.
3 *
4 * The listening file descriptors are stored in the integer array 'fds'
5 * and their number is set in '*count'.
6 *
7 * The addresses to bind are specified in the global server.bindaddr array
8 * and their number is server.bindaddr_count. If the server configuration
9 * contains no specific addresses to bind, this function will try to
10 * bind * (all addresses) for both the IPv4 and IPv6 protocols.
11 *
12 * On success the function returns C_OK.
13 *
14 * On error the function returns C_ERR. For the function to be on
15 * error, at least one of the server.bindaddr addresses was
16 * impossible to bind, or no bind addresses were specified in the server
17 * configuration but the function is not able to bind * for at least
18 * one of the IPv4 or IPv6 protocols. */
19int listenToPort(int port, socketFds *sfd) {
20 int j;
21 char **bindaddr = server.bindaddr;
22 int bindaddr_count = server.bindaddr_count;
23 char *default_bindaddr[2] = {"*", "-::*"};
24
25 /* Force binding of 0.0.0.0 if no bind address is specified. */
26 if (server.bindaddr_count == 0) {
27 bindaddr_count = 2;
28 bindaddr = default_bindaddr;
29 }
30
31 for (j = 0; j < bindaddr_count; j++) {
32 char* addr = bindaddr[j];
33 int optional = *addr == '-';
34 if (optional) addr++;
35 if (strchr(addr,':')) {
36 /* Bind IPv6 address. */
37 sfd->fd[sfd->count] = anetTcp6Server(server.neterr,port,addr,server.tcp_backlog);
38 } else {
39 /* Bind IPv4 address. */
40 sfd->fd[sfd->count] = anetTcpServer(server.neterr,port,addr,server.tcp_backlog);
41 }
42 if (sfd->fd[sfd->count] == ANET_ERR) {
43 int net_errno = errno;
44 serverLog(LL_WARNING,
45 "Warning: Could not create server TCP listening socket %s:%d: %s",
46 addr, port, server.neterr);
47 if (net_errno == EADDRNOTAVAIL && optional)
48 continue;
49 if (net_errno == ENOPROTOOPT || net_errno == EPROTONOSUPPORT ||
50 net_errno == ESOCKTNOSUPPORT || net_errno == EPFNOSUPPORT ||
51 net_errno == EAFNOSUPPORT)
52 continue;
53
54 /* Rollback successful listens before exiting */
55 closeSocketListeners(sfd);
56 return C_ERR;
57 }
58 anetNonBlock(NULL,sfd->fd[sfd->count]);
59 anetCloexec(sfd->fd[sfd->count]);
60 sfd->count++;
61 }
62 return C_OK;
63}
此处用到了bindaddr选项,我们先看看redis是如何读取这部分配置的。
1if (!strcasecmp(argv[0],"bind") && argc >= 2) {
2 int j, addresses = argc-1;
3
4 if (addresses > CONFIG_BINDADDR_MAX) {
5 err = "Too many bind addresses specified"; goto loaderr;
6 }
7 /* Free old bind addresses */
8 for (j = 0; j < server.bindaddr_count; j++) {
9 zfree(server.bindaddr[j]);
10 }
11 for (j = 0; j < addresses; j++)
12 server.bindaddr[j] = zstrdup(argv[j+1]);
13 server.bindaddr_count = addresses;
14}
CONFIG_BINDADDR_MAX 默认设置是16,也即允许最多绑定16个ip地址,否则报错。记录前会先释放老的配置,也即在redis.conf文件中多次设置bind会使新的配置覆盖老的配置。且所有ip要一次性写在bind 选项后面
1bind 127.0.0.1 10.22.134.5 ::1
监听地址绑定会导致服务仅会接收到绑定IP的连接请求。如果使用redis的主机地址固定这种方式可以增加安全性。
redis对每个监听地址做了如下事情
graph TD; Start --> 0; 0["char **bindaddr = server.bindaddr"] --> A; A["int bindaddr_count = server.bindaddr_count"] --> B; B("遍历所有绑定的地址 for(j = 0; j < bindaddr_count; j++)") --> C; C["char* addr = bindaddr[j]"] --> D; D{{"判断是否IPV6 if (strchr(addr,':'))"}} -->|Y| E; D -->|N| F; E["anetTcp6Server"] --> G; F["anetTcpServer"] --> G; G{{"监听失败 sfd->fd[sfd->count] == ANET_ERR"}} -->|Y| h; h{{"判断错误返回码"}} -->|N| H; h -->|Y Socket 未被创建| B H("closeSocketListeners(sfd);") --> J; G -->|N| J; J("anetNonBlock 设置非阻塞") --> K; K("anetCloexec 指定子进程自动关闭该句柄") --> L("sfd->count++"); L --> B;
可以看出这个函数中主要的工作是anetTCPServer做的。
1static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
2{
3 int s = -1, rv;
4 char _port[6]; /* strlen("65535") */
5 struct addrinfo hints, *servinfo, *p;
6
7 snprintf(_port,6,"%d",port);
8 memset(&hints,0,sizeof(hints));
9 hints.ai_family = af;
10 hints.ai_socktype = SOCK_STREAM;
11 hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
12 if (bindaddr && !strcmp("*", bindaddr))
13 bindaddr = NULL;
14 if (af == AF_INET6 && bindaddr && !strcmp("::*", bindaddr))
15 bindaddr = NULL;
16
17 if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
18 anetSetError(err, "%s", gai_strerror(rv));
19 return ANET_ERR;
20 }
21 for (p = servinfo; p != NULL; p = p->ai_next) {
22 if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
23 continue;
24
25 if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
26 if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
27 if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
28 goto end;
29 }
30 if (p == NULL) {
31 anetSetError(err, "unable to bind socket, errno: %d", errno);
32 goto error;
33 }
34
35error:
36 if (s != -1) close(s);
37 s = ANET_ERR;
38end:
39 freeaddrinfo(servinfo);
40 return s;
41}
这个函数理解没什么难的,先通过getaddrinfo对地址进行解析,并拿到解析后的IP地址。之后对所有解析的IP地址创建Socket,针对每个创建的Socket对其使用anetSetReuseAddr,设置地址重用并绑定监听地址用于监听该地址的连接请求。
anetTcp6Server流程基本一致,只是协议参数使用的是AF_INET6,仅此而已。
监听的Socket被存储在server.ipfd 以及 server.tlsfd 两个服务器变量中留待后用。其中server.ipfd是未加密的socket,server.tlsfd是ssl加密的socket。
评论