服务器百万级并发量测试
并发量指的是服务器同时承载的客户端数量
测试标准
- 并发量:fd数量、协程的数量
- 每秒接入量
- 断开连接
如何测试服务器百万级并发量,需要进行以下操作。
1、修改进程最大打开 fd 的数量
error: too many open files
ulimit -a # 查看 open files 的数量,默认1024
# 临时修改
ulimit -n 1048576 # 修改
# 永久修改
sudo vim /etc/security/limits.conf
# 添加内容,软连接:超出软限制会发出警告;硬连接:绝对限制,任何情况不允许超过该限制
* soft nofile 1048576
* hard nofile 1048576
2、设置服务器多开端口
error : Cannot assign requested address
sockfd:sockfd -> (sip, sport, dip, dport, proto)
sockfd 五元组内任意元素不相同代表不同的 sockfd,服务器能判断出属于不同的连接,使用不同的sockfd 与之通信。
对于服务器与客户端来说,服务器 ip 地址不变,客户端 ip 不变,协议不变,唯一可变的只有两者的端口号。对于客户端来说,客户端可以多端口发起申请,增加 sockfd 的数量。同理对于服务器来说,也可以同时使用多端口进行响应,增加 sockfd 的数量。
3、修改客户端 netfilter 的过滤上限
error:connection time out
Netfilter 是 linux 内核的网络数据包处理框架,当客户端发送的包的数量超过规定的上限,则会丢弃包,因此测试前需要修改其过滤上限。
# 修改客户端 sysctl.conf 中 netfilter 的过滤上限
vim /etc/sysctl.conf
# 修改 netfilter 过滤上限,默认65536
net.nf_conntrack_max = 1048576 #内存中最多ip_conntrack结构的数量
# 可选:调整tcp的缓存空间
net.ipv4.tcp_mem = 252144 524288 786432 # 协议栈的空间大小,单位页(4k)
net.ipv4.tcp_wmem = 2048 2048 4096 #每个sockfd的写buffer大小, 最小值 默认值 最大值
net.ipv4.tcp_rmem = 2048 2048 4096 #每个sockefd的读buffer大小, 最小值 默认值 最大值
fs.file-max = 1048576 # 设置fd的最大值,并非数量
# 重新加载配置文件
sysctl -p
4、测试方法
准备一台服务端,三个客户端,每个客户端 34w 连接数,服务器接收连接100w
客户端连接数:
服务器端连接数:
5、测试代码
客户端代码:
// mul_port_client_epoll.c -> ./client ip port
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define MAX_BUFFER 128
#define MAX_EPOLLSIZE (384*1024)
#define MAX_PORT 100
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
// 设置非阻塞io
static int ntySetNonblock(int fd) {
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
// 设置地址可重用
static int ntySetReUseAddr(int fd) {
int reuse = 1;
return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}
int main(int argc, char **argv) {
if (argc <= 2) {
printf("Usage: %s ip port\n", argv[0]);
exit(0);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int connections = 0;
char buffer[128] = {0};
int i = 0, index = 0;
struct epoll_event events[MAX_EPOLLSIZE];
int epoll_fd = epoll_create(MAX_EPOLLSIZE);
strcpy(buffer, " Data From MulClient\n");
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
struct timeval tv_begin;
gettimeofday(&tv_begin, NULL);
while (1) {
// 每次换用不同的端口号
if (++index >= MAX_PORT) index = 0;
struct epoll_event ev;
int sockfd = 0;
if (connections < 340000 && !isContinue) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
goto err;
}
addr.sin_port = htons(port + index);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
goto err;
}
ntySetNonblock(sockfd);
ntySetReUseAddr(sockfd);
sprintf(buffer, "Hello Server: client --> %d\n", connections);
send(sockfd, buffer, strlen(buffer), 0);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
connections ++;
}
//connections ++;
if (connections % 1000 == 999 || connections >= 340000) {
struct timeval tv_cur;
memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
gettimeofday(&tv_begin, NULL);
int time_used = TIME_SUB_MS(tv_begin, tv_cur);
printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
int nfds = epoll_wait(epoll_fd, events, connections, 100);
for (i = 0;i < nfds;i ++) {
int clientfd = events[i].data.fd;
if (events[i].events & EPOLLOUT) {
sprintf(buffer, "data from %d\n", clientfd);
send(sockfd, buffer, strlen(buffer), 0);
} else if (events[i].events & EPOLLIN) {
char rBuffer[MAX_BUFFER] = {0};
ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
if (length > 0) {
printf(" RecvBuffer:%s\n", rBuffer);
if (!strcmp(rBuffer, "quit")) {
isContinue = 0;
}
} else if (length == 0) {
printf(" Disconnect clientfd:%d\n", clientfd);
connections --;
close(clientfd);
} else {
if (errno == EINTR) continue;
printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
} else {
printf(" clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
}
}
usleep(500);
}
return 0;
err:
printf("error : %s\n", strerror(errno));
return 0;
}