海量设备接入时TCP长连接对系统资源的占用不是“每个连接吃1个线程”那种线性消耗,而是文件描述符、内核内存、收发缓冲区、CPU软中断等多个层面的累加,真正需要高枕无忧就得在Linux内核参数、业务心跳策略和接入架构三个维度同时下手,否则设备一上万,资源就会悄悄被吃光。
为什么TCP长连接在物联网场景下成了资源黑洞
接入海量设备时,TCP长连接能保证数据通道实时在线,但服务器每维持一条连接都背着隐性成本,资源占用并非体现在“带宽”上,而在于操作系统分配的各种内核资源,开一个长连接,服务端至少需要一张文件描述符(fd),还要在内存中维护TCP控制块(sock结构体)以及收发缓冲区。
文件描述符从来都是第一瓶颈
多数Linux服务器默认单进程fd上限是1024,即便调高到65535,面对十万甚至百万级设备依然不够用,海量设备场景中,fd是最先爆掉的一层,学会用以下命令排查,你能快速判断服务端是否到了极限:
# 查看当前进程已打开的fd数 ls /proc/<pid>/fd | wc -l # 查看系统级fd限制 cat /proc/sys/fs/file-max # 查看单进程限制 ulimit -n
设备数达到一定量级后,服务端进程的fd数会直线攀升,值得注意的是,fd耗尽不会立刻让系统崩溃,而是表现为新连接无法建立,旧连接却迟迟不释放,这种半死不活的状态最让人头疼,因为业务侧看监控一切正常,但设备就是连不上。
内核内存比你想的更会吃资源
每个TCP长连接占用的内存主要来自两个部分:socket接收缓冲区加上发送缓冲区,对应内核参数rmem和wmem,默认情况下发送缓冲区约为16KB,接收缓冲区约为87KB,但实际使用量取决于读写频率,一条长连接如果持续有数据流动,内核会自动调优缓冲区大小,最高可以翻到数MB。
一万条活跃长连接,按平均值每条占用100KB内核内存计算,光TCP栈本身就需要接近1GB内存,若业务层每个连接再配一个读缓冲或写队列,应用内存消耗又是另一笔账,更隐蔽的是,内核中每个socket还有struct sock、struct socket、struct file等结构体,三者叠加,单连接纯内核开销就有2KB至4KB。
CPU开销:别忽略软中断和定时器
设备规模上来以后,CPU可能先扛不住,TCP长连接活跃时,每次收包都触发软中断(softirq),连接数越多,中断频率越高,多核CPU的负载分布如果不均匀,某些核心会先被打满,Linux内核的SO_REUSEPORT和多队列网卡(RSS)就是为了分散这种压力而生的。
定时器也是一种容易被忽略的消耗,每个TCP连接都有保活定时器、重传定时器、延迟ACK定时器,这些定时器在内存中由内核时间轮维护,连接量过大时,定时器粒度和处理开销之间需要做细致权衡,默认的TCP_KEEPIDLE等参数常需要调优。
TCP长连接和短连接哪个更适合海量设备
很多团队在选型时纠结于长连接还是短连接。

这个问题的答案取决于是“交互频率高”还是“设备量大”,短连接由操作系统回收资源,服务端不用花精力维护连接状态,但每次建立连接都要走TCP三次握手,时间成本和SYN队列压力也不小,长连接省去了反复握手的开销,却要对空闲连接持续付出保活成本。
维度对比:
| 资源项 | 长连接 | 短连接 |
|---|---|---|
| fd占用 | 长时间占用,连接存活期不释放 | 请求结束随即释放 |
| 内核内存 | 每连接常驻2KB-4KB | 瞬时占用,峰值后回落 |
| CPU开销 | 定时器保活、心跳触发不断 | 三次握手与四次挥手频次高 |
| 服务端复杂度 | 需要心跳管理、超时剔除 | 连接状态简单,实现容易 |
行业共识认为:设备量超过一万、且需要服务端主动下发指令的场景,长连接是唯一符合实际的选择,但必须配合心跳分级管理和空闲连接淘汰策略。
心跳与保活机制决定资源生命周期
心跳是长连接时代的必要之恶,设备端每隔30秒到5分钟发一个心跳包,服务端据此判断设备存活。心跳间隔太长,僵尸连接占着fd不释放;间隔太短,网络带宽和CPU都被白白消耗,业内专家指出,量产项目中“服务端判定超时时间 = 心跳间隔 × 3”是最稳妥的冗余策略。
服务端要主动清理不活跃连接,一般顺序是:
- 设置TCP_KEEPIDLE为7200秒,但不要指望内核KEEPALIVE兜底,它太迟钝
- 应用层维持最后心跳时间戳,超时未收到心跳的设备主动断开
- 对已断开的连接,将SO_LINGER设为0并立即发RST,防止进入TIME_WAIT堆积
TIME_WAIT堆积问题也藏其中
长连接并非完全规避了TIME_WAIT问题,服务端主动断连时,主动关闭方同样会进入TIME_WAIT状态,默认持续60秒,期间fd和本地端口都被占用,海量设备服务端代码中,不规范地主动Close操作可能瞬间堆积数万条TIME_WAIT连接。
需要区分的是,TIME_WAIT资源问题更常出现在“高并发短连接”架构中,长连接场景真正要防的是ESTABLISHED状态的僵尸连接,它们占着连接池却完全不工作,如果你遇到服务端dmesg提示TCP: time wait bucket table overflow,那说明TIME_WAIT参数确实需要调优。
怎么判断服务端是否被长连接拖垮
判断长连接是否在拖垮服务端,不能只看“连接数”,需要联动观察fd使用率、内存增量、CPU软中断占比和业务响应延迟,很多团队先看连接数觉得还好,但系统已经假死,问题就出在没看全维度。
用ss命令看到每个连接的资源真相
# 查看当前所有TCP连接状态统计 ss -s # 查看每个socket占用内存大小(单位KB) ss -m state established # 查看fd与TCP连接的数量关系 ss -lnt | wc -l
真正需要重点关注的指标是:

- ESTABLISHED状态连接数占最大连接数的比例
- 每条连接的实际收发缓冲区大小
- 是否存在高比重SYN_RECV或CLOSE_WAIT状态连接
- 文件描述符总数与系统上限的差值
CLOSE_WAIT状态连接多,多半是业务层忘了调用close方法,这种泄漏最致命,很多长连接服务在客户端主动断开时,服务端没能正确感知,连接在各个worker上挂着,积少成多后直接拖垮全部节点。
内存和CPU联动排查法
排查长连接资源问题时,按下面路径逐步确认:
- 用free命令看可用内存下降趋势,重点对比“buff/cache”的增长
- 用top按内存排序,检查应用进程RES是否持续膨胀
- 用top按CPU排序,看软中断(si)占比是否超过15%以上,超过则说明收包中断过于集中
- 用
cat /proc/net/sockstat查看系统已经用掉多少TCP socket内存
如果TCP内存(sockstat中tcp_mem)占到总内存的25%以上,说明TCP协议栈层面的内存压力已经很高,内核可能拒绝为新建连接分配sk_buff。
TCP长连接怎么优化资源占用,把万级设备压到最低成本
优化目标是提高单机接入密度,让同样的硬件承载更多连接,业界通用思路是在Linux内核层、应用层以及接入架构三层同步优化,只改一处收效有限。
内核参数按这个基准调整
# 文件描述符限制调大 fs.file-max = 1000000 # 允许更多TIME_WAIT状态连接复用 net.ipv4.tcp_tw_reuse = 1 # 减少TIME_WAIT连接数(仅用于高并发场景,谨慎开启) net.ipv4.tcp_max_tw_buckets = 20000 # 扩大本地端口范围,避免端口耗尽 net.ipv4.ip_local_port_range = 1024 65535 # 调整TCP读写缓冲区,避免内存被过度预占 net.ipv4.tcp_rmem = 4096 8192 16777216 net.ipv4.tcp_wmem = 4096 8192 16777216
其中最关键的是tcp_rmem和tcp_wmem,默认值经常导致每连接预分配过大内存,把初始值从16KB及87KB调低后,空闲长连接占据的内存会少一个量级,但需要注意,业务中如果有大包传输,还需另测缓冲区上限是否够用。
应用层:事件驱动和分级保活
应用层选型上,拒绝一个连接一个线程的传统阻塞IO模型,改用epoll(Linux)或kqueue(BSD)事件驱动模型,Go语言的netpoller、Java NIO、C++的libevent或Boost.Asio都遵循这一原则,事件驱动下,处理十万连接只需要几十到几百个线程,资源开销是常数级的。
心跳分级策略是降本增效的常用手段:
- 在线且最近5分钟有数据交互的设备,不做额外心跳探测
- 5分钟无数据设备改为一分钟一次轻量级心跳
- 超过15分钟无心跳标记为待清理,发送探测包确认后踢掉
网关层:连接收敛和协议转换
如果设备量实在太大,单台服务器扛不住,接入网关架构要考虑。在业务入口增加一层接入网关,负责维护海量客户端的长连接,再通过内部短连接转发数据到业务集群,这样业务层就能轻松做到无状态水平扩展。

典型做法是MQTT Broker或自研TCP网关做连接收敛,网关节点通过一致性哈希将设备映射到固定后端节点,避免后端集群扩容时大量断连重连,做物联网设备接入遇到性能瓶颈时,这种“设备连网关、网关连后端”的架构是最常见且有效的解法,虽然多一跳会带来少许延迟,但整体收益远高于损耗。
连接上限和全局限流
给每个接入节点设置最大连接数上限,是防止慢速连接耗尽系统资源的安全阀,主流接入网关均有类似配置:
- NGINX的
worker_connections参数 - HAProxy的
maxconn参数 - Netty服务端的
SO_BACKLOG及自定义连接数计数器
设置上限时留出20%到30%余量给突发情况,同时也给GC线程或内部任务调度留CPU时间片,当负载超出设定上限,网关直接返回“服务器繁忙”,不要跟客户端反复重试握手,那会让情况更糟。
物联网卡TCP长连接成本怎么算才不亏
讨论资源占用最终要落到成本上,长连接成本体现在带宽、内存和运维投入三方面。主要成本项并非数据流量,而是Idle状态下的连接保持费用,在电信运营商物联网卡套餐里,一般采用“流量+连接数”计费,连接管理费与在线率直接挂钩。
评估成本时要算三笔账:
- 基础设施成本,单台服务器能支撑的并发连接数越高,均摊到每连接的硬件成本越低
- 流量成本,心跳包若按每5分钟发送一次计算,一颗50字节的心跳包每个月会产生约432KB流量,大规模设备下这部分费用会被极大放大
- 开发运维成本,排查CLOSE_WAIT泄漏、优化连接池所带来的隐性工时消耗
常见问题
为什么设备量不大但TCP长连接资源占用很高?
设备量不大却资源紧张,常见原因有两类:一是服务端存在连接泄漏,未正确关闭已失效的socket,导致CLOSE_WAIT状态堆积;二是收发缓冲区配置过大,每条连接预分配内存太多,用ss和lsof命令排查有多少异常状态连接,随后调整内核参数即可见效。
TCP长连接一直占用资源吗,有没有办法降低空闲连接开销?
空闲连接也占用fd和内核sock结构,这是无法避免的,但可以降低单连接内存预分配值,调低tcp_rmem初始值,同时开启写缓冲的自动调优(tcp_autotuning),让空闲连接的内存占用最小化,如果连接长时间无数据,建议服务端主动断开,而非硬着头皮保持连接,否则这部分连接就是纯成本。
海量连接下修改哪些内核参数优先级最高?
fs.file-max、net.ipv4.ip_local_port_range、net.ipv4.tcp_rmem、net.ipv4.tcp_wmem优先级排在最前面,这四个参数分别对应fd上限、端口耗尽、接收内存、发送内存,是海量设备接入最直接的四类瓶颈,调好这四个参数之后,再根据实际观测优化定时器参数和TCP keepalive。