监听网络端口

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。

评论