暗色模式

Linux TCP 连接诊断:从 ss 到内核计数器

技术教程
2026-09-26
19
0
本文要点
  • 排查「连不上 / 连得慢 / 连接数只涨不跌」时,内核其实一直在记录三张表:socket 表(谁在连、什么状态、队列里堆了多少字节)、累计计数器(连了多少次、失败多少次、重传多少段)、每条连接的控制块(tcp_info:RTT、拥塞窗口、重传)。对应命令就是 ss、/proc/net/snmp、ss -ti——零安装、零抓包
  • ss -lnt 里 LISTEN 行的 Recv-Q / Send-Q 不是收发队列:Recv-Q 是「已完成握手、还在 accept 队列里等应用取走」的连接数,Send-Q 是 listen() 的 backlog 上限。实测这台机器上 sshd 的 22 端口是 128、systemd-resolved 的 53 端口是 4096;Recv-Q 顶到 Send-Q 时新连接会被丢,现象是「超时」而不是「拒绝」
  • ss -tan state established 会漏掉真正要盯的僵尸连接:实测接收端没 close() 时连接停在 CLOSE-WAIT,established 列表里查不到它,得看 ss -tan 全状态。CLOSE-WAIT 只增不减 = 应用忘了关连接
  • ss 只是内核 socket 表的翻译层,原始形态在 /proc/net/tcp:地址列是小端十六进制、端口列是大端——3500007F:0035 就是 127.0.0.53:53;状态是数字码(0A=LISTEN、01=ESTABLISHED);inode 列能把 socket 和进程对上号(socket:[inode]),IPv6 在 /proc/net/tcp6
  • /proc/net/snmp 的 Tcp: 行是累计计数器(15 个数字、顺序固定,实测 RetransSegs 已经累计到 7911),单看没有信息量,看的是变化量:nstat 不带 -a 时只打印「自上次调用以来变化过的计数器」,所以第一次跑常常一片空白,要先 nstat -az 存一次基线
  • 一次可控小流量就能验证计数器怎么动:连一次本机端口 + 连一个没人监听的端口,实测增量正好是 TcpActiveOpens 2、TcpPassiveOpens 1、TcpAttemptFails 1、TcpInSegs 15 / TcpOutSegs 13——AttemptFails 在涨,就是有客户端连不上
  • 单条连接的深水区在 ss -ti 的 tcp_info 续行(以 tab 开头的第二行):rto(重传超时)、rtt(平滑 RTT / 抖动)、cwnd(拥塞窗口)、ssthresh、unacked(在途字节)、retrans(重传次数)
  • 为了让「正常」有对照,用 tc netem 在 lo 上注入 120ms 延迟 + 5% 丢包:实测 RTT 从 1.114ms 涨到 254.63ms、cwnd 从 27 降到 20,并冒出 unacked:23 和 retrans:1/7——干净链路上这两个字段根本不出现(tcp_info 的字段是条件打印的)
  • 两个实测坑:ss -ti 的字段在续行里,只 grep 关键字容易只抓到表头;tc qdisc add dev lo root netem 不能原地改,已有规则时报 Error: Exclusivity flag on, cannot modify.,必须先 tc qdisc del dev lo root
  • 全程零安装:ss / nstat / tc 都是 iproute2 自带,/proc/net/* 是内核直接给的。1 核 1.9G、无 swap 的演示机上跑完全部三个演示,磁盘占用为 0,收尾时 lo 的 qdisc 已回到 noqueue

Linux TCP 连接诊断:从 ss 到内核计数器

「服务连不上」「连得上但很慢」「连接数只涨不跌」——这三类问题的第一反应通常是抓包。抓包当然能定位,但它有个前提:你得先知道该盯哪条连接、哪个方向,否则在几十万行 pcap 里翻起来非常痛苦。

内核其实一直在记录。 它维护着三张随时可查的表:

  • socket 表——有哪些连接、各自什么状态、队列里堆了多少字节、属于哪个进程;
  • 累计计数器——总共发起/接受过多少连接、失败多少次、重传过多少段、收到过多少错误;
  • 每条连接的控制块(tcp_info)——这条连接的 RTT、重传超时、拥塞窗口、在途字节。

它们对应的入口分别是 ss、/proc/net/snmp、ss -ti,全是系统自带命令,不需要装包,也不需要抓包。而且开销极低:读的是一个内存里的哈希表和一个计数器数组,没有包捕获、没有 ptrace。

本文按「从全机到单条连接」的顺序走一遍:先看整机有哪些连接,再看内核原始的 socket 表和计数器,最后钻进一条连接的 TCP 控制块。每一步都在一台 1 核 1.9G、没有 swap 的 Ubuntu 22.04 小机器上实测(内核 5.15.0-30-generic、iproute2 5.15.0、iproute2 自带的 ss 5.15.0),踩到的坑会单独列出来。

包这一层的排查(ping、tcpdump、链路连通性)见 Linux 网络排查:从 ping 到 tcpdump 的故障定位手册,本文补的是它下面那一层——包已经进出去了,内核自己怎么记账。

全机视角:先看有几条连接

三个命令按「粗到细」排列:

ss -s          # 按状态汇总的连接总数
ss -lnt        # 监听中的 TCP 端口(l=listen, n=不做域名解析, t=TCP)
ss -tan        # 全部 TCP 连接,含 TIME-WAIT、CLOSE-WAIT

ss -s 是一眼能看完的概览,实测输出:

Total: 154
TCP:   4 (estab 1, closed 0, orphaned 0, timewait 0)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       1         1         0
TCP       4         3         1
INET      5         4         1
FRAG      0         0         0

三个数字各有口径:Total 是所有协议族的 socket 总数,TCP 那行按状态拆开(estab 只数 ESTABLISHED,TIME-WAIT 和 CLOSE-WAIT 都不算),下面那张表按协议 × 地址族二维展开。所以「总数 154,estab 只有 1」是正常的——一台只开着 SSH 的机器就是这样。

LISTEN 行的 Recv-Q / Send-Q 不是队列长度

这是 ss -lnt 里最容易被误读的地方:

State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   127.0.0.53%lo:53   0.0.0.0:*
LISTEN 0      128    0.0.0.0:22          0.0.0.0:*
LISTEN 0      128    [::]:22             [::]:*

在非 LISTEN 的行里,Recv-Q / Send-Q 确实是这个 socket 的接收队列和发送队列字节数;但在 LISTEN 行里,含义完全变了:

  • Recv-Q = 已完成三次握手、还排在 accept 队列里等应用 accept() 的连接数;
  • Send-Q = listen(fd, backlog) 时给的 backlog 上限,不是「剩余还能接几个」。

实测这台机器上,sshd 的 22 端口 backlog 是 128,systemd-resolved 的 53 端口是 4096(Ubuntu 的单元文件里显式设过)。这两行都是「零 Recv-Q」,也就是没有积压。

当 Recv-Q 逼近 Send-Q,说明握手排满了、应用 accept 得太慢:内核会把新来的 SYN 丢掉(或交给 SYN cookie 兜住),客户端看到的是连接超时,而不是 Connection refused。这个区别很重要——「超时」让你去查网络,「队列满」才是真因。

state established 会骗你

ss 支持按状态过滤,语法很直白:

ss -tan state established    # 只看已建立
ss -tan state syn-sent       # 只看 SYN 发出、还没回应的(连不上的第一现场)
ss -tan state time-wait | wc -l   # 数一下 TIME-WAIT 有多少

(三个状态名在 iproute2 5.15 上都实测可用,返回码 0。)

但只看 established 会漏掉真正要盯的僵尸连接。实测中一次演示里接收端拿到连接后没持有 socket,连接立刻从 ESTABLISHED 掉到 CLOSE-WAIT:

CLOSE-WAIT 1      0          127.0.0.1:38422       127.0.0.1:8899

ss -tn state established 查不到它,ss -tan 才看得到。这个状态的语义是「对端已经发来 FIN(它关了),本端应用还没 close()」——所以它只增不减就是应用在泄漏连接,是最典型的句柄泄漏现场。

TIME-WAIT 恰好相反:它是主动关闭方的正常收尾状态,量大有调优空间(tcp_tw_reuse 那一类,见 Linux sysctl 内核参数调优:从 /proc/sys 到 sysctl.conf 永久配置),和 CLOSE-WAIT 完全是两回事。

内核里的原始形态:/proc/net/tcp

ss 并不神秘,它读的就是内核的 socket 表,只不过替你翻译了一遍。翻译前的样子:

ss -lnt
echo '--- 内核里这张表的原始形态:地址是小端十六进制 ---'
head -1 /proc/net/tcp
grep -v '^ *sl' /proc/net/tcp | head -3
echo '--- 解码地址与状态码 ---'
python3 - <<'PY'
import socket, struct
S = {1: 'ESTABLISHED', 2: 'SYN_SENT', 6: 'TIME_WAIT', 8: 'CLOSE_WAIT', 10: 'LISTEN'}
for line in open('/proc/net/tcp').readlines()[1:4]:
    f = line.split()
    ip, port = f[1].split(':')
    ip = socket.inet_ntoa(struct.pack('<I', int(ip, 16)))
    print(f'{ip}:{int(port, 16):<6} st=0x{f[3]} {S.get(int(f[3], 16), "?")}')
PY

ss -lnt 与 /proc/net/tcp 原始字段对照:3500007F:0035 解码为 127.0.0.53:53,状态码 0A 是 LISTEN、01 是 ESTABLISHED

解码结果(截图最后三行):

127.0.0.53:53    st=0x0A LISTEN
0.0.0.0:22       st=0x0A LISTEN
172.16.0.84:22   st=0x01 ESTABLISHED

三行对应三条 socket:systemd-resolved 的存根 DNS、sshd 的监听、以及我自己这条 SSH 会话(本机 172.16.0.84:22 ↔ 客户端)。几个要点:

  • 地址是小端十六进制,端口却是大端:3500007F:0035 要按 4 字节倒序读(7F 00 00 35 → 127.0.0.53),端口 0035 直接按十六进制读就是 53。这个不对称是第一眼最容易读错的地方,解码脚本里 struct.pack('<I', ...) 处理的就是它。
  • 状态是数字码:st 列的 0A 是 LISTEN、01 是 ESTABLISHED。完整对照见下面的表。
  • tx_queue:rx_queue 是这个 socket 的发送/接收队列字节数:LISTEN 行恒为 0;实测那条 SSH 连接是 000002B4:00000000,也就是发送队列里排着 692 字节(0x2B4)。
  • inode 列能把 socket 和进程对上号:/proc/<pid>/fd/ 里那个 socket:[259048] 就是它——ss -p 显示进程名靠的正是这条线索。排查「这个端口到底是谁占的」时,它是 lsof 之外的另一条路。
  • IPv6 在 /proc/net/tcp6(演示机上该文件存在、权限 -r--r--r--),格式与 tcp 完全一致,只是地址是 32 位十六进制。

st 状态码全表(net/tcp_states.h 里的定义,十六进制):

st状态什么时候出现
01ESTABLISHED连接已建立,可以传数据
02SYN_SENT本端发了 SYN,等对端 SYN+ACK(连不上的第一现场)
03SYN_RECV收到 SYN、回了 SYN+ACK,等最后一个 ACK
04FIN_WAIT1本端主动关闭,FIN 已发出
05FIN_WAIT2本端 FIN 已被确认,等对端 FIN
06TIME_WAIT双方都关了,等 2MSL 后彻底消失
07CLOSE完全关闭
08CLOSE_WAIT对端已关,本端应用还没 close()
09LAST_ACK被动关闭方等最后一个 ACK
0ALISTEN正在监听
0BCLOSING双方几乎同时关闭

累计计数器:/proc/net/snmp 与 nstat 的增量视图

/proc/net/snmp 里的 Tcp: 行是内核从开机起累计的 TCP 计数器,15 个数字,顺序是固定的:

grep '^Tcp:' /proc/net/snmp | tail -1

实测这一行是:

Tcp: 1 200 120000 -1 462 48433 13 27218 1 847549 872048 7911 10 61079 0

逐字段对照(值是上面的实测值):

字段实测含义
RtoAlgorithm1重传超时算法(1 = RFC 793 的经典算法)
RtoMin / RtoMax200 / 120000RTO 下限 / 上限(毫秒)
MaxConn-1最大连接数(-1 = 不限制)
ActiveOpens462本机主动发起连接的次数(connect())
PassiveOpens48433本机被动接受连接的次数(服务被连)
AttemptFails13连接尝试失败的次数
EstabResets27218已建立连接被 RST 掉的次数
CurrEstab1当前处于 ESTABLISHED 的连接数
InSegs / OutSegs847549 / 872048收 / 发的 TCP 报文段总数
RetransSegs7911重传的报文段总数
InErrs / OutRsts10 / 61079收到的错误段数 / 发出的 RST 数
InCsumErrors0校验和错误数

单看这行没有信息量——它是开机以来的累计值,OutSegs 是 87 万还是 8.7 万,取决于机器开了多久。有用的是变化量,而 nstat 就是干这个的:

  • nstat(不带参数):打印自上次调用以来变化过的计数器,没变的一律不显示;
  • nstat -a:忽略历史文件,全部打印;
  • nstat <名字>:只看指定的计数器(名字可以用正则)。

所以排查前要先「存一次基线」,否则第一次跑 nstat 往往是空的。完整演示:

CNT='TcpActiveOpens TcpPassiveOpens TcpAttemptFails TcpInSegs TcpOutSegs TcpRetransSegs'
echo '--- 1) 内核原始计数器:/proc/net/snmp 的 Tcp 行,字段顺序固定 ---'
grep '^Tcp:' /proc/net/snmp | tail -1
echo '--- 2) 先读一次存基线,把历史文件推到现在 ---'
nstat -az $CNT > /dev/null
echo '--- 3) 造流量:一次成功连接 + 一次连没人监听的端口 ---'
python3 - <<'PY'
import socket, threading
srv = socket.socket(); srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', 8898)); srv.listen(4)
threading.Thread(target=lambda: [srv.accept()[0].recv(65536) for _ in range(50)], daemon=True).start()
c = socket.create_connection(('127.0.0.1', 8898))
c.sendall(b'y' * 200000)
c.close()
try: socket.create_connection(('127.0.0.1', 8897), timeout=1)   # 端口没人听
except OSError: pass
PY
echo '--- 4) 再读一次:不带 -a 时 nstat 只打印变化过的计数器 ---'
nstat $CNT

nstat 增量视图实测:一次成功连接 + 一次被拒的连接,让 TcpActiveOpens 涨 2、TcpPassiveOpens 涨 1、TcpAttemptFails 涨 1、TcpInSegs/OutSegs 涨 15/13

第二步的 nstat -az > /dev/null 把当前值写进历史文件(相当于「按下秒表」),第三步制造流量,第四步读增量,输出正好是五个计数器:

#kernel
TcpActiveOpens                  2                  0.0
TcpPassiveOpens                 1                  0.0
TcpAttemptFails                 1                  0.0
TcpInSegs                       15                 0.0
TcpOutSegs                      13                 0.0

这五个数字和刚才做的事严格对得上:

  • ActiveOpens +2:本机发起了两次 connect()(一次成功、一次被拒);
  • PassiveOpens +1:有一次连接在本机被接受(127.0.0.1:8898 那个监听);
  • AttemptFails +1:被拒的那次——对端回了 RST,连接尝试失败;
  • InSegs +15 / OutSegs +13:两百多 KB 的本机往返加上那次失败的握手,一共 15 个收包、13 个发包。

这就是增量视图的价值:AttemptFails 在涨,等于「有客户端连不上」;RetransSegs 在涨,等于「有丢包」;EstabResets 在涨,等于「有连接被对端 RST 掐断」。它们的绝对值没有意义,斜率才有。

收尾提醒:nstat -az 会把历史文件推到此刻,也就是消费掉了上一次的增量。生产环境里排查前想留证据,先跑一次 nstat -az 存档再动手。

单条连接的深水区:tcp_info

前两节都是「全机视角」,最后落到一条连接上。ss -i 会为每条连接多打出一行内核 TCP 控制块里的信息——注意它是以 tab 开头的续行,紧跟在 socket 行下面。这是本机一条真实 SSH 连接(字段太多,中间省略):

State  Recv-Q Send-Q Local Address:Port   Peer Address:Port
ESTAB  0      84     172.16.0.84:22       117.140.251.220:24411
     cubic wscale:6,7 rto:240 rtt:36.088/2.118 … cwnd:10 …

字段很多,先把最常看的挑出来:

字段含义怎么用
rto重传超时(毫秒)内核按 RTT 动态算出来的,通常不用直接看
rtt平滑 RTT / 平均偏差(毫秒)第一个数是 RTT,第二个数是抖动,判断「慢在哪一段」的第一手数据
cwnd拥塞窗口(报文段数)乘上 MSS 就是在途字节上限;带宽延迟积大而 cwnd 上不去,说明被拥塞控制了
ssthresh慢启动阈值掉到很低说明刚经历过丢包
unacked已发出未被确认的字节数连着 retrans 一起看,判断是否卡在恢复阶段
retrans重传数(当前窗口 / 累计)增长就是丢包,最直接的证据
segs_out / segs_in发出的 / 收到的报文段数判断数据往哪个方向流

问题在于,一条正常连接的 tcp_info 里没有「异常」可看——所以演示时得先造出一个「异常」当对照。tc netem 可以在不碰物理网卡的前提下给回环设备加延迟和丢包(lo 上的规则只影响本机回环,SSH 走的是 eth0,不会被自己坑到),完整脚本:

tc qdisc del dev lo root 2>/dev/null   # 先清干净:netem 不能原地改,残留规则要先删
cat > /tmp/tcpinfo.py <<'PY'
import socket, subprocess, threading, time

# 按对端端口锁定本机这条连接,取它后面那行内核 tcp_info,只留关心的字段
SS = ("ss -tin | awk '$5 ~ /:8899$/ {getline; print; exit}' "
      "| tr ' ' '\n' | grep -E "
      "'^(rto|rtt|cwnd|unacked|retrans|segs_out|segs_in|ssthresh)' | paste -sd' '")

srv = socket.socket(); srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', 8899)); srv.listen(8)

def serve():                       # 一直收,别让接收窗口把发送端卡住
    while True:
        try: c, _ = srv.accept()
        except OSError: return
        threading.Thread(target=lambda s=c: [s.recv(262144) for _ in range(99999)],
                         daemon=True).start()

threading.Thread(target=serve, daemon=True).start()

def blast(sec):                    # 持续灌数据,tcp_info 才有东西可看
    c = socket.create_connection(('127.0.0.1', 8899))
    end = time.time() + sec
    while time.time() < end:
        try: c.sendall(b'x' * 65536)
        except OSError: break
    return c

def snap(label):
    out = subprocess.run(SS, shell=True, capture_output=True, text=True).stdout.strip()
    print(f'{label} -> {out}')

c1 = blast(3); snap('干净 lo 链路'); c1.close(); time.sleep(0.5)
subprocess.run('tc qdisc add dev lo root netem delay 120ms loss 5%', shell=True)
c2 = blast(3); snap('延迟 120ms + 丢包 5%'); c2.close()
PY
python3 /tmp/tcpinfo.py; rm -f /tmp/tcpinfo.py
tc qdisc del dev lo root 2>/dev/null; echo '已清理 netem 与临时脚本'

netem 注入前后 tcp_info 对照:干净 lo 链路 rto:204 rtt:1.114 cwnd:27,注入 120ms 延迟 + 5% 丢包后 rto:488 rtt:254.63 cwnd:20 unacked:23 retrans:1/7

两次快照的实测结果:

干净 lo 链路 -> rto:204 rtt:1.114/0.897 cwnd:27 ssthresh:19 segs_out:13596 segs_in:3366
延迟 120ms + 丢包 5% -> rto:488 rtt:254.63/26.049 cwnd:20 ssthresh:14 segs_out:201 segs_in:63 unacked:23 retrans:1/7

对照着读,信息量比想象的大:

  • rtt 从 1.114ms 变成 254.63ms:注入的是 120ms 单程延迟,往返就是 240ms 上下,实测值和注入值吻合。第二个数(/0.897 → /26.049)是平均偏差,也就是抖动——它从不到 1ms 涨到 26ms,说明这条链路不只是慢,而且不稳定。
  • cwnd 从 27 降到 20:拥塞窗口被丢包打下来了。丢包时内核会把 ssthresh 砍半(19 → 14)并缩小 cwnd,这是拥塞控制的正常反应,不是故障。
  • unacked:23 冒出来了:有 23 个报文段(在途字节)还没被确认。干净链路上这个字段根本不出现——因为当时它的值是 0。
  • retrans:1/7 冒出来了:当前窗口重传了 1 个,累计重传 7 个。这是「链路在丢包」最直接的证据。
  • segs_out 从 13596 掉到 201:同样的 3 秒,干净链路上发了 1.3 万个报文段,加了 240ms RTT 之后只发了 201 个——吞吐的差距不在带宽,在往返时间。

最后一条是这组数字最实用的地方:如果对端抱怨「你们接口慢」,而 rtt 显示 250ms、retrans 还在涨,那问题在链路上,不在应用;反过来 rtt 正常但 cwnd 一直很小、retrans 持续增长,就要去查中间设备了。

字段是条件打印的:retrans 和 unacked 只在非零时出现,delivery_rate 这类也不总有。解析脚本里「字段取不到」很可能不是 bug,而是它的值本来就是 0——别写死字段个数。

一张排查路径表

把三层证据按「现象」组织起来,比背参数有用:

现象先看哪里命令
Connection refused对端有没有在监听;本端失败计数有没有涨对端 ss -lnt,本端 nstat TcpAttemptFails
连接卡住不动(发出去的 SYN 没回应)本端有没有卡在 SYN_SENTss -tan state syn-sent
连得上但很慢这条连接的 RTT 与重传ss -ti 看 rtt / retrans / cwnd
服务端「接不过来」LISTEN 行的 Recv-Q 是否逼近 Send-Qss -lnt
连接数只增不减是否堆在 CLOSE-WAIT(应用没关连接)ss -tan(别只看 established)
整机网络异常计数器斜率:失败、重传、RSTnstat -az 存基线 → 隔一会儿 nstat

坑清单(都是本机实测)

  1. ss -ti 的字段在续行里。tcp_info 是紧跟在 socket 行下面的、以 tab 开头的一行,grep 关键字很容易只抓到表头。稳妥的取法是先按端口定位 socket 行、再取它的下一行——本文脚本用的是 awk '$5 ~ /:8899$/ {getline; print; exit}',先匹配「对端端口是 8899 的那条 socket」,再读它的下一行。
  2. state established 会漏掉 CLOSE-WAIT。僵尸连接不在 established 里,排查连接泄漏要看 ss -tan 全状态(ss -s 的 estab 字段同样只数 ESTABLISHED)。
  3. nstat 不带 -a 时「没变化就不打印」。第一次跑常常一片空白,会让人以为计数器坏了;要先 nstat -az 存基线。反过来,-az 会把历史推到此刻,等于消费掉上一次的增量。
  4. LISTEN 行的 Send-Q 不是发送队列,是 listen() 的 backlog 上限;Recv-Q 才是「等 accept 的连接数」。
  5. tc qdisc add 不能原地改 netem。已有规则时实测报 Error: Exclusivity flag on, cannot modify.(返回码非 0),必须先 tc qdisc del dev lo root。本文脚本第一行就是这句删除,就是为了避开它;lo 的默认 qdisc 是 noqueue,删掉 netem 之后会回到它。
  6. 演示完一定要把 netem 删掉。残留的 120ms 延迟会让本机所有回环通信一起变慢(包括 systemd-resolved 的 127.0.0.53);本次第一轮演示就踩了这个坑——上一轮脚本中途报错退出,没走到清理那一步,导致下一轮「干净链路」的 RTT 直接是 251ms。排查时看到的「网络慢」,有可能是自己上一条命令留下的。

小结

TCP 诊断的三层证据,各自回答一个不同的问题:

  1. ss -tan / ss -lnt——「现在有哪些连接、什么状态、队列堆了多少」。用来确认现象(连不上、连得慢、连接堆积),一两秒就能看完;
  2. /proc/net/snmp + nstat——「一段时间内发生了什么」。先 nstat -az 存基线,做事,再 nstat 读增量,看的是斜率不是绝对值;
  3. ss -ti 的 tcp_info——「这一条连接到底怎么了」。rtt 定位慢在哪一段,retrans 确认丢包,cwnd / unacked 判断拥塞状态。

顺序是先粗后细:先确认有没有连接、再看计数器有没有异常增长、最后才钻到单条连接。反过来的话,很容易在一堆正常的连接里找一个不存在的异常。

全程零安装、零抓包、磁盘零占用——一台 1 核 1.9G 的小机器就能把这些全部跑一遍,收尾把 lo 上的 netem 删掉即可(本文演示机结束时 lo 的 qdisc 是 noqueue,磁盘可用 5.1G,无任何残留)。

想继续往下走:tc netem 还能注入抖动、乱序、重复包,玩法见 Linux 网络模拟:tc 与 netem 注入延迟、抖动与丢包;要逐包看握手细节就轮到 Linux 网络排查:从 ping 到 tcpdump 的故障定位手册;只是想确认某个端口通不通,netcat 网络调试工具:从端口探测到文件传输 比 ss 更顺手;而 tcp_tw_reuse、somaxconn 这些和本文直接相关的内核参数,在 Linux sysctl 内核参数调优:从 /proc/sys 到 sysctl.conf 永久配置 里有完整清单。

发表评论

暂无评论,快来抢沙发吧!