美文网首页
实践单机实现百万连接

实践单机实现百万连接

作者: wayyyy | 来源:发表于2022-10-01 15:52 被阅读0次
    客户端最大连接数的上限?

    操作系统采用 <客户端IP : 客户端端口> : <服务端IP : 服务端端口> 四元组来标识一条TCP连接。

    所以要想实现百万连接:

    • 第一种是服务器端只开启一个进程,然后使用很多个客户端进程绑定不同的客户端 ip 来连接,假设 20个ip * 5w(端口范围是最大65535,这里保守算5w)

    • 第二种是服务器开启多个进程,这样客户端就可以只使用一个 ip 即可,原理类似。

    但在这之前,需要我们先配置些参数:

    文件句柄的限制

    一个tcp连接就需要占用一个文件描述符,一旦文件描述符用完,新的连接就会返回给我们错误是:Can’topen so many files。linux系统出于安全角度的考虑,在多个位置都限制了可打开的文件描述符的数量,包括系统级、进程级、用户进程级。

    • fs.file-max:当前系统可打开的最大数量
      编辑 /etc/sysctl.conf 文件,加入:

      fs.file-max=1100000
      

      然后输入:

      # sysctl -p
      

      可使用 sysctl -p 使之生效。

      可新打开个终端使用 sysctl -p 查看,配置是否生效。

    • fs.nr_open:当前系统单个进程可打开的最大数量
      编辑 /etc/sysctl.conf 文件,加入:

      fs.nr_open=70000 
      

      然后输入:

      # sysctl -p
      

      使配置生效。

      可新打开个终端使用 sysctl -p 查看,配置是否生效。

    • nofile:每个用户的进程可打开的最大数量

      # ulimit -n
      

      这里输出1024,意味着最多只能打开1024个文件。

      • 临时修改:

        # ulimit -n 1000000
        

        这种修改只对当前登录用户目前的使用环境有效,系统重启或用户退出后就会失效。

      • 永久修改:
        编辑 /etc/security/limits.conf 文件,下面修改对所有用户生效。

        *  soft  nofile  55000  
        *  hard  nofile  55000
        

        注意!!!:hard nofile 一定要比 fs.nr_open 要小,否则可能导致用户无法登陆。

      验证程序的配置是否生效,可使用:

      # cat /proc/[pid]/limits
      
      image.png
    端口号范围限制

    操作系统上端口号1024以下是系统保留的,从1024-65535是用户使用的。但有时Linux 默认设置 端口范围并不是 1024-65535。

    • 查看端口限制:

      # sysctl net.ipv4.ip_local_port_range 
      
      image.png

      或者:

      # cat /proc/sys/net/ipv4/ip_local_port_range
      
    • 暂时性修改

      # echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range
      

      或者:

      # sysctl -w net.ipv4.ip_local_port_range="1024 64000"
      
    • 永久性修改
      编辑 /etc/sysctl.conf 文件,在其中加入:

      net.ipv4.ip_local_port_range = 1024 65535
      
    实践

    按照方案一,我们只要实现单个客户端进程5w个连接就可以实现单机百万了。
    所以这里实践只按照实现单个客户端进程5w个连接目标来做。

    客户端代码:

    #include <netinet/in.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <arpa/inet.h> 
    
    #define MAX_CONNECTION_NUM 50000
    
    int buildConnect(const char *lIp, const char *sIp, int sPort)
    {
        int skFd; 
        if((skFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            printf("\n Error : Could not create socket\n");
            return 0;
        }
    
        struct sockaddr_in cliAddr;
        cliAddr.sin_family = AF_INET;
        cliAddr.sin_addr.s_addr = inet_addr(lIp);
        cliAddr.sin_port = 0;
        if(bind(skFd,  (struct sockaddr *)&cliAddr, sizeof(cliAddr)) < 0)
        {
            printf("\n Error : Bind Failed \n");
        }
    
        struct sockaddr_in srvAddr;
        srvAddr.sin_family = AF_INET;
        srvAddr.sin_addr.s_addr = inet_addr(sIp);
        srvAddr.sin_port = htons(sPort); 
        if(connect(skFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) < 0)
        {
           printf("\n Error : Connect Failed \n");
           return 0;
        } 
    
        return skFd;
    }
    
    int main(int argc, char *argv[])
    {
        int i = 0, sPort, fd;
        char lIp[16], sIp[16];
    
        if(argc != 4)
        {
            printf("\n Usage: %s <local ip> <server ip> <server port>\n", argv[0]);
            return 1;
        }
    
        // 1. 从命令行获取并解析local ip、server ip以及端口
        strcpy(lIp, argv[1]);
        strcpy(sIp, argv[2]);
        sPort = atoi(argv[3]);
        
        // 2. 开始建立连接
        int *sockets = (int *)malloc(sizeof(int) * MAX_CONNECTION_NUM);
        for(i = 1; i <= MAX_CONNECTION_NUM; i++)
        {
            if(0 == i % 1000)
            {//稍稍停顿一下,避免把服务端的握手队列打满
                printf("%s 连接 %s:%d成功了 %d 条!\n", lIp, sIp, sPort, i);
                sleep(1);
            }
            
            fd = buildConnect(lIp, sIp, sPort);
            if(fd > 0)
            {
                sockets[i-1] = fd;
            }else{
                return 1;
            }
        }
        sleep(300);
    
        // 3. 释放所有的连接
        printf("关闭所有的连接...\n");
        for(i = 0; i < MAX_CONNECTION_NUM; i++)
        {
            close(sockets[i]);
        }
     
        return 0;
    }
    

    服务端代码

    #include <arpa/inet.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_CONNECTION_NUM 1100000
    
    int main(int argc, char *argv[])
    {
        char ip[16];
        int lisFd, conFd, port;
        struct sockaddr_in servAddr; 
    
        if(argc != 3)
        {
            printf("\n Usage: %s <server ip> <server port>\n", argv[0]);
            return 1;
        }
    
        // 1. 从命令行获取并解析server ip以及端口
        strcpy(ip, argv[1]);
        port = atoi(argv[2]);
    
        // 2. 创建server
        if((lisFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            printf("\n Error : Could not create socket\n");
            return 0;
        }
    
        servAddr.sin_family = AF_INET;
        servAddr.sin_addr.s_addr = inet_addr(ip);
        servAddr.sin_port = htons(port); 
        if(bind(lisFd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0)
        {
            printf("\n Error : Bind Failed \n");
        }
        if((listen(lisFd, 1024)) < 0)
        {
            printf("\n Error : Listen Failed \n");
        }
    
        // 3. 接收连接
        int i = 0;
        int *sockets = (int *)malloc(sizeof(int) * MAX_CONNECTION_NUM);
        while(1)
        {
            conFd = accept(lisFd, (struct sockaddr*)NULL, NULL); 
            if(conFd > 0)
            {
                sockets[i++] = conFd;
                printf("%s %d accept success:%d\n", ip, port, i);
            }
        }
    }
    

    找两个机器,测试如下:
    运行服务端

    # gcc server.c -o server
    # ./server 192.168.48.139 8080  # 服务端ip 为 192.168.48.139
    

    运行客户端

    # gcc client.c -o client
    # ./client 192.168.48.137 192.168.48.139 8080  # 客户端ip为 192.168.48.137
    

    参考资料:
    1、https://mp.weixin.qq.com/s/f_CMt2ni0vegB3-pf2BTTg

    相关文章

      网友评论

          本文标题:实践单机实现百万连接

          本文链接:https://www.haomeiwen.com/subject/jtedartx.html