因为我们没有大量的测试机器,所以只能在单机上模拟大量客户端去连接服务器。
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull 更新一下。
1. 客户端与服务器程序
- 客户端
本文所使用的客户端程序路径是:
unp/program/concurrent_test/ccrt
ccrt 程序可以一次发起大量连接请求,主要通过参数 -n 指定。比如:
$ ./ccrt -h sun -p 8000 -n 40000
上面的命令表示发起 40000 个连接请求去连接 sun 主机上的服务器。ccrt 程序每次连接成功或失败后,会在屏幕打印如下字样:
sockfd = xxxx, port = xxxx, client xxxx connecting success! // 或者 sockfd = xxxx, port = xxxx, client xxxx connecting failed!
sockfd 表示客户端的套接字,port 表示系统分配的端口号。
- 服务器程序使用的是上一篇文章我们修复了僵尸进程的多并发服务器,它的路径是:
unp/program/echo/processzombie
2. 实验
2.1 实验一:测试单机环境多进程并发量
- 在 flower 主机上启动服务器
[flower] $ ./echo -s -h flower
- 在 sun 主机启动客户端,发起 40000 个连接请求
[sun] $ su root // 切换到 root 用户 [sun] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数 [sun] $ ./ccrt -h flower -n 40000
图1 sun 主机向 flower 发起 40000 个连接请求,在第 3618个请求上失败
很不幸的是,服务器在第 3618 次 fork 的时候产生了错误,提示资源暂时不可用(Resource temporarily unavailable). 毕竟想让单机 fork 40000 个进程出来实在是太难为它了,这中间有一次测试直接让操作系统崩溃了……
可见,多进程并发服务器因为受系统进程数量的限制,并发量也就在几千左右。
接下来,在客户端按下 CTRL D 释放掉连接吧,不然现在服务器已经卡的没法使用了。
2.2 测试单机一次最多能接收多少个连接
ccrt 程序同时也可以扮演服务器的角色。当 ccrt 充当服务器的时候,它循环的 accept,但是并不处理客户端发来的数据。伪代码类似下面这样:
while(1) { sockfd = accept(listenfd); }
我们要做的就是统计这样的服务器能承受多大的压力。
- 在 flower 主机上启动服务器
[flower] $ ./ccrt -s -h flower
- 在 sun 主机启动客户端,发起 40000 个连接请求
[sun] $ su root // 切换到 root 用户 [sun] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数 [sun] $ ./ccrt -h flower -n 40000
图2 sun 主机向 flower 发起 40000 个连接,客户机端口耗尽
图1 左上侧是 sun 主机上的客户端,右上侧是 flower 主机上的服务器,我们看到,flower 主机上的多进程服务器经受住了考验!可是不幸的是,客户端在发起第 28232 个连接的时候失败了!!!因为系统的端口资源被耗尽(图 2 中显示最后一次分配的端口是 0!),要知道,操作系统一共只准备了 65536 个端口,其中有大量端口我们都是无法使用的,当可用端口被用完的时候,也正是 ccrt 结束生命的时候。
另一方面,这个 28232 并不是随便就得出来的,我们可以查看 /proc/sys/net/ipv4/ip_local_port_range
这个文件里的数据,内容为
32768 60999
它表示系统可自动分配的端口范围,刚好是 28232 个。
好了,先不要关掉 sun 主机上的 ccrt 程序,虽然打印了 done,但是它并没有关闭 ESTABLISHED 状态的连接。
- 接下来,在另一台主机 moon 上启动 ccrt,继续向 flower 主机发起 40000 个连接。
[moon] $ su root // 切换到 root 用户 [moon] $ ulimit -n 65535 // 修改单进程最大可打开的文件描述符数 [moon] $ ./ccrt -h flower -n 40000
图3 moon 主机上的客户端在发起第 2991 次连接时失败
重点看图 3 底部的数据包,moon 主机在第 2991 次发起连接请求的时候,发送了 SYN 段给 flower 主机,但是都没有得到 flower 主机的回应!!!重传了 6 次 SYN 报文后,客户端放弃,connect 函数返回,并报告超时。
算上图 2 和图 3 中所有成功的连接,一共是 28232 + 2991 = 31223 次连接,也就是在纯粹的只 accept 的情况下,flower 主机并发量达到了 31223 次,约 3 万次左右。另一方面,你得知道,我的 flower 主机只是一个虚拟机,如果换成真实主机,配置再好点的话,10 W+ 应该不成问题。
知乎上看到有人实现了单机 300W 并发量,玩的很溜^_^. 传送门
注意:服务器不会有端口耗尽的情况,因为本机套接字地址始终都是固定的,变化的只是远端套接字地址,tcp 连接是一个四元组 {local ip:port <-> foreigh ip:port}。
3. 总结
在上面的实验中,我们有幸观察到了:
- fork 返回错误(这真的很少见,
errno = EAGAIN
) - 端口耗尽(这也不太常见,
errno = EADDRNOTAVAIL
) - 单机服务器并发数影响因素
- 进程打开描述符的描述数限制
- ip 地址个数
232 × 端口号个数 65536 (理论最大值) - 机器本身所能承受的极限,比如内存的限制
- 服务器架构,比如多进程服务器多线程服务器会受进程个数,线程个数的限制
- 其它因素
最后,我希望看到这篇文章的同学,也能在你的机器上测试一下,大家把数据贴出来看看,共享一下。
欢迎入群讨论:610441700,加群记得注明来意,否则会被拒绝。