最多能创建多少个TCP连接?

本文通过一个生动的故事讲述了Linux服务器上创建TCP连接的限制,包括端口号范围、文件描述符限制、线程资源及内存占用,强调了在并发编程中理解系统资源限制的重要性,并介绍了如何通过IO多路复用等技术优化资源使用。

低并发编程

战略上藐视技术,战术上重视技术

本文坚持看到结尾才有动图

气不气?

我是一个 Linux 服务器上的进程,名叫小进。

老是有人说我最多只能创建 65535 个 TCP 连接。

我不信这个邪,今天我要亲自去实践一下。

我走到操作系统老大的跟前,说:

"老操,我要建立一个 TCP 连接!"

老操不慌不忙,拿出一个表格递给我,"小进,先填表吧"

我一看这个表,这不就是经典的 socket 四元组嘛。我只有一块网卡,其 IP 地址是 123.126.45.68,我想要与 110.242.68.3 的 80 端口建立一个 TCP 连接,我将这些信息填写在了表中。

源端口号填什么呢?我记得端口号是 16 位的,可以有 0 ~ 65535 这个范围的数字,那我随便选一个吧!

正当我犹豫到底选什么数字的时候,老操一把抢过我的表格。

"你墨迹个啥呢小进?源端口号不用你填,我会给你分配一个可用的数字。源IP也不用你填,我知道都有哪些网卡,并且会帮你选个合适的。真是个新手,回去等消息吧。"

"哦"

老操带着我的表格,走了。

过了很长时间,老操终于回来了,并且带着一个纸条。

"小进,你把这个收好了。"

我问道,"这是啥呀?"

老操不耐烦地说道,"刚刚说你是新手你还不服,这个 5 表示文件描述符,linux 下一切皆文件,你待会和你那个目标 IP 进行 TCP 通信的时候,就对着这个文件描述符读写就好啦。"

"这么方便!好的,谢谢老操。"

我拿着这个文件描述符,把它放到属于我的内存中裱起来了,反正我只是想看看最多能创建多少 TCP 连接,又不是去真的用它,嘻嘻。

 

端口号

 

过了一分钟,我又去找老操了。

"老操,我要建立一个 TCP 连接!"

老操不慌不忙,拿出一个表格递给我,"小进,先填表吧"

这回我熟悉了,只把目标IP和目标端口填好。

老操办好事之后,又带着一个纸条回来,上面写着数字"6"。

就这样,我每隔一分钟都去找老操建立一个新的 TCP 连接,目标 IP 都是110.242.68.3,目标端口都是 80。

老操也很奇怪,不知道我在这折腾啥,他虽然权力大,但无权拒绝我的指令,每次都兢兢业业地把事情办好,并给我一张一张写着文件描述符的纸条。

直到有一次,我收到的纸条有些不同。

我带着些许责怪的语气问,"老操,这是怎么回事呀?"

老操也没好气地说,"这表示端口号不够用啦!早就觉得你小子不对劲了,一个劲地对着同一个 IP 和端口创建 TCP 连接,之前没办法必须执行你给的指令,现在不行了,端口号不够用了,源端口那里我没法给你填了。"

我也不是那么好骗的,质疑道。"老操,你也别欺负我这个新手,我可是知道端口号是 16 位的,范围是 1~65535,一共可以创建 65535 个 TCP 连接,我现在才创建了 63977 个,怎么就不够了!"

老操鄙视地看了我一眼,"你小子可真是闲的蛋疼啊,还真一个个数,来我告诉你吧,Linux 对可使用的端口范围是有具体限制的,具体可以用如下命令查看。"

[root]# cat /proc/sys/net/ipv4/ip_local_port_range 
1024 65000

"看到没,当前的限制是1024~65000,所以你就只能有63977个端口号可以使用。"

我赶紧像老操道歉,"哎哟真是抱歉,还是我见识太少,那这个数可以修改么?"

老操也没跟我一般见识,还是耐心地回答我,"可以的,具体可以 vim /etc/sysctl.conf 这个文件进行修改,我们在这个文件里添加一行记录"

net.ipv4.ip_local_port_range = 60000 60009

"保存好后执行 sysctl -p /etc/sysctl.conf 使其生效。这样你就只有 10 个端口号可以用了,就会更快报出端口号不够用的错误"

"原来如此,谢谢老操又给我上了一课。"

哎不对,建立一个 TCP 连接,需要将通信两端的套接字(socket)进行绑定,如下:

源 IP 地址:源端口号 <---->  目标 IP 地址:目标端口号

只要这套绑定关系构成的四元组不重复即可,刚刚端口号不够用了,是因为我一直对同一个目标IP和端口建立连接,那我换一个目标端口号试试。

我又把这个表交给老操,老操一眼就看破了我的小心思,可是也没办法,马上去给我建立了一个新的TCP连接,并且成功返回给我一个新的文件描述符纸条。

看来成功了,只要源端口号不用够用了,就不断变换目标 IP 和目标端口号,保证四元组不重复,我就能创建好多好多 TCP 连接啦!

这也证明了有人说最多只能创建 65535 个TCP连接是多么荒唐。

 

文件描述符

 

找到了突破端口号限制的办法,我不断找老操建立TCP连接,老操也拿我没有办法。

直到有一次,我又收到了一张特殊的纸条,上面写的不是文件描述符。

我又没好气地问老操,"这又是咋回事?"

老操幸灾乐祸地告诉我,"呵呵,你小子以为突破端口号限制就无法无天了?现在文件描述符不够用啦!"

"怎么啥啥都有限制啊?你们操作系统给我们的限制也太多了吧?"

"废话,你看看你都建了多少个TCP连接了!每建立一个TCP连接,我就得分配给你一个文件描述符,linux 对可打开的文件描述符的数量分别作了三个方面的限制。"

系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看

用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看

进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看

天呢,真是人在屋檐下呀,我赶紧看了看这些具体的限制。

[root ~]# cat /proc/sys/fs/file-max
100000
[root ~]# cat /proc/sys/fs/nr_open
100000
[root ~]# cat /etc/security/limits.conf
...
* soft nproc 100000
* hard nproc 100000

原来如此,我记得刚刚收到的最后一张纸条是。

再之后就收到文件描述符不够的错误了。

我又请教老操,"老操,那这个限制可以修改么?"

老操仍然耐心地告诉我,"当然可以,比如你想修改单个进程可打开的最大文件描述符限制为100,可以这样。"

echo 100 > /proc/sys/fs/nr_open

"原来如此,我这就去把各种文件描述符限制都改大一点,也不多,就在后面加个0吧"

"额,早知道不告诉你小子了。"老操再次用鄙视的眼睛看着我。

 

线程

 

突破了文件描述符限制,我又开始肆无忌惮地创建起了TCP连接。

但我发现,老操的办事效率越来越慢,建立一个TCP连接花的时间越来越久。

有一次,我忍不住责问老操,"你是不是在偷懒啊?之前找你建一个TCP连接就花不到一分钟时间,你看看最近我哪次不是等一个多小时你才搞好?"

老操也忍不住了,"小进啊你还好意思说我,你知不知道你每建一个TCP连接都需要消耗一个线程来为你服务?现在我和CPU老大那里都忙得不可开交了,一直在为你这好几十万个线程不停地进行上下文切换,我们精力有限啊,自然就没法像以前那么快为你服务了。"

听完老操的抱怨,我想起了之前似乎有人跟我说过 C10K 问题,就是当服务器连接数达到 1 万且每个连接都需要消耗一个线程资源时,操作系统就会不停地忙于线程的上下文切换,最终导致系统崩溃,这可不是闹着玩的。

我赶紧像操作系统老大请教,"老操,实在不好意思,一直以为你强大无比,没想到也有忙得不可开交的时候呀,那我们现在应该怎么办呀?"

老操无奈地说,"我劝你还是别再继续玩了,没什么意义,不过我想你也不会听我的,那我跟你说两句吧。"

你现在这种每建一个TCP连接就创建一个线程的方式,是最传统的多线程并发模型,早期的操作系统也只支持这种方式。但现在我进化了,我还支持 IO 多路复用的方式,简单说就是一个线程可以管理多个 TCP 连接的资源,这样你就可以用少量的线程来管理大量的 TCP 连接了。

我一脸疑惑,"啥是 IO 多路复用啊?"。

老操一脸鄙视,"你这... 你去看看闪客的《你管这破玩意叫 IO 多路复用》,就明白了。"

这次真是大开眼界了,我赶紧把代码改成了这种 IO 多路复用的模型,将原来的 TCP 连接销毁掉,改成同一个线程管理多个 TCP 连接,很快,操作系统老大就恢复了以往的办事效率,同时我的 TCP 连接数又多了起来。

 

内存

 

突破了端口号、文件描述符、线程数等重重限制的我,再次肆无忌惮地创建起了TCP连接。

直到有一次,我又收到了一张红牌。

嗨,又是啥东西限制了呀,改了不就完了。我不耐烦地问老操,"这回又是啥毛病?"

老操说道。"这个错误叫内存溢出,每个TCP连接本身,以及这个连接所用到的缓冲区,都是需要占用一定内存的,现在内存已经被你占满了,不够用了,所以报了这个错。"

我看这次老操特别耐心,也没多说什么,但想着被内存限制住了,有点不太开心,于是我让老操帮我最后一个忙。

"老操呀,帮小进我最后一个忙吧,你权利大,你看看把那些特别占内存的进程给杀掉,给我腾出点地方,我今天要完成我的梦想,看看TCP连接数到底能创建多少个!"

老操见我真的是够拼的,便答应了我,杀死了好多进程,我很是感动。

 

CPU

 

有了老操为我争取的内存资源,我又开始日以继日地创建TCP连接。

老操也不再说什么,同样日以继日地执行着我的指令。

有一次,老操语重心长地对我说,"差不多了,我劝你就此收手吧,现在 CPU 的占用率已经快到 100% 了。"

我觉得老操这人真的可笑,经过这几次的小挫折,我明白了只要思想不滑坡,方法总比苦难多,老操这人就是太谨慎了,我岂能半途而废,不管他。

我仍然继续创建着 TCP 连接。

直到有一天,老操把我请到一个小饭馆,一块吃了顿饭,吃好后说道。"咱哥俩也算是配合了很久啦,今天我是来跟你道个别的。"

我很不解地问,"怎么了老操,发生什么事了?。"

老操说,"由于你的 TCP 连接,CPU 占用率已经很长时间维持在 100%,我们的使用者,也就是我们的上帝,几乎什么事情都做不了了,连鼠标动一下都要等好久,所以他给我下达了一个重启的指令,我执行这个指令后,你,以及像你一样的所有进程,包括我这个操作系统本身,一切都就消失了。"

我大惊失色,"啊,这么突然么?这条指令什么时候执行?"

老操缓缓起身,"就现在了,刚刚这条指令还没得到 CPU 运行的机会,不过现在到了。"

突然,我眼前一黑,一切都没了。

 

总结

 

资源

一台Linux服务器的资源

一个TCP连接占用的资源

占满了会发生什么

CPU

看你花多少钱买的

看你用它干嘛

电脑卡死

内存

看你花多少钱买的

取决于缓冲区大小

OOM

临时端口号

ip_local_port_range

1

cannot assign requested address

文件描述符

fs.file-max

1

too many open files

进程\线程数

ulimit -n

看IO模型

系统崩溃

后记

其实这个问题,我觉得结论不重要,最重要的是思考过程。

而思考过程其实相当简单,就是,寻找限制条件而已,其实一开始这篇文章,我写了个故事在开头,但后来感觉放在后记更合适。故事是这样的。

闪客:小宇,我问你,你一天最多能吃多少个汉堡?

小宇:额,你这问的太隐私了吧,不过看在你教我技术的份上,我就告诉你,最多能吃 4 个左右吧。

闪客:咳咳真的么?好吧,那你一分钟最多能吃多少个汉堡?

小宇:快的话可能 2 个,不过正常应该最多就能吃完 1 个了。

闪客:好的,那我问你,刚刚这两个问题你为什么能不假思索地回答出来呢?

小宇:哈哈你这是什么话,我自己我当然了解了。

闪客:不,你仔细想想你回答这两个问题的逻辑。

小宇:哦我明白你的意思了,当你问我一天最多能吃多少个汉堡时,我考虑的是我的胃的容量最多能容下多少个汉堡。而当你问我一分钟最多能吃多少个汉堡时,我考虑的时我吃汉堡的速度,按照这个速度在一分钟内能吃多少。

闪客:没错,你总结得很好!一天最多吃多少个汉堡,此时时间非常充裕,所以主要是胃的容量限制了这个汉堡最大值,计算公式应该是:

最多汉堡数 = 胃的容量 ÷ 汉堡的体积

而一分钟最多吃多少个汉堡,此时胃的容量非常充裕,限制汉堡最大值的是时间因素,计算公式是:

最多汉堡数 = 一分钟 ÷ 吃一个汉堡的耗时

所以,取决于最先触达的那个限制条件。

而最大 TCP 连接数这个问题,假如面试被问到了,即使你完全不会,也应该有这样的思路。

而如果你有了这样的思路,你多多少少都能回答出让面试官满意的答案,因为计算机很多时候,更看重思路,而不是细枝末节。

战略上藐视技术,战术上重视技术

<think>好的,我现在需要回答用户关于使用Rust创建TCP连接时一个端口最多可以连接多少设备的问题。首先,我需要回顾一下TCP连接的基本原理,特别是关于端口和连接的限制。 用户之前已经询问过TCP和UDP的选择问题,现在转向Rust的具体实现,特别是端口连接数的限制。我需要确定问题的核心:一个端口能处理多少并发连接。可能用户误解了“端口”在这里的角色,因为通常来说,一个服务器端口可以接受多个客户端的连接,但具体数量受多种因素影响。 首先,我应该明确TCP连接的四元组:源IP、源端口、目标IP、目标端口。服务器通常监听一个特定端口(比如80),而客户端使用各自的IP和端口来连接。因此,理论上,服务器的一个端口可以接受来自不同客户端IP和端口的多个连接。理论上,这个数字的上限由可用端口数、IP地址数量以及系统资源决定。例如,如果客户端IP数量多,每个客户端可以使用不同的端口,那么服务器的一个端口可以接受的连接数可能非常高,但实际受限于系统资源如文件描述符、内存和CPU。 接下来,我需要考虑Rust中的实现。Rust的标准库提供了TcpListener来处理传入的连接。每个接受的连接会生成一个新的TcpStream,这需要消耗一个文件描述符。系统对文件描述符的数量有限制,可以通过ulimit调整。此外,内存和处理能力也会限制并发连接数。如果使用异步运行时如Tokio,可以高效处理大量连接,但依然受制于系统资源。 然后,我需要查阅Rust相关文档或常见实践,确认在Rust中如何处理高并发连接。例如,Tokio这样的异步运行时可以处理数十万甚至更多的连接,但需要优化代码和系统配置。可能还需要提到连接数不仅受Rust程序的影响,还受操作系统设置(如最大文件描述符数、TCP参数)的影响。 用户可能想知道具体的数字,但实际中这个数字不是固定的,而是取决于上述因素。需要举例说明可能的限制因素,比如每个连接的内存占用,文件描述符的限制(默认可能是1024,但可以增加到更高),以及网络堆栈的配置(如somaxconn)。 最后,要总结如何最大化连接数,包括调整系统参数、使用异步编程、优化资源管理等。可能还需要提到实际案例或基准测试,展示Rust在处理高并发连接时的表现,比如某些项目声称处理数百万连接,但需要具体配置支持。 需要确保回答准确,同时避免技术错误。例如,区分端口和连接数的关系,明确服务器的监听端口可以接受多个客户端的连接,每个客户端由不同的源IP和源端口标识。因此,理论上的连接数是(客户端IP数 × 客户端端口数),但实际上受限于系统资源。 可能还需要提到,单个端口的同时连接数上限并不是由TCP协议本身设定的,而是由实现和系统限制决定的。因此,在Rust中,只要系统资源允许,可以处理非常多的连接,但需要正确配置和优化代码。 总结思考步骤后,组织成结构化的回答,涵盖关键点:四元组概念、系统资源限制、Rust实现中的注意事项(如异步运行时)、调整系统参数的方法,以及实际应用中的建议。</think>在 Rust 中,一个 TCP 端口(监听端口)理论上可以接受的并发连接数**没有硬性协议限制**,但实际受以下因素制约: -- ### **1. TCP 连接的本质:四元组** 每个 TCP 连接由 **四元组** 唯一标识: - 服务端 IP + 服务端端口 - 客户端 IP + 客户端端口 因此,**单个服务端端口**可以接受来自不同 **客户端 IP 或客户端端口** 的大量连接。理论上,最大连接数为: `客户端 IP 数 × 客户端端口数`(约 `2^32(IPv4) × 2^16(端口) ≈ 2^48`)。但实际中远达不到此上限。 --- ### **2. 实际限制因素** #### **(1)系统资源限制** - **文件描述符(File Descriptor)**: 每个 TCP 连接占用一个文件描述符。系统默认限制(如 `ulimit -n`)通常为 `1024`,但可通过以下方式调整: ```bash # Linux 临时修改(示例设置为 100 万) ulimit -n 1000000 # 永久修改需编辑 /etc/security/limits.conf ``` - **内存与 CPU**: 每个连接需维护内核缓冲区、应用层数据结构(如 Rust 的 `TcpStream`)。粗略估算: - 内核内存:约 4KB~16KB/连接 - 应用内存:取决于业务逻辑(如读写缓冲区大小)。 若 100 万连接,仅内核可能消耗 4GB~16GB 内存。 - **网络协议栈配置**: - `somaxconn`:系统允许的未完成连接队列大小(默认通常为 `128`)。 - `net.core.netdev_max_backlog`:网络设备队列长度。 需调整这些参数以支持高并发(通过 `sysctl` 命令)。 #### **(2)Rust 实现方式** - **同步阻塞模型**: 每连接一个线程(如 `std::thread::spawn`)时,线程数受系统线程上限限制(通常数千级别)。**不适用于高并发**。 - **异步非阻塞模型**: 使用 `async/await` + 运行时(如 **Tokio**),可单线程处理数十万连接。 ```rust use tokio::net::TcpListener; #[tokio::main] async fn main() { let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap(); loop { let (socket, _) = listener.accept().await.unwrap(); tokio::spawn(async move { // 处理 socket }); } } ``` --- ### **3. 实际能支持多少连接?** - **常规场景**: 普通服务器(4GB 内存 + 默认配置)通常支持数千到数万连接。 - **优化后场景**: - 调整 `ulimit`、`sysctl` 参数 - 使用异步框架(如 Tokio) - 精简内存占用(如禁用 Nagle 算法、减少缓冲区) 可支持 **数十万到百万级** 连接(需高性能硬件)。 --- ### **4. 建议与注意事项** 1. **优先使用异步框架**(如 Tokio),避免阻塞线程。 2. **监控资源使用**:关注内存、CPU、文件描述符消耗。 3. **优化协议**:减少单连接资源占用(如短连接改长连接)。 4. **压力测试**:使用工具(如 `wrk`、`JMeter`)模拟高并发场景。 --- ### **示例:调整系统参数(Linux)** ```bash # 增大文件描述符限制 echo "* soft nofile 1000000" >> /etc/security/limits.conf echo "* hard nofile 1000000" >> /etc/security/limits.conf # 调整网络协议栈 echo "net.core.somaxconn = 100000" >> /etc/sysctl.conf echo "net.ipv4.tcp_max_syn_backlog = 100000" >> /etc/sysctl.conf sysctl -p # 生效 ``` --- ### **总结** - **理论上**:单个端口可接受约 `2^48` 个连接(由四元组唯一性决定)。 - **实际上**:受限于系统资源和代码实现,优化后可达百万级。 - **Rust 优势**:异步运行时(如 Tokio)+ 零成本抽象,适合实现高并发 TCP 服务。
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值