C语言TCP服务器百万并发调参优化
文章目录
背景
结合之前完成的C语言TCP服务端程序,其能够接受的最大并发数较低,故我们希望进行一些编码上或者是系统参数上的改变来实现提高TCP服务器的最大并发数
注:这里说的最大并发数(或称最大并发量),**是指客户端的数量(**即通信socket的数量)
而不是每秒请求的数量qps,注意区别!
本实验最后的结果也并未达到百万并发,只接近80w,故本博客重点在于调优的参数和代码的修改有哪些。
实验准备
准备四台虚拟机 版本皆为 20.04
服务器:4g内存,双核
三台客户端:2g内存,单核
若想要修改Ubuntu静态IP地址可参考:https://blog.csdn.net/baidu_39332177/article/details/123131601
优化调参
出现Connection refused错误
描述
当建立1024个socket的clientfd之后,别的客户端再想连接就出现Connection refused错误
问题原因
通过ulimit -a
查看服务器端默认的open files
参数数量只有1024个
解决办法
修改open files
数量,方法如下
- 方法一:临时修改,输入
sudo ulimit -n 1048576
,但是重启后又恢复原来设置值 - 方法二:永久修改,输入
sudo vim /etc/security/limits.conf
然后在文件结尾位置添加相关设置
hard
指警告的设定,可以超过这个值,但是超过后有警告
soft
指严格的设定,不允许超过这个值
客户端连接服务器时候产生Too many open files错误
描述
原因和解决办法
由于我们只在服务器端修改了open files的数量,必须要在客户端也将open files设置为1048576
出现Cannot assign requested address错误
描述
原因分析
首先,问题是不能分配客户端地址还是服务器地址?sockfd和网络地址之间有什么关系?
根据socket可以找到一个五元组 socket–>(远程IP,远程端口,本机IP,本机端口,协议),socket和五元组是一对一关系,通过这个五元组可以通过recv
和send
函数来收发数据。
所以该问题原因就是五元组的组合被耗尽,服务器的本机IP以及远程IP和远程端口号已经确定,所以唯一能够利用的就是增添本机端口数量来增加socket的数量。
注意:本机端口数量最大只能有65535
解决办法
使用100个端口号与服务器地址和socket的绑定,得到100个监听的fd,并将其上epoll树,由epoll来监控事件到来,关键代码的修改如下:
int epfd = epoll_create(1); // 1.创建一个监视事件的管理员fd
int sockfds[MAX_PORT] = {0}; // listen fd集合
int i = 0;
//开MAX_PORT个端口进行监听
for(i = 0;i < MAX_PORT; ++i)
{
//创建监听的文件描述符,相当于酒店门口迎宾的人员
//为客户提供服务由其他服务员来做
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....8987
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
{
perror("bind");
return -2;
}
//利用sockfd进行监听
//第二个参数指定能处理的最大连接请求
if(listen(sockfd, 5) < 0)
{
perror("listen");
return -3;
}
printf("tcp server listen on port: %d\n", port + i);
struct epoll_event ev;
//有数据到来socketfd,表示可读了,则会触发EPOLLIN
//对端(客户端)读取了一些数据走,则表示可以往这个socketfd写了,则会触发EPOLLOUT
ev.events = EPOLLIN;
ev.data.fd = sockfd;
//EPOLL_CTL_ADD:在epoll的监视列表中添加一个文件描述符(即参数fd),指定监视的事件类型(参数event)
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
sockfds[i] = sockfd;
}
Connection timed out 错误
描述
这个问题在ubuntu20.04上不存在,但还是记录一下。
原因与解决方案
首先查看file-max
参数值是否不足100w
file-max : sets the maximum number of file-handles that the Linux kernel will allocate.
jyhlinux@ubuntu:~$ cat /proc/sys/fs/file-max
9223372036854775807
已经大于100w,故接着检查nf_conntrack_max
(设置连接跟踪的最大值)
jyhlinux@ubuntu:~$ sudo modprobe ip_conntrack #这句用来启动ip_conntrack,不加则下面语句失败
jyhlinux@ubuntu:~$ cat /proc/sys/net/netfilter/nf_conntrack_max
262144
这里我们可以看出连接跟踪最大值只有262144,没有到100w,所以解决方法就是将该参数设置为100w之上
sudo vim /etc/sysctl.conf
将net.nf_conntrack_max = 1048576
添加进配置文件
然后输入下方语句生效配置
sudo sysctl -p
Cannot open /proc/meminfo:Too many open files in system file-max
遇到该问题说明file-max
不够大,在etc/sysctl.conf
修改参数即可
jyhlinux@ubuntu:~$ sudo modprobe ip_conntrack #这句用来启动ip_conntrack,不加则下面语句失败
jyhlinux@ubuntu:~$ sudo vim /etc/sysctl.conf
添加下面的语句
然后输入下面的语句生效配置
sudo sysctl -p
内存回收设置的调优
以下配置代码都是添加到etc/sysctl.conf
文件中,注意开始要加上sudo modprobe ip_conntrack
-
第一个参数:tcp协议栈内存
net.ipv4.tcp_mem = 252144 524288 786432
tcp_mem(3个INTEGER变量):low, pressure, high
low:当TCP使用了低于该值的内存页面数时,TCP不会考虑释放内存。
pressure:当TCP使用了超过该值的内存页面数量时,TCP试图稳定其内存使用,进入pressure模式,当内存消耗低于low值时则退出pressure状态。
high:允许所有tcp sockets用于排队缓冲数据报的页面量,当内存占用超过此值,系统拒绝分配socket,后台日志输出“TCP: too many of orphaned sockets” -
第二个参数:tcp发送缓冲区
net.ipv4.tcp_wmem = 1024 1024 2048
表示tcp连接(socket)的发送(write)缓存区的最小、默认、最大值,分别为1KB ,1KB,2KB
-
第三个参数:tcp接收缓冲区
net.ipv4_tcp_rmem = 1024, 1024, 2048
表示tcp连接(socket)的接受(recive)缓存区的最小、默认、最大值,分别为1KB ,1KB,2KB
这样设置之后,默认的1个socket的file descriptor大小为2KB(rmem + wmem),百万个fd就大概是2g内存。
杂项
- 最后测试的时候,我这个配置也只能最多到82万左右,不知道什么原因,也加大了客户端和服务端的内存
- 大量客户机断开连接宕机时候,处理器使用率会飙升
- 内存和CPU使用率最好维持在80%一下
注意
本实验主要目的是了解百万并发所需要的优化,故代码就不详加给出了。如果面试时候,如果别人问起这个服务器调优,就和面试官说清楚解决了哪几个问题,不要没有达到100w就唯唯诺诺,不敢回答。