准备工作
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;
}