邯郸物流:曹工说Redis源码(4)-- 通过redis server源码来明白 listen 函数中的 backlog 参数

admin 2个月前 (06-18) 科技 70 2

文章导航

Redis源码系列的初衷,是辅助我们更好地明白Redis,更懂Redis,而怎么才气懂,光看是不够的,建议随着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者随着我这边一起阅读。由于我用c也是好几年以前了,些许错误在所难免,希望读者能不惜指出。

曹工说Redis源码(1)-- redis debug环境搭建,使用clion,到达和调试java一样的效果

曹工说Redis源码(2)-- redis server 启动历程剖析及简朴c语言基础知识弥补

曹工说Redis源码(3)-- redis server 启动历程完整剖析(中)

本讲主题

早上,手艺群里,有个同砚问了个问题

这样看来,照样有部门同砚,对backlog这个参数,不甚了解,以是,爽性本讲就讲讲这个话题。

原本可以直接拿java来举例,不外这几天正好在看redis,而且 redis server就是服务端,也是对外提供监听端口的,而且其用 c 语言编写,直接挪用操作系统的api,不像java那样封装了一层,我们直接拿redis server的代码来剖析,就能离真相更近一点。

我会拿一个例子来讲,例子里的代码,是直接从redis的源码中拷贝的,一行没改,通过这个例子,我们也能更明白redis一些。

demo解说

backlog参数简朴解说

好比我监听某端口,那么客户端可以来同该端口,确立socket毗邻;正常情形下,服务端(bio模式)会一直壅闭挪用accept。

人人想过没有,accept是怎么拿到这个新进来的socket的?实在,这中心就有个壅闭行列,当行列没有元素的时刻,accept就会壅闭在这个行列的take操作中,以是,我个人感受,accept操作,实在和行列的从队尾或队头取一个元素,是一样的。

当新客户端确立毗邻时,完成了三次握手后,就会被放到这个行列中,这个行列,我们一样平常叫做:全毗邻行列。

而这个行列的最大容量,或者说size,就是backlog这个整数的巨细。

正常情形下,只要服务端程序,accept不要卡壳,这个backlog行列多大多小都无所谓;若是设置大一点,就能在服务端accept速率对照慢的时刻,起到削峰的作用,怎么感受和mq有点像,哈哈。

说完了,下面最先测试了,首先测试程序正常accept的情形。

main测试程序


int main() {
    // 1
    char *pVoid = malloc(10);
    // 2
    int serverSocket = anetTcpServer(pVoid, 6380, NULL, 2);
    printf("listening...");
    
    while (1) {
        int fd;
        struct sockaddr_storage sa;
        socklen_t salen = sizeof(sa);
		// 3
        char* err = malloc(20);
        // 4
        if ((fd = anetGenericAccept(err, serverSocket, (struct sockaddr*)&sa, &salen)) == -1)
            return ANET_ERR;
        printf("accept...%d",fd);
    }
}
  • 1处,我们先分配了一个10字节的内存,这个主要是存放错误信息,在c语言编程中,不能像高级语言一样抛异常,以是,返回值一样平常用来返回0/1,示意函数挪用的成功失败;若是需要在函数内部修改什么器械,一样平常就会先new一个内存出来,然后把指针传进去,然后在里面就对这片内存空间举行操作,这里也是一样。

  • anetTcpServer 是我们自界说的,内部会实现如下逻辑:在本机的6380端口上举行监听,backlog参数即全毗邻行列的size,设为2。若是失足的话,就会把错误信息,写入1处的谁人内存中。

    这一步挪用完成后,端口就起好了。

  • 3处,同样分配了一点内存,供accept毗邻失足时使用,和1处作用类似

  • 4处,挪用accept去从行列取毗邻

anetTcpServer,监听端口

int anetTcpServer(char *err, int port, char *bindaddr, int backlog) {
    return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}


static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog) {
    int s, rv;
    char _port[6];  /* strlen("65535") */
    struct addrinfo hints, *servinfo, *p;

    snprintf(_port, 6, "%d", port);
    // 1
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = af;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;    /* No effect if bindaddr != NULL */
	
    // 2
    if ((rv = getaddrinfo(bindaddr, _port, &hints, &servinfo)) != 0) {
        anetSetError(err, "%s", gai_strerror(rv));
        return ANET_ERR;
    }
    for (p = servinfo; p != NULL; p = p->ai_next) {
        // 3
        if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
            continue;
		// 4
        if (anetSetReuseAddr(err, s) == ANET_ERR) goto error;
        // 5
        if (anetListen(err, s, p->ai_addr, p->ai_addrlen, backlog) == ANET_ERR) goto error;
        goto end;
    }

    error:
    	s = ANET_ERR;
    end:
    	freeaddrinfo(servinfo);
    return s;
}
  • 1处,new一个结构体,c语言中,new一个工具对照贫苦,要先界说一个结构体类型的变量,如struct addrinfo hints,,然后挪用memset来初始化内存,然后设置各个属性。总体来说,这里就是new了一个ipv4的地址

  • 2处,由于一样平常服务器都有多网卡,多个ip地址,另有环回网卡之类的,这里的getaddrinfo,是行使我们第一步的hints,去辅助我们筛选出一个最终的网卡地址出来,然后赋值给 servinfo 变量。

    这里可能有不准确的地方,人人可以直接看官方文档:

    int getaddrinfo(const char *node, const char *service,
    const struct addrinfo *hints,
    struct addrinfo **res);

    Given node and service, which identify an Internet host and a service, getaddrinfo() returns one or more addrinfo structures, each of which contains an Internet address that can be specified in a call to bind(2) or connect(2).

  • 3处,使用第二步拿到的地址,new一个socket

  • 4处,anetSetReuseAddr,设置SO_REUSEADDR选项,我简朴查了下,可参考:

    [socket常见选项之SO_REUSEADDR,SO_REUSEPORT]

    SO_REUSEADDR
    一样平常来说,一个端口释放后会守候两分钟之后才气再被使用,SO_REUSEADDR是让端口释放后立刻就可以被再次使用

  • 5处,挪用listen举行监听,这里用到了我们传入的backlog参数。

    其中,backlog参数的官方说明,如下,意思也就是说,是行列的size:

其中,anetListen是我们自界说的,我们接着看:

/*
 * 绑定并建立监听套接字
 */
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
    // 1
    if (bind(s, sa, len) == -1) {
        anetSetError(err, "bind: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }
	// 2
    if (listen(s, backlog) == -1) {
        anetSetError(err, "listen: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    return ANET_OK;
}
  • 1处,这里举行绑定
  • 2处,这里挪用操作系统的函数,举行监听,其中,第一个参数就是前面的socket file descriptor,第二个,就是backlog。

若何运行

代码地址:

https://gitee.com/ckl111/redis-3.0-annotated-cmake-in-clion/blob/master/our-redis-implementation/my_anet.c

https://gitee.com/ckl111/redis-3.0-annotated-cmake-in-clion/blob/master/our-redis-implementation/my_anet.h

人人把上面这两个文件,自己放到一个linux操作系统的文件夹下,然后执行以下下令,就能把这个demo启动起来:

测试

查看监听端口是否启动

[root@mini2 ~]# netstat -ano|grep 6380
tcp        0      0 0.0.0.0:6380            0.0.0.0:*               LISTEN      off (0.00/0/0)

开启一个shell,毗邻到6380端口

阳光在线,诚信在线  第1张

我这边开了3个shell,去毗邻6380端口,然后,我执行:

[root@mini2 ~]# netstat -ano|grep 6380
tcp        0      0 0.0.0.0:6380            0.0.0.0:*               LISTEN      off (0.00/0/0)
tcp        0      0 127.0.0.1:51386         127.0.0.1:6380          ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:54442         127.0.0.1:6380          ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:51930         127.0.0.1:6380          ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:6380          127.0.0.1:51386         ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:6380          127.0.0.1:54442         ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:6380          127.0.0.1:51930         ESTABLISHED off (0.00/0/0)

可以看到,已经有3个socket,毗邻到6380端口了。

查看端口对应的backlog行列的相关器械

怎么看backlog那些呢?有个下令叫ss,其是netstat的升级版,执行以下下令如下:

[root@mini2 ~]# ss -l |grep 6380
tcp    LISTEN     0      2       *:6380                  *:*     

上面我们查询了6380这个监听端口的状态,其中,

  • 第一列,tcp,传输协议的名称

  • 第二列,状态,LISTEN

  • 第三列,查阅man netstat可以看到,

    Recv-Q
           Established: The count of bytes not copied by the user program connected to this socket.  
           Listening: Since Kernel 2.6.18 this column contains  the  current syn backlog.
    

    当其为Established状态时,应该是缓冲区中没被拷贝到用户程序的字节的数目;

    当其为LISTEN状态时,示意当前backlog这个行列,即前面说的全毗邻行列的,容量的巨细;这里,由于我们的程序一直在accept毗邻,以是这里为0

  • 第4列,官方文档:

    Send-Q
    Established: The count of bytes not acknowledged by the remote host.  	
    
    Listening:   Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.
    

    当其为Established时,示意我方缓冲区中还没有被对方ack的字节数目

    当其为Listen时,示意全毗邻行列的最大容量,我们是设为2的,以是这里是2。

测试2

当我们程序不去accept的时刻,会怎么样呢,修改程序如下:

int main() {
    char *pVoid = malloc(10);
    int serverSocket = anetTcpServer(pVoid, 6380, NULL, 2);
    printf("listening...");

    while (1){
        sleep(100000);
    }

}

然后我们再去开启3个客户端毗邻,然后,最后看ss下令的情形:

[root@mini2 ~]# ss -l |grep 6380
tcp    LISTEN     3      2       *:6380                  *:*   

再执行netstat看看:

[root@mini2 ~]# netstat -ano|grep 6380
tcp        0      0 127.0.0.1:50238         127.0.0.1:6380          ESTABLISHED off (0.00/0/0)
tcp        0      0 127.0.0.1:50362         127.0.0.1:6380          ESTABLISHED off (0.00/0/0)

发现了吗,只有2个毗邻是ok的。由于我们的全毗邻行列,最大为2,现在已经full了啊,以是新毗邻进不来了。

总结

人人可以随着我的demo试一下,信赖明白会更深刻一点。

以前我也写了一篇,人人可以参考下。

Linux中,Tomcat 怎么承载高并发(深入Tcp参数 backlog)

,

sunbet 申博

sunbet 申博www.sunbet88.us是Sunbet指定的Sunbet官网,Sunbet提供Sunbet(Sunbet)、Sunbet、申博代理合作等业务。

站点信息

  • 文章总数:199
  • 页面总数:0
  • 分类总数:8
  • 标签总数:298
  • 评论总数:225
  • 浏览总数:11105

标签列表