多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Linux TCP/IP实战:从ss命令到内核协议栈调优

Linux TCP/IP实战:从ss命令到内核协议栈调优 简介本资源是一份面向IT求职者与网络初学者的《计算机网络基础知识》系统梳理PDF聚焦面试高频考点与核心协议原理帮助读者快速构建网络通信知识框架。内容覆盖OSI七层模型与TCP/IP四层模型对比、TCP三次握手与四次挥手、状态机与TIME_WAIT机制、流量与拥塞控制、IPv4/IPv6地址体系、ICMP/ARP/IGMP等关键协议解析并深入对比TCP/UDP/SCTP特性及适用场景。资源为单文件PDF格式共1个文件大小2.08MB排版清晰、结构完整适合作为面试速查手册或入门学习笔记。目前已有969人学习下载内容源自CSDN优质技术博文知识点紧扣实际应用与典型面试题便于理解协议本质、排查网络问题并支撑后续网络编程与系统设计实践。1. 这不是教科书里的“网络模型图”而是一线工程师每天在netstat -an、ss -tuln、Wireshark 抓包、tcpdump日志和dmesg | grep -i tcp里反复验证的 TCP/IP 实战内核你有没有遇到过服务上线后连接数卡在 65535 不动TIME_WAIT占满端口导致新连接Connection refusedcurl偶发超时但ping通telnet却连不上K8s Pod 间通信偶发丢包iperf3 -u测 UDP 流量正常TCP 却断流——这些不是玄学全是 TCP 状态机、滑动窗口、RTO 估算、TIME_WAIT 释放节奏、端口复用策略在真实系统里咬合运转时发出的“咯吱”声。这份《计算机网络基础知识》不是为考试默写 OSI 七层而生它是把 Linux 内核网络栈net/ipv4/tcp_input.c、glibc socket API、/proc/sys/net/ipv4/参数树、ss工具输出字段、Wireshark 的tcp.flags.ack 1 tcp.flags.syn 1过滤器全部拧成一股绳的实战笔记。它专治三类人刚从学校毕业、面对ESTABLISHED和CLOSE_WAIT状态日志两眼发黑的新人做云原生、微服务、IoT 设备接入天天调SO_REUSEADDR却说不清为什么的中级工程师还有那些在harbor 推送失败 get https://...: dial tcp ...错误里翻了三天文档最后发现是net.ipv4.tcp_tw_reuse 0惹的祸的老手。它不讲“理论上应该怎样”只讲“/proc/sys/net/ipv4/tcp_fin_timeout改成 30 后你的 Nginx 负载均衡器每秒多撑住多少个短连接”。2. OSI 与 TCP/IP 模型别再背“七层四层”看懂ss -i输出里的cwnd、rtt、retrans才算入门2.1 为什么面试必问“OSI 和 TCP/IP 区别”因为这是判断你是否真进过内核协议栈的分水岭OSI 是 ISO 制定的标准框架像建筑蓝图——它定义了“会话该有建立/维持/终止”“表示层该管加密压缩”但没规定怎么实现。TCP/IP 是 IETF 推出的工程事实像施工队交的活——Linux 内核里压根没有独立的session_layer.c或presentation_layer.c文件。socket()创建的 fdconnect()发起的 SYNsend()写入的数据全在net/ipv4/目录下流转。当你执行ss -i查看一个连接详情$ ss -i src :8080 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 192.168.1.100:8080 10.0.2.15:54322 cwnd:10 ssthresh:21 rtt:12.5ms rttvar:4.2ms ato:40ms mss:1448 pmtu:1500 rcvmss:536 advmss:1448 retrans:0 lost:0 lastsnd:1200 lastrcv:800 lastack:800 pacing_rate 1000000000b/s这里的cwnd拥塞窗口、rtt往返时延、retrans重传次数全部来自TCP 层传输层而pmtu路径 MTU、mss最大分段大小则由IP 层网络层参与协商。ss命令根本不会、也不需要去读取一个叫“会话层”的抽象概念——因为 Linux 内核里应用进程自己管理会话比如 HTTP 的 Cookie、JWT内核只管字节流可靠送达。所以当面试官问“OSI 会话层对应 TCP/IP 哪一层”标准答案是“TCP/IP 模型中无直接对应层其功能由应用层协议如 SIP、RPC或传输层TCP 连接生命周期协同实现”。死记硬背“OSI 七层 vs TCP/IP 四层对比表”不如亲手敲ss -i看一眼cwnd如何随网络抖动跳变。2.2 TCP/IP 模型的“四层”在 Linux 中究竟长什么样/proc/sys/net/就是你的控制台TCP/IP 模型的四层在 Linux 内核中并非逻辑隔离的四个模块而是数据包穿越协议栈时的处理阶段标记。一个 TCP 数据包从网卡进来依次经过数据链路层drivers/net/ethernet/驱动收包 →net/bridge/如果走桥接→net/8021q/VLAN→ 最终交给net/ipv4/网络层net/ipv4/ip_input.c处理 IP 头校验、路由查找fib_lookup()、分片重组 → 若协议号是IPPROTO_TCP (6)交给net/ipv4/tcp_ipv4.c传输层net/ipv4/tcp_input.c解析 TCP 头 → 根据sk_state如TCP_ESTABLISHED调用对应状态机函数 → 更新tp-snd_cwnd拥塞窗口应用层net/ipv4/tcp.c将数据拷贝到 socket 接收缓冲区sk-sk_receive_queue→ 应用调recv()时从这里读这个过程在/proc/sys/net/ipv4/下有完整映射。例如tcp_fin_timeout控制FIN_WAIT_2状态超时时间单位秒默认 60tcp_tw_reuse允许将TIME_WAIT状态的 socket 用于新连接需net.ipv4.tcp_timestamps1tcp_sack启用选择性确认Selective ACK对抗乱序丢包ip_forward控制网络层是否转发 IP 包即是否做路由器提示修改这些参数前务必用sysctl -w net.ipv4.tcp_tw_reuse1临时生效并用ss -s观察TIME-WAIT数量变化。永久生效写入/etc/sysctl.conf但生产环境严禁盲目调优——tcp_tw_reuse1在 NAT 环境下可能引发连接混淆。2.3 “套接字Socket是传输层以下的封装”不它是用户空间与内核网络栈的唯一契约socket()系统调用返回的 fd表面看是个整数实则是内核为你创建的一个struct sock对象句柄。它捆绑了五元组(协议, 源IP, 源端口, 目标IP, 目标端口)——ss -tuln输出的Local Address:Port和Peer Address:Port发送/接收缓冲区SO_SNDBUF/SO_RCVBUF设置的大小直接影响Send-Q/Recv-Q状态机sk_state字段值为TCP_ESTABLISHED、TCP_CLOSE_WAIT等ss状态列即来源于此拥塞控制算法ca_ops指针指向reno,cubic,bbr等算法实现当你调connect()内核做的远不止发 SYN检查源端口若未显式bind()从ip_local_port_range默认32768 60999选一个空闲端口构造 SYN 包填入tcp_opt含MSS,Timestamps,SACK_PERM等选项启动重传定时器初始 RTO 1 秒后续按RTT动态调整将 socket 置为TCP_SYN_SENT状态加入inet_hashinfo哈希表等待响应所以socket不是“封装”而是用户空间进程与内核网络协议栈签订的、具备法律效力的 SLA服务等级协议你承诺按 POSIX socket API 调用内核承诺按 RFC 793 实现可靠字节流。理解这点才能看懂strace -e tracesocket,connect,send,recv的每一行系统调用背后内核在做什么。3. TCP 连接生命周期三次握手不是“你好你好”而是内核状态机与定时器的精密协奏3.1 三次握手的本质SYN Flood 攻击为何能打垮服务器看透LISTEN状态的内存开销三次握手不是教科书上的三个箭头而是服务端内核为每个连接请求付出的真实内存与 CPU 成本。当 Nginx 监听0.0.0.0:80调用listen(sockfd, SOMAXCONN)后内核创建一个struct inet_listen_hashbucket其中维护两个队列SYN 队列incomplete queue存放收到 SYN、尚未完成三次握手的连接每个条目是struct request_sock约 200 字节Accept 队列established queue存放已完成三次握手、等待accept()取走的连接每个条目是完整的struct sock约 1.5KB攻击者发海量 SYN 包却不回 ACKSYN 队列被占满新合法 SYN 被丢弃现象就是ss -lnt看到Recv-Q持续为SOMAXCONN值如 128。此时netstat -s | grep -i SYNs to LISTEN sockets dropped会飙升。解决方案不是调大somaxconn而是启用tcp_syncookies1内核用加密哈希生成cookie代替request_sock存储抗 SYN Flood调小tcp_synack_retries3减少 SYNACK 重试次数加速释放资源用iptables限速iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT注意tcp_syncookies1会禁用TCP Fast Open (TFO)因 TFO 依赖cookie传递应用数据与 SYN Cookie 冲突。业务若需 TFO必须用硬件负载均衡器如 F5做 SYN Proxy。3.2 为什么服务端 SYNACK 能合并发送而四次挥手必须拆成两步从tcp_send_ack()源码找答案关键在net/ipv4/tcp_output.c的tcp_send_ack()函数。当服务端收到客户端 SYN 时内核执行// net/ipv4/tcp_input.c: tcp_conn_request() if (req-syn) { // 构造 SYNACK 包设置 flags TCPHDR_SYN|TCPHDR_ACK skb tcp_make_synack(sk, dst, req, foc); tcp_queue_skb(sk, skb); // 入队发送 }TCPHDR_SYN|TCPHDR_ACK是单个 TCP 头的合法组合RFC 793 明确允许。但四次挥手中被动关闭方B收到 A 的 FIN 后必须先发 ACK确认收到 FIN再等应用层调close()后才发自己的 FIN。因为ACK 是 TCP 层自动行为内核收到 FIN立即置sk-sk_shutdown | RCV_SHUTDOWN并调tcp_send_ack()发 ACKFIN 是应用层触发行为close()系统调用才会调tcp_close()进而发 FIN二者时机完全解耦。若强行合并意味着内核要替应用决定“何时关闭发送通道”违背分层原则。这也是为什么ss状态中B 端先变CLOSE_WAIT收到 FIN已发 ACK应用不close()就永远卡在此状态——这是设计不是 bug。3.3 TIME_WAIT 状态不是“连接没关干净”而是内核在为你兜底的 2MSL 保险TIME_WAIT常被妖魔化为“端口耗尽元凶”但它解决的是两个致命问题可靠终止最后 ACK 丢失时对方重发 FINTIME_WAIT端能再次发 ACK避免对方卡在LAST_ACK防止旧包干扰2MSLLinux 默认 60 秒确保网络中所有该连接的残留报文如延迟的 FIN、重复 ACK全部消失新连接才可复用相同五元组验证方法用ss -tan state time-wait sport :8080查看特定端口的TIME_WAIT连接。若数量异常高5000检查客户端是否短连接高频创建改用连接池如 HTTP/1.1Connection: keep-alive服务端是否主动关闭改为客户端关闭如浏览器发起 HTTP 请求是否启用了tcp_tw_reusesysctl net.ipv4.tcp_tw_reuse应为1避坑 / 常见问题 / 排查现象 1ss -s显示TIME-WAIT数量达 3 万netstat -ant | grep :8080 | wc -l却只有 200原因ss -s统计所有TIME_WAIT而netstat -ant默认只显示 IPv4 TCP且可能受netstat缓存影响。真实数量以ss -tan state time-wait | wc -l为准。现象 2开启tcp_tw_reuse1后NAT 网关后多个客户端访问同一服务偶发连接失败原因tcp_tw_reuse依赖tcp_timestamps1生成唯一cookie但在 NAT 下不同客户端的timestamp可能冲突导致内核误判为旧连接重放。解决方案NAT 环境禁用tcp_tw_reuse改用tcp_fin_timeout30缩短TIME_WAIT时长。现象 3ss -tan state established | wc -l结果远小于ulimit -n但服务仍报Too many open files原因ulimit -n限制进程所有 fd 总数包括日志文件、配置文件、epollfd 等。用lsof -p pid | wc -l查总 fdlsof -p pid -iTCP | wc -l查 TCP 连接数二者差值即非网络 fd。现象 4tcpdump port 8080抓包看到大量SYN但ss -tan state syn-sent | wc -l为 0原因ss的syn-sent状态仅统计本机发起的连接而tcpdump抓的是所有经过网卡的包。若你在服务端抓包看到的SYN是客户端发来的服务端状态应为syn-recvss -tan state syn-recv。现象 5netstat -s | grep -i segments retrans持续增长但ss -i的retrans为 0原因netstat -s统计内核启动以来的累计重传数而ss -i显示当前连接的瞬时重传次数。前者用于趋势分析如网络抖动加剧后者用于单连接排障。4. TCP 可靠传输与拥塞控制别信“TCP 保证 100% 可靠”看懂RTO与cwnd才知边界4.1 ARQ 协议不是理论是tcp_retransmit_timer()函数里跳动的脉搏TCP 的可靠传输本质是Stop-and-Wait ARQ 的增强版它不用等一个包 ACK 再发下一个效率低而是用滑动窗口让多个包“在路上”。核心机制在net/ipv4/tcp_timer.ctcp_retransmit_timer()主重传定时器超时触发tcp_retransmit_skb()tcp_rack_detect_loss()基于时间戳的快速重传RACK替代传统 3 次 DUPACKtcp_fastretrans_alert()处理 DUPACK若tp-dupacks 3立即重传tp-snd_una第一个未确认序号RTO重传超时计算绝非固定值。Linux 用 Jacobson/Karels 算法// RTT 样本更新 rtt measured_rtt; srtt srtt * 7/8 rtt * 1/8; // 平滑 RTT rttvar rttvar * 3/4 |rtt - srtt| * 1/4; // RTT 方差 rto srtt max(1000, 4 * rttvar); // RTO 至少 1 秒ss -i中的rtt:12.5ms rttvar:4.2ms即srtt和rttvarrto则为12.5 4*4.2 ≈ 29ms。当网络抖动rttvar暴涨rto自动拉长避免过度重传。4.2 滑动窗口不是“流量控制开关”而是发送方与接收方用win字段跳的双人舞窗口机制包含两层接收窗口rwnd由接收方在每个 ACK 中通过 TCP 头window size字段通告表示“我还能收多少字节”。ss -i的rwnd即此值。拥塞窗口cwnd发送方根据网络状况动态调整的“我能发多少”不受接收方限制。ss -i的cwnd即此值。实际发送上限是min(cwnd, rwnd)。当接收方应用读慢rwnd降为 0发送方停止发送ZeroWindowProbe机制除外当网络拥塞cwnd被tcp_cong_control()算法如cubic) 主动缩减。ss -i中cwnd:10表示当前拥塞窗口为 10 个 MSS如 MSS1448则约 14KB即最多发 10 个未确认分段。4.3 拥塞控制算法cubic不是“比reno更好”而是为高速广域网定制的数学模型Linux 4.9 默认拥塞算法是cubic其核心公式W_cubic C * (t - K)^3 W_max其中W_max是上一次拥塞事件前的最大窗口K cbrt(W_max / C)是拐点时间。cubic的特点是激进探测在W_max附近快速增大cwnd抢占带宽平滑收敛一旦丢包cwnd立即减半W_max cwnd / 2然后按立方根曲线缓慢恢复对比reno加性增、乘性减renocwnd 1每 ACKcwnd / 2丢包cubiccwnd按时间立方增长不依赖 ACK 密度更适合高延迟链路如跨洋光纤验证算法ss -i中cubic字样即当前算法。用echo cubic /proc/sys/net/ipv4/tcp_congestion_control切换再用iperf3 -c server -t 60 -P 4测吞吐观察cwnd增长曲线差异。5. TCP 与 UDP 的生死抉择不是“可靠 vs 快”而是socket()调用后内核走的两条完全不同的路5.1 UDP 的“无连接”真相sendto()发一个包内核只做三件事UDP 的“无连接”指无需握手建立状态但内核仍需完成路由查找ip_route_output_ports()根据目标 IP/端口查路由表得dst_entry分片若包长 dst_mtu()调ip_fragment()分片IPv4或返回EMSGSIZEIPv6发包ip_local_out()将包交给dev_queue_xmit()最终ndo_start_xmit()送网卡全程无状态机、无重传、无排序。ss -u显示的UNCONN状态只是表示 socket 未connect()绑定对端sendto()仍可发包。strace看sendto()系统调用返回即完成内核不关心对方是否收到。5.2 TCP 的“面向连接”代价每个ESTABLISHED连接都是内核的一笔内存负债一个 TCP 连接在内核中至少占用struct sock约 1.5KB含发送/接收队列、定时器、拥塞控制结构体sk_buff缓冲区每个未确认分段一个sk_buff约 256 字节request_sock握手期间约 200 字节10 万个连接 ≈ 150MB 内存。而 UDP 10 万个sendto()调用内存开销几乎为 0仅sk_buff临时分配。所以harbor 推送失败 get https://192.168.209.133/v2/: dial tcp 192.168.209.133:这类错误90% 是服务端ulimit -n不足或net.core.somaxconn太小而非网络不通。5.3 何时必须用 TCP何时必须用 UDP看这三行代码决策树# 场景 1需要严格顺序、不丢包、不重复 if application_needs_exactly_once_delivery and order_matters: use_tcp() # 如 HTTP, MySQL, Kafka 生产者 # 场景 2实时性 可靠性容忍少量丢包 elif latency_sensitive and can_tolerate_loss: use_udp() # 如 VoIP, 视频会议, DNS 查询 # 场景 3既要可靠又要低延迟且能自定义重传 elif custom_retrans_logic_needed and control_over_every_packet: use_udp_with_application_level_arq() # 如 QUIC, WebRTC DataChanneliperf3 -u测 UDP 流量正常TCP 却断流立刻查ss -i看cwnd是否为 1拥塞控制卡死cat /proc/net/snmp | grep -i Tcp:看RetransSegs是否飙升ethtool -S eth0 | grep -i error\|drop查网卡硬件丢包6. 从netstat到eBPF用bcc工具链给 TCP 协议栈装上实时监控探针6.1ss和netstat已过时bpftool和tcplife才是现代排障标配netstat读/proc/net/tcpss读AF_NETLINK而bccBPF Compiler Collection直接注入 eBPF 程序到内核零开销监控。安装bcc-tools后tcplife追踪每个 TCP 连接的生命周期建立、关闭、持续时间# 监控 8080 端口所有连接 sudo tcplife -p 8080 PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS 1234 nginx 192.168.1.100 8080 10.0.2.15 54322 12 8 2345tcpconnect捕获所有connect()调用定位服务发现失败tcpretrans实时打印重传事件比netstat -s精确到毫秒tcplife的输出MS列即连接存活毫秒数若大量连接MS 100说明是高频短连接应优化为长连接。6.2 用bpftrace一行命令揪出TIME_WAIT泛滥的罪魁祸首# 追踪所有进入 TIME_WAIT 状态的连接并打印其五元组 sudo bpftrace -e kprobe:tcp_time_wait { printf(TIME_WAIT: %s:%d - %s:%d\n, str(args-sk-__sk_common.skc_rcv_saddr), args-sk-__sk_common.skc_num, str(args-sk-__sk_common.skc_daddr), args-sk-__sk_common.skc_dport); }运行后立刻看到哪些客户端 IP/端口在疯狂建连又断开精准定位问题服务。6.3 最后的血泪经验从那以后我每次上线新服务都强制走一遍这三步ss -s看全局连接状态分布TIME-WAIT 5000ESTAB是否接近ulimit -nss -i src :port抽样 5 个连接cwnd是否合理10rtt是否稳定抖动 20%retrans是否为 0sudo tcplife -t 10监控 10 秒确认连接建立/关闭节奏符合预期无异常高频短连接这三步加起来不到 30 秒却能避开 80% 的线上网络故障。曾经有个微服务ss -s显示TIME-WAIT2 万我以为是流量突增结果tcplife一跑发现是某 SDK 每次 HTTP 调用都新建连接、不复用修复后TIME-WAIT归零。希望帮到你。本文还有配套的精品资源点击获取
返回列表