1. 前言
在网上看到什么百万并发之类的,感觉很高大上,也看了一些学习视频,决定自己测试一下,满足一下好奇心,同时也学习一些知识。本次主要在已经准备好的源码上对系统参数做出一些调整。
2. 环境准备
- 两台笔记本配置都比较低,运行内存 8G
- 启动 3 个 linux 虚拟机
- 两个客户端配置 4G 内存 4 个核心、另外一个做服务器 8G 4 个核心。
- 服务器使用 由 epoll 作为分发器实现的 单线程的 reactor 模型
3. 理论基础
3.1. 服务器理论最大并发数
程序方面对于并发的限制主要由 fd 的个数,以及对应 pcb 中五元组信息的个数。
五元组包括:源 IP 源端口 目的 IP 目的端口 协议
本次测试实验中:
源 IP:固定的
源端口:20
目的 IP:3 个客户端 IP
目的端口:65535
协议:TCP
本次实验环境理论并发数:1 * 20 * 3 * (65535 - 1024) * 1 = 3870660
4. 尝试过程
4.1. 第一次尝试
启动服务后,刚启动一个客户端,并发到 999 后,报错 too many open files
判断这是进程受到文件描述符个数的限制。
下面修改包括所有用到的客户端服务器对应的数据。
使用 linux 命令:ulimit -a
查看默认进程支持文件描述符个数为 1024
修改进程默认打开的文件描述符个数。
ulimit -n 1048576
4.2. 第二次尝试
上述修改调整了进程支持文件描述符的个数。再次运行。
此时,服务器在并发数 53999 时,报错连接超时。
怀疑是 linux 防火墙的连接追踪表的大小导致的,查看一下表的大小是 65535 感觉非常接近。
修改连接追踪表的大小:sysctl -w net.netfilter .nf conntrack max=1000000
4.3. 第三次尝试
这次开了 3 个客户端和一个服务器。并发数量也到达了 77 万左右。但是此时服务器设置的内存是 4G。
4.4. 第四次尝试
将服务器调整到 6.5G 内存后,进行了多次测试,很遗憾由于电脑内存实在不够用,最后也只跑到 96 万的样子。
5. 总结
5.1. 实操的重要性
虽然过程并不算复杂,但是自己搭建环境,实际操作下来确实会有更加直观的感受,就是有一点遗憾没有到达 100 万的并发。希望大家有时间可以自己试试。
5.2. 可能需要修改的参数
vi /etc/sysctl.conf #vi编辑,然后添加设置 ; sysctl -p #生效
fs.file-max = 3145728 //设置系统可以打开的最大文件数
fs.suid_dumpable = 1 //如果设置为 1,允许将 core 文件的 SetUID 位设置为有效
kernel.core_uses_pid = 1 //如果设置为 1,core 文件名将包含进程的 PID
kernel.printk = 4 4 1 7 //控制内核消息的输出级别。这里设置为 4 4 1 7 表示输出所有消息
kernel.shmmax = 7516192768 //共享内存段的最大值(以字节为单位)
kernel.shmall = 1835008 //共享内存总量的最大值(以页面为单位)
net.ipv4.ip_local_port_range = 8192 65535 //本地端口范围,定义了分配给本地主机使用的端口的范围
net.ipv4.ip_nonlocal_bind = 1 //允许非本地 IP 地址绑定到套接字
net.ipv4.netfilter.ip_conntrack_max = 1048576 //系统上允许的最大活动连接数
net.ipv4.tcp_max_tw_buckets = 1048576 //系统上允许的最大 TIME-WAIT 状态的 TCP 连接数
net.ipv4.tcp_syncookies = 1 //启用 SYN cookies,可以防止 SYN 攻击
net.ipv4.tcp_rmem = 4096 87380 4120576 //TCP 接收缓冲区的最小值、默认值和最大值
net.ipv4.tcp_wmem = 4096 16384 4120576 //TCP 发送缓冲区的最小值、默认值和最大值
5.3. 代码
//creator.c
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/time.h>
#define BUFFER_LENGTH 512
typedef int (*RCALLBACK)(int fd);
// listenfd
// EPOLLIN -->
int accept_cb(int fd);
// clientfd
//
int recv_cb(int fd);
int send_cb(int fd);
// conn, fd, buffer, callback
struct conn_item {
int fd;
char rbuffer[BUFFER_LENGTH];
int rlen;
char wbuffer[BUFFER_LENGTH];
int wlen;
union {
RCALLBACK accept_callback;
RCALLBACK recv_callback;
} recv_t;
RCALLBACK send_callback;
};
// libevent -->
int epfd = 0;
struct conn_item connlist[1048576] = {0}; // 1024 2G 2 * 512 * 1024 * 1024
// list
struct timeval zvoice_king;
//
// 1000000
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int set_event(int fd, int event, int flag) {
if (flag) { // 1 add, 0 mod
struct epoll_event ev;
ev.events = event ;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
} else {
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int accept_cb(int fd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
if (clientfd < 0) {
return -1;
}
set_event(clientfd, EPOLLIN, 1);
connlist[clientfd].fd = clientfd;
memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].rlen = 0;
memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].wlen = 0;
connlist[clientfd].recv_t.recv_callback = recv_cb;
connlist[clientfd].send_callback = send_cb;
if ((clientfd % 1000) == 999) {
struct timeval tv_cur;
gettimeofday(&tv_cur, NULL);
int time_used = TIME_SUB_MS(tv_cur, zvoice_king);
memcpy(&zvoice_king, &tv_cur, sizeof(struct timeval));
printf("clientfd : %d, time_used: %d\n", clientfd, time_used);
}
return clientfd;
}
int recv_cb(int fd) { // fd --> EPOLLIN
char *buffer = connlist[fd].rbuffer;
int idx = connlist[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
if (count == 0) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
connlist[fd].rlen += count;
#if 1 //echo: need to send
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen = connlist[fd].rlen;
connlist[fd].rlen -= connlist[fd].rlen;
#else
//http_request(&connlist[fd]);
//http_response(&connlist[fd]);
#endif
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd) {
char *buffer = connlist[fd].wbuffer;
int idx = connlist[fd].wlen;
int count = send(fd, buffer, idx, 0);
set_event(fd, EPOLLIN, 0);
return count;
}
int init_server(unsigned short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
return -1;
}
listen(sockfd, 10);
return sockfd;
}
// tcp
int main() {
int port_count = 20;
unsigned short port = 2048;
int i = 0;
epfd = epoll_create(1); // int size
for (i = 0;i < port_count;i ++) {
int sockfd = init_server(port + i); // 2048, 2049, 2050, 2051 ... 2057
connlist[sockfd].fd = sockfd;
connlist[sockfd].recv_t.accept_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
}
gettimeofday(&zvoice_king, NULL);
struct epoll_event events[1024] = {0};
while (1) { // mainloop();
int nready = epoll_wait(epfd, events, 1024, -1); //
int i = 0;
for (i = 0;i < nready;i ++) {
int connfd = events[i].data.fd;
if (events[i].events & EPOLLIN) { //
int count = connlist[connfd].recv_t.recv_callback(connfd);
//printf("recv count: %d <-- buffer: %s\n", count, connlist[connfd].rbuffer);
} else if (events[i].events & EPOLLOUT) {
// printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count = connlist[connfd].send_callback(connfd);
}
}
}
getchar();
//close(clientfd);
}
//mul_port_client_epoll.c
#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 20 //与creator开的端口数一致即可
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
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;
}
//ntySetReUseAddr(sockfd);
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;
}