茉莉花新闻网

中華青年思想與行動的聚合地

一台主机上只能保持最多65535个TCP连接吗?

张彦飞的回答

几年前的我也产生过同样的困惑。

为了给自己解惑,我扒内核源码,做测试实验,写技术文章,从头到尾把这个问题扒了一遍。

要想把这个问题搞清楚,关键的地方在于要把TCP连接的两端里的客户端和服务端两个角色分开来讨论。 因为它两对端口号的使用方式不一样,区分开了能讨论的更清晰。

先抛出结论,无论是服务端还是客户端,单机支撑 100W 以上的连接都是没有问题的。

我在 4GB 的机器上都测试过的。如果内存更大,能支持的连接数会更多。咱们先从理论讲起来。

一、TCP 并发理论基础

1 服务器理论最大并发数

TCP连接四元组是由源IP地址、源端口、目的IP地址和目的端口构成。

当四元组中任意一个元素发生了改变,那么就代表的是一条完全不同的新连接

我们算下服务器上理论上能达成的最高并发数量。拿我们常用的 Nginx 举例,假设它的 IP 是 A,端口80。这样就只剩下源IP地址、源端口是可变的。

IP 地址是一个 32 位的整数,所以源 IP 最大有 2 的 32 次方这么多个。 端口是一个 16 位的整数,所以端口的数量就是 2 的 16 次方。

2 的 32 次方(ip数)× 2的 16 次方(port数)大约等于两百多万亿。

所以理论上,我们每个 server 可以接收的连接上限就是两百多万亿。(不过每条 TCP 连接都会消耗服务器内存,实践中绝不可能达到这个理论数字,稍后我们就能看到。)

2 客户端理论最大并发数

注意:这里的客户端是一个角色,并不具体指的是哪台机器。当你的 java/c/go 程序响应用户请求的时候,它是服务端。当它访问 redis/mysql 的时候,你这台机器就变成客户端角色了。这里假设我们一台机器只用来当客户端角色。

我们再算一下客户端的最大并发数的上限。

很多同学认为一台 Linux 客户端最多只能发起 64 k 条 TCP 连接。因为TCP 协议规定的端口数量有 65535 个,但是一般的系统里 1024 以下的端口都是保留的,所以没法用。可用的大约就是 64 k 个。

但实际上客户端可以发出的连接远远不止这个数。咱们看看以下两种情况

情况1: 这个 64 k 的端口号实际上说的是一个 ip 下的可用端口号数量。而一台 Linux 机器上是可以配置多个 IP 的。假如配置了 20 个 IP,那这样一台客户端机就可以发起 120 万多个 TCP 连接了。

情况2: 再退一步讲,假定一台 Linux 上确实只有一个 IP,那它就只能发起 64 k 条连接了吗? 其实也不是的。

根据四元组的理论,只要服务器的 IP 或者端口不一样,即使客户端的 IP 和端口是一样的。这个四元组也是属于一条完全不同的新连接。

比如下面的两条连接里,虽然客户端的 IP 和端口完全一样,但由于服务器侧的端口不同,所以仍然是两条不同的连接。

  • 连接1:客户端IP 10000 服务器IP 10000
  • 连接2:客户端IP 10000 服务器IP 20000

所以一台客户端机器理论并发最大数是一个比服务器的两百万亿更大的一个天文数字(因为四元组里每一个元素都能变)。这里就不展开计算了,因为已经没有意义了。

3 Linux 最大文件描述符限制

linux 下一切皆文件,包括 socket。所以每当进程打开一个 socket 时候,内核实际上都会创建包括 file 在内的几个内核对象。该进程如果打开了两个 socket,那么它的内核对象结构如下图。

进程打开文件时消耗内核对象,换一句直白的话就是打开文件对象吃内存。所以linux系统出于安全角度的考虑,在多个位置都限制了可打开的文件描述符的数量,包括系统级、进程级、用户进程级。

  • fs.file-max: 当前系统可打开的最大数量
  • fs.nr_open: 当前系统单个进程可打开的最大数量
  • nofile: 每个用户的进程可打开的最大数量

本文的实验要涉及对以上参数的修改。

4 TCP 连接的内存开销

介绍内存开销之前,需要先理解内核的内存使用方式。只有理解了这个,才能深刻理解 TCP 连接的内存开销。

Linux 内核和应用程序使用的是完全不同的两套机制。 Linux 给它的内核对象分配使用 SLAB 的方式。

一个 slab 一般由一个或者多个 Page 组成(每个 Page 一般为 4 KB)。在一个 slab 内只分配特定大小、甚至是特定的对象。这样当一个对象释放内存后,另一个同类对象可以直接使用这块内存。通过这种办法极大地降低了碎片发生的几率。

v2 f38fa9e2ae302ccd7d7a832c86ec52b0 720w

Linux 提供了 slabtop 命令来按照占用内存从大往小进行排列,这对我们查看内核对象的内存开销非常方便。

在 Linux 3.10.0 版本中,创建一个socket 需要消耗 densty、flip、sock_inode_cache、TCP 四个内核对象。这些对象加起来总共需要消耗大约 3 KB 多一点的内存。

如果连接上有数据收发的话,还需要消耗发送、接收缓存区。这两个缓存区占用内存影响因素比较多,既受收发数据的大小,也受 tcp_rmem、tcp_wmem 等内核参数,还取决于服务器进程能否及时接收(及时接收的话缓存区就能回收)。总之影响因素比较多,不同业务之间实际情况差别太大,比较复杂。所以不在本文讨论范围之内。

二、百万连接达成实验

了解了理论基础后,其实你的疑惑就得到基本的解释了。剩下的事情你可以把我的这本电子书下载回去慢慢看。

v2 eca75615ee22c55049da19563414f9c2 1440w

下载地址在这里:

飞哥的《理解了实现再谈网络性能》电子书发布啦!

咱们来接着继续来看实验过程。

本实验需要准备两台机器。一台作为客户端,另一台作为服务器。如果你选用的是 c 或者 php 源码,这两台机器内存只要大于 4GB 就可以。 如果使用的是 Java 源码,内存要大于 6 GB。对 cpu 配置无要求,哪怕只有 1 个核都够用。

本方案中采用的方法是在一台客户端机器上配置多个 ip 的方式来发起所有的 tcp 连接请求。所以需要为你的客户端准备 20 个 IP,而且要确保这些 IP 在内网环境中没有被其它机器使用。如果实在选不出这些 IP,那么可以采用百看不如一练,动手测试单机百万连接的保姆级教程! 一文中的方案二。

除了用 20 个 IP 以外,也可以使用 20 台客户端。每个客户端发起 5 万个连接同时来连接这一个server。但是这个方法实际操作起来太困难了。

客户端机和服务器分别下载源码:

github.com/yanfeizhang/

下面我们来详细看每一个实验步骤。

1 调整客户端可用端口范围

默认情况下,Linux 只开启了 3 万多个可用端口。但我们今天的实验里,客户端一个进程要达到 5 万的并发。所以,端口范围的内核参数需要修改。

#vi /etc/sysctl.conf
net.ipv4.ip_local_port_range = 5000 65000

执行 sysctl -p 使之生效。

2 调整客户端最大可打开文件数

我们要测试百万并发,所以客户端的系统级参数 fs.file-max 需要加大到 100 万。另外 Linux 上还会存在一些其它的进程要使用文件,所以我们需要多打一些余量出来,直接设置到 110 万。

对于进程级参数 fs.nr_open 来说,因为我们开启 20 个进程来测,所以它设置到 60000 就够了。这些都在 /etc/sysctl.conf 中修改。

#vi /etc/sysctl.conf
fs.file-max=1100000 
fs.nr_open=60000  

sysctl -p 使得设置生效。并使用 sysctl -a 查看是否真正 work。

#sysctl -p
#sysctl -a
fs.file-max = 1100000
fs.nr_open = 60000

接着再加大用户进程的最大可打开文件数量限制(nofile)。这两个是用户进程级的,可以按不同的用户来区分配置。 这里为了简单,就直接配置成所有用户 * 了。每个进程最大开到 5 万个文件数就够了。同样预留一点余地,所以设置成 55000。 这些是在 /etc/security/limits.conf 文件中修改。

注意 hard nofile 一定要比 fs.nr_open 要小,否则可能导致用户无法登陆。

# vi /etc/security/limits.conf
*  soft  nofile  55000  
*  hard  nofile  55000

配置完后,开个新控制台即可生效。 使用 ulimit 命令校验是否生效成功。

#ulimit -n
55000

3 服务器最大可打开文件句柄调整

服务器系统级参数 fs.file-max 也直接设置成 110 万。 另外由于这个方案中服务器是用单进程来接收客户端所有的连接的,所以进程级参数 fs.nr_open, 也一起改成 110 万。

#vi /etc/sysctl.conf
fs.file-max=1100000 
fs.nr_open=1100000  

sysctl -p 使得设置生效。并使用 sysctl -a 验证是否真正生效。

接着再加大用户进程的最大可打开文件数量限制(nofile),也需要设置到 100 万以上。

# vi /etc/security/limits.conf
*  soft  nofile  1010000  
*  hard  nofile  1010000

配置完后,开个新控制台即可生效。 使用 ulimit 命令校验是否成功生效。

4 为客户端配置额外 20 个 IP

假设可用的 ip 分别是 CIP1,CIP2,......,CIP20,你也知道你的子网掩码。

注意:这 20 个 ip 必须不能和局域网的其它机器冲突,否则会影响这些机器的正常网络包的收发。

在客户端机器上下载的源码目录 test02 中,找到你喜欢用的语言,进入到目录中找到 tool.sh。修改该 shell 文件,把 IPS 和 NETMASK 都改成你真正要用的。

为了确保局域网内没有这些 ip,最好先执行代码中提供的一个小工具来验证一下

make ping

当所有的 ip 的 ping 结果均为 false 时,进行下一步真正配置 ip 并启动网卡。

make ifup

使用 ifconfig 命令查看 ip 是否配置成功。

#ifconfig
eth0
eth0:0
eth0:1
...
eth:19

5 开始实验

ip 配置完成后,可以开始实验了。

在服务端中的 tool.sh 中可以设置服务器监听的端口,默认是 8090。启动 server

make run-srv

使用 netstat 命令确保 server 监听成功。

netstat -nlt | grep 8090
tcp  0   0.0.0.0:8090  0.0.0.0:*  LISTEN

在客户端的 tool.sh 中设置好服务器的 ip 和端口。然后开始连接

make run-cli

同时,另启一个控制台。使用 watch 命令来实时观测 ESTABLISH 状态连接的数量。

实验过程中不会一帆风顺,可能会有各种意外情况发生。 这个实验我前前后后至少花了有一周时间,所以你也不要第一次不成功就气馁。 遇到问题根据错误提示看下是哪里不对。然后调整一下,重新做就是了。 重做的时候需要重启客户端和服务器。

对于客户端,杀掉所有的客户端进程的方式是

make stop-cli

对于服务器来说由于是单进程的,所以直接 ctrl + c 就可以终止服务器进程了。 如果重启发现端口被占用,那是因为操作系统还没有回收,等一会儿再启动 server 就行。

当你发现连接数量超过 100 万的时候,你的实验就成功了。

watch "ss -ant | grep ESTABLISH"
1000013

这个时候别忘了查看一下你的服务端、客户端的内存开销。

先用 cat proc/meminfo 查看,重点看 slab 内存开销。

$ cat /proc/meminfo
MemTotal:        3922956 kB
MemFree:           96652 kB
MemAvailable:       6448 kB
Buffers:           44396 kB
......
Slab:          3241244KB kB

再用 slabtop 查看一下内核都是分配了哪些内核对象,它们每个的大小各自是多少。

v2 5807cabe7e99dacf4b717c8084915624 1440w

如果发现你的内核对象和上图不同,也不用惊慌。因为不同版本的 Linux 内核使用的内核对象名称和数量可能会有些许差异。

6 结束实验

实验结束的时候,服务器进程直接 ctrl + c 取消运行就可以。客户端由于是多进程的,可能需要手工关闭一下。

make stop-cli

最后取消为实验临时配置的新 ip

make ifdown

最后

上面实验只用了一种方法,还有另外一种方法,参见百看不如一练,动手测试单机百万连接的保姆级教程! 一文中的方案二。

总结下,一台主机上的 TCP 连接数并不会受端口号 65535 的限制,我们有很多的办法绕开。最终限制最大 TCP 连接数的资源是机器上的内存

我们上述的实验只涉及了连接本身的内存开销,如果连接上有数据收发你们还需要消耗接收缓存区、发送缓存区内存开销。这个开销受实际收发速度、内核参数配置大小的影响,情况会比较复杂。

不过我用收发 “Hello world” 之类的短消息也简单测试了一下。

v2 cd1d97736b1047428db62ad904943009 1440w

下面这些文章是我之前发表的几篇相关的文章。

我把我在网络方面的研究整理成了本电子书叫《理解了实现再谈网络性能》。深度挖了一下网络在 Linux 上是怎么实现的,进而帮助大家深刻理解网络再 CPU、内存等方面的开销,相信对于你一定有帮助。

飞哥的《理解了实现再谈网络性能》电子书发布啦!

Github: github.com/yanfeizhang/

同类信息

查看全部

茉莉花论坛作为一个开放社区,允许您发表任何符合社区规定的文章和评论。

茉莉花新闻网

        中国茉莉花革命网始创于2011年2月20日,受阿拉伯之春的感召,大家共同组织、发起了中国茉莉花革命。后由数名义工无偿坚持至今,并发展成为广受翻墙网民欢迎的新闻聚合网站并提供论坛服务。

新闻汇总

邮件订阅

输入您的邮件地址:

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram