单服务器TCP百万连接测试的三个限制

准备工作

ubuntu 虚拟机

测试流程

服务器:使用epoll,接收所有连接,看是否能到达1M的连接数。
客户端:就是一个简单的while循环,循环只做一件事,就是申请一个socket然后connect()
不断输出连接数看看能到达多少。

测试1

启动服务器绑定9911端口,启动client连接,发现只能保持1k左右的连接。
在这里插入图片描述

限制1: 最大打开的 fd

原因就在于shell 进程的最大打开文件fd,用于通信的clientfd也算,可以查看最大支持的fd数量是多少,使用 ulimit -n 查看。

ulimit -n

在这里插入图片描述
发现是1024,所以被限制在了1024。

解决方式

通过改个大的值,同样的客户端也需要修改。(这个方法只在这个shell下生效)

ulimit -n 1048576

测试2

解决了fd限制的问题之后,继续测试。
客户端在连接到大2w8左右的时候connect()出错了,打印errno看看是99,cannot assign requested address,说明端口耗尽(实际上只是客户端对服务器的那个端口的组合耗尽了,客户端如果连接其他端口还能继续用,没有影响)。
在这里插入图片描述

限制2: 端口限制

TCP连接靠的是 sock 进行通信,每一个TCP使用的socket对应一个四元组(也就是一个连接) (源IP, 源Port,目的IP, 目的Port),要建立不同连接就需要改变这个四个中的一个,因为是同一台主机(我只用了一个IP),所以只能改变端口了。

可用端口可通过命令查看:

cat /proc/sys/net/ipv4/ip_local_port_range

在这里插入图片描述
可以看到这台虚拟机最多只能用 2w8 左右个端口。服务器绑定一个端口9911,客户端最多2w8个端口可用,连接最大只能到 1 * 2w8 = 2w8。
在这里插入图片描述
那么怎么才能多创建几个连接呢,如果服务端多几个端口,组合数就多了,sock对应的四元组组合(连接)就多了,如图:多了蓝色的几个连接。
在这里插入图片描述

解决方式

服务器多监听几个端口试试
这里在服务端创建了100个listenfd,绑定在100个不同的端口上,把这些listenfd都放到epoll里面去。客户端在connect失败之后就换一个服务器的端口进行connect(),结果如下,可以看到已经超过了可用端口数2w8.
在这里插入图片描述

测试3

上面已经超过了2w8(可用端口数),但是在连接停止在6w5左右。
在这里插入图片描述

限制3: nf_conntrack_max

解决了前两个限制,还有一个限制就是 nf_conntrack_max,当连接超过设定值(65535)的时候就无法连接上了,服务器配置问题。

Linux 内核中有一个内核模块 nf_conntrack 用于做连接跟踪,当 iptables 添加了 nat 相关规则时此模块会被自动启用。net.netfilter.nf_conntrack_max: 连接跟踪表最大值,超出后会导致操作系统会丢包。默认为 65535,优化至 6553500。

– 引用《https://xie.infoq.cn/article/c271f0b17cc3b0cf2c2f7f73c》

查看nf_conntrack_max的命令:

sysctl -a | grep nf_conntrack_max
# 输出:
# net.netfilter.nf_conntrack_max = 65535
# net.nf_conntrack_max = 65535

解决方式

vi /etc/sysctl.conf
# 添加一行 
net.netfilter.nf_conntrack_max = 1048576
# 执行下列命令使其生效
sysctl -p

再次测试:
20W连接,内存只有100M了,很慢了,没时间等了,就不继续往下了,维护TCP连接用了大概 1600M的内存。。。
在这里插入图片描述

一些常用知识

  • 调整最大打开fd
#以下两个是进程级别的
# vi /etc/sysctl.conf
fs.nr_open=1100000  //要比 hard nofile 大一点
# sysctl -p(生效)
# vi /etc/security/limits.conf
*  soft  nofile  1000000
*  hard  nofile  1000000

# file-max 是系统级别的
fs.file-max=1100000
  • 查看进程号为 pid 的进程打开的fd
ls /proc/{pid}/fd/

测试代码

epoll服务器测试代码。

g++ test_server.cpp -o server
./server server_port
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <iostream>
#include <signal.h>
#include <unordered_set>

#define _TEST_MODE_

#define EPOLL_SIZE (1024 * 20)
#define BUFF_SIZE 1024
// 多少个监听端口
#define MAX_SERVER_PORTS 100

bool is_listenfd(std::unordered_set<int>& fds, int fd) {
    return fds.find(fd) != fds.end();
}

int main(int argc, char** argv) {
    #if 1
    int epollfd = epoll_create(EPOLL_SIZE);
    epoll_event ev, events[EPOLL_SIZE];
    // 创建100个listenfd
    std::unordered_set<int> listenfds;
    for(int i = 0; i < MAX_SERVER_PORTS; i++) {
    	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	    sockaddr_in server_addr;

	    server_addr.sin_family = AF_INET;
	    server_addr.sin_port = htons(atoi(argv[1])+i);
	    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	    bind(listenfd, (sockaddr*)&server_addr, sizeof(sockaddr_in));
	    if(-1 == listen(listenfd, 1024)) {
            std::cerr << "listen failed." << std::endl;
            exit(-1);
	    }
	    std::cout << i << " listening " << std::endl;

        listenfds.insert(listenfd);

    	ev.events = EPOLLIN;
        ev.data.fd = listenfd;
        epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
    }
    
    char buff1[16] = {0};
    char msg_buff[BUFF_SIZE + 1] = {0};

    int conn_cnt = 0;
    while(1) {
        int nReady = epoll_wait(epollfd, events, EPOLL_SIZE, -1);
        for(int i = 0; i < nReady; i++) {
            if(is_listenfd(listenfds, events[i].data.fd)) {
                sockaddr_in client_addr;
                socklen_t len = sizeof(sockaddr_in);
                int clientfd = accept(events[i].data.fd, (sockaddr*)&client_addr, &len);

                if(clientfd <= 0) continue;

				++conn_cnt;
                printf("listenfd: %d new clientfd:%d \t connections: %d\n",events[i].data.fd, clientfd, conn_cnt);

                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = clientfd;
                epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &ev);
            } else {
                int clientfd = events[i].data.fd;
                int read_len = recv(clientfd, msg_buff, BUFF_SIZE, 0);
                if(read_len <= 0) {

                    close(clientfd);
                    ev.data.fd = clientfd;
                    ev.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev);
                } else {
					// 这个else分支可以不需要
                }
            }
        }
    }

    close(epollfd);
    // close(listenfd);
    #endif
    

    return 0;
}

客户端代码:

g++ test_client.cpp -o client
./client server_ip server_port

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <memory>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <errno.h>

// 最多尝试 100 个server的监听端口
#define MAX_SERVER_PORTS 100

int main(int argc, char** argv) {

    sockaddr_in server_addr_list[MAX_SERVER_PORTS];
    for(int i = 0; i < MAX_SERVER_PORTS; ++i) {
        server_addr_list[i].sin_family = AF_INET;
        server_addr_list[i].sin_port = htons(atoi(argv[2]) + i);
        server_addr_list[i].sin_addr.s_addr = inet_addr(argv[1]);
    }

    sockaddr_in server_addr;
    

    int cnt = 0;
    time_t start, last;
    start = last = time(NULL);

    int i = 0;
    while(1) {
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        int state = connect(sockfd, (sockaddr*)&server_addr_list[i], sizeof(sockaddr));

		// 换一个服务器端口进行连接
        if(state == -1) {
            i++;
            printf("连接失败, errno: %d \n", errno);
            printf("connecting to %d(%d) server port with max_connections: %d in %d s.\n", i, ntohs(server_addr_list[i].sin_port), cnt, (int)difftime(time(NULL), start));
            if(i >= MAX_SERVER_PORTS) break;
	        continue;
        }

        if(++cnt % 1000 == 0) {
            time_t cur = time(NULL);
            printf("current connections: %d (1000 connections in %f s)\n", cnt, (float)difftime(cur, last));
            last = cur;
        }
	    
    }

    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP连接超时是指在建立TCP连接时,如果一方在规定的时间内没有收到对方的响应,就会认为连接超时。 在TCP协议中,当两台主机进行通信时,双方需要进行三次握手来建立连接。在第一次握手中,客户端向服务器发送一个请求,服务器在收到请求后会回复一个确认响应。如果客户端在一定时间内没有收到服务器的响应,就会认为连接超时。同样地,在后续的握手过程中,如果任意一方在规定的时间内没有收到对方的响应,也会触发连接超时。 连接超时的原因可能是网络延迟、网络拥塞、服务器繁忙、客户端或服务器故障等。当连接超时发生时,可以考虑进行以下几个方面的排查与解决: 1. 网络状况:检查网络连接是否正常,可以尝试使用其他设备进行连接,确认是否存在网络问题。 2. 网络延迟:考虑通过改变连接的位置或使用更快的网络环境来避免延迟问题。 3. 服务器状态:确认服务器是否繁忙或发生故障,可以联系服务器管理员检查服务器的运行状态。 4. 防火墙或代理设置:检查防火墙或代理是否对TCP连接进行了限制,可以尝试暂时关闭防火墙或代理进行测试。 5. 客户端或服务器故障:如果其他设备可以正常连接,但某一台设备无法建立连接,则可能是设备本身的问题,可以尝试重新启动设备或修复相关软件。 在调试和解决TCP连接超时问题时,需要综合考虑以上因素并逐步排查。通过定位问题原因,可以采取相应的措施来解决连接超时的问题,以确保正常的网络通信和连接建立。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值