【Linux入门环境编程】百万并发的服务器
【Linux入门环境编程】百万并发的服务器
1 百万并发的服务器实现
tcp_server实现百万并发
- 并发——一秒钟的请求数量(qps)
实现步骤:
-
准备4个虚拟机,最小要求:
- 至少一个4G内存,2核CPU,作为服务器——Server——128
- 另外三个2G内存,1核CPU,作为客户端——Client——129、130、131
-
服务器代码:tcp_server.c
- 注意变化:每新增一个连接,新增一个端口
//额外引入头文件
#include
-
客户端测试代码:mul_port_client_epoll.c
- 注意根据服务器能接受的端口数量,设置MAX_PORT的端口数量
//额外引入头文件
#include
#include
代码实现
tcp_server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_LENGTH 1024
#define EPOLL_SIZE 1024
#define MAX_PORT 100 // 最大可复用端口数量
void *client_routine(void *arg) {
int clientfd = *(int *)arg;
while (1) {
char buffer[BUFFER_LENGTH] = {0};
int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
if (len < 0) {
close(clientfd);
break;
} else if (len == 0) { // disconnect
close(clientfd);
break;
} else {
printf("Recv: %s, %d byte(s)
", buffer, len);
}
}
}
// 检查传入的文件描述符 fd 是否存在于一个文件描述符数组 fds 中
int islistenfd(int fd, int *fds) {
int i = 0;
for (i = 0;i < MAX_PORT;i ++) {
if (fd == *(fds+i)) return fd;
}
return 0;
}
// ./tcp_server
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Param Error
");
return -1;
}
int port = atoi(argv[1]); // start
int sockfds[MAX_PORT] = {0}; // listen fd
int epfd = epoll_create(1);
int i = 0;
for (i = 0;i < MAX_PORT;i ++) { // 每创建100个端口
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port+i); // 8888 8889 8890 8891 .... 8987
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("bind");
return 2;
}
if (listen(sockfd, 5) < 0) {
perror("listen");
return 3;
}
printf("tcp server listen on port : %d
", port + i);
//
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
sockfds[i] = sockfd;
}
//
#if 0
while (1) {
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
pthread_t thread_id;
pthread_create(&thread_id, NULL, client_routine, &clientfd);
}
#else
struct epoll_event events[EPOLL_SIZE] = {0};
while (1) {
int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5); // -1, 0, 5
if (nready == -1) continue;
int i = 0;
for (i = 0;i < nready;i ++) {
int sockfd = islistenfd(events[i].data.fd, sockfds);
if (sockfd) { // listen 2
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
fcntl(clientfd, F_SETFL, O_NONBLOCK);
int reuse = 1;
setsockopt(clientfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
} else {
int clientfd = events[i].data.fd;
char buffer[BUFFER_LENGTH] = {0};
int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
if (len < 0) {
close(clientfd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
} else if (len == 0) { // disconnect
close(clientfd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
} else {
printf("Recv: %s, %d byte(s), clientfd: %d
", buffer, len, clientfd);
}
}
}
}
#endif
return 0;
}
mul_port_client_epoll.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
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
", 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
");
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) {
printf("port, index : %d, %d
", port, index);
perror("connect");
goto err;
}
ntySetNonblock(sockfd);
ntySetReUseAddr(sockfd);
sprintf(buffer, "Hello Server: client --> %d
", 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
", 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
", 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
", rBuffer);
if (!strcmp(rBuffer, "quit")) {
isContinue = 0;
}
} else if (length == 0) {
printf(" Disconnect clientfd:%d
", clientfd);
connections --;
close(clientfd);
} else {
if (errno == EINTR) continue;
printf(" Error clientfd:%d, errno:%d
", clientfd, errno);
close(clientfd);
}
} else {
printf(" clientfd:%d, errno:%d
", clientfd, errno);
close(clientfd);
}
}
}
usleep(1 * 1000);
}
return 0;
err:
printf("error : %s
", strerror(errno));
return 0;
}
百万并发服务器出现问题:
connection refused问题:
sudo ./2_mul_port_client_epoll 192.168.21.128 80
connect: Connection refused
error : Connection refused
-
服务器不允许链接,可能原因1:文件系统默认每个进程的fd个数只有1024个
-
修改文件
/etc/security/limits.conf
,加入* soft nofile 1048576 * hard nofile 1048576
修改完成后重启
sudo reboot
-
也可以通过
ulimit -a
进行修改:sudo su sudo ulimit -n 1048576
-
出现问题:在 Ubuntu 20 中无法修改open files (-n)限制
-
查找原因:Ubuntu 20 可能使用了systemd的资源控制功能
-
进入
/etc/systemd/system.conf
检查是否有对LimitNOFILE
(文件描述符限制)的设置,发现#DefaultLimitNOFILE=1024:52428
找到原因,修改成:
#DefaultLimitNOFILE=1024:1048576
-
再次执行以上修改,修改成功!
-
-
request address问题:
-
客户端报错request address问题,在客户端连接数达到27000之前连接断开,可能问题:客户端端口地址耗尽
- 端口地址只有65535个,如何开启多个端口实现监听
- 解决方法:把服务器的监听端口增加到100个,针对不同的
connection timed out问题
-
客户端又报错connection timed out问题,在客户端连接数达到65000之前断开,可能问题: 文件系统达到最大值,防火墙连接达到最大值
-
检查文件系统最大值
fs.file-max
:cat /proc/sys/fs/file-max
服务器返回:9223372036854775807,三个客户端返回:397322
-
检查内核中防火墙对外连接最大值
nf_conntrack_max
cat /proc/sys/net/netfilter/nf_ //用tab查找你想要的信息
服务器返回:65535,三个客户端返回:65535
如果没有
nf_conntrack_max
可以加载以下命令:sudo modprobe ip_conntrack
内核中的参数设置都可以进入
/etc/sysctl.conf
sudo vim /etc/sysctl.conf
增加:
fs.file-max = 1048576 net.netfilter.nf_conntrack_max=1048576 //ubuntu20可用
命令生效:
sudo sysctl -p
如果出现报错:
sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack max: No such file or directory
可以加载以下命令:
sudo modprobe ip_conntrack
-
服务器内存崩溃,内存回收
-
跑到79w左右时,服务器内存崩溃,CPU利用率达到100%
-
在20w左右时,而且连接一开始时服务器内存就已经跑满,开始内存回收
-
注意CPU和内存不要超过80%
-
每次启动时都要重新执行
sudo sysctl -p
net.ipv4.tcp_mem = 252144 524288 786432 net.ipv4.tcp_wmem = 1024 1024 2048 net.ipv4.tcp_rmem = 1024 1024 2048
这是TCP协议栈的配置,其中:
tcp_mem = 252144 524288 786432
指TCP协议栈的总内存容量为1G 2G 3G
,即在内存12G之间时不做操作,在内存23G之间时减缓分配,在内存3G之上时停止分配;tcp_wmem = 1024 1024 2048
指socket发送缓冲区为1K 1K 2K
,分别是最小、默认、最大tcp_rmem = 1024 1024 2048
指socket接收缓冲区为1K 1K 2K
,分别是最小、默认、最大
服务器CPU崩溃,CPU使用率占满
- 一旦数据连接到80w(804453804125),CPU核心会全部突然占用100%(8个核心都不管用),然后进程崩溃
https://github.com/0voice