
简述 TCP/IP:从一个数据包说起
打开浏览器,输入一个网址,页面很快出现在眼前。这件事熟悉得近乎无聊,但若继续追问,问题马上就多了起来:电脑怎么知道服务器在哪?数据为什么没有走错门?跨过 Wi-Fi、光猫、运营商和海底光缆之后,对方又如何确认内容没有缺斤少两?
很多回答会祭出一张 OSI 七层模型图,再从物理层一路背到应用层。图当然没错,只是背完之后,人仍然可能解释不了一个更实在的问题:当网络会丢包、乱序、重复、限速甚至中断时,TCP/IP 到底替应用做了什么,又故意没做什么?
先给出一个不太严谨但好用的答案:IP 负责把一个个数据报尽力送到目的主机,TCP 或 UDP 负责让主机上的具体应用交换数据。IP 像跨城物流网络,只认地址并不断转运;端口像园区里的房间号;TCP 则额外提供签收、排序、补发和速度协商。至于包裹一定能到、何时能到、沿哪条路到,网络从来没有承诺。
这正是 TCP/IP 最重要,也最容易被忽视的气质:底层保持简单,只提供尽力而为的分组传输;需要多强的可靠性,由端系统和应用自己决定。
一、TCP/IP 不是一条协议
从一张最小地图开始
TCP/IP 通常指一组共同工作的互联网协议,而不是一个名叫「TCP/IP」的巨型协议。工程上常把它画成四层:
| TCP/IP 层次 | 常见协议或技术 | 主要回答的问题 |
|---|---|---|
| 应用层 | HTTP、DNS、SSH、SMTP | 数据表示什么,双方按什么规则交谈 |
| 传输层 | TCP、UDP、QUIC | 数据交给哪个进程,是否需要可靠、有序传输 |
| 网际层 | IPv4、IPv6、ICMP | 数据报要送到哪台主机,下一跳往哪里走 |
| 链路层 | Ethernet、Wi-Fi、PPP | 在当前这段链路上,帧如何交给相邻节点 |
OSI 七层模型更适合讲概念边界,TCP/IP 四层模型更接近互联网协议实际落地的方式。两者不是两支互相开除学籍的门派,也不必强行逐层对号入座。比如 TLS 究竟算表示层、会话层还是应用层,争论半天不会让握手快一毫秒。
假设笔记本通过 Wi-Fi 请求一台 Web 服务器。HTTP 生成请求,TCP 把字节流拆成若干段,IP 为它们写上源地址和目的地址,Wi-Fi 再将 IP 数据报装进帧,交给当前网络的下一跳。到了路由器,旧的链路层帧会被拆掉;路由器查看 IP 首部、选择出口,再套上适合下一段链路的新帧。
这个过程叫封装与解封装。每层只处理自己的头部,向上一层隐藏一部分细节。HTTP 通常不用知道数据走了光纤还是 5G,IP 也不必理解载荷是一张图片还是一句聊天消息。分层不是为了让教材多画几只方框,而是为了隔离变化:Wi-Fi 可以升级,应用协议无需跟着重写;HTTP 可以从文本换成二进制帧,路由器仍然只转发 IP 数据报。
为什么是「分组交换」?
传统电话网络会为一次通话建立相对固定的电路。计算机通信却经常突发:加载网页时猛传一阵,读页面时又安静下来。如果为每台沉默中的电脑长期保留整条线路,利用率未免太奢侈。
分组交换把数据切成较小的包,让许多通信共享链路。每个包可以被路由器独立存储、排队和转发。当某条路径故障时,路由协议还可能让后续流量改走其他路径。代价也很明确:共享意味着竞争,竞争意味着排队;队列可能塞满,数据包可能丢失;不同路径和排队时间还会带来乱序与时延抖动。
互联网没有在网络核心里为每条连接保存完整的可靠传输状态,而是把重组、确认和重传等工作主要放在端系统。历史上,早期 TCP 同时承担主机间寻址与端到端传输,后来才拆分成 IP 与 TCP。1974 年的 RFC 675 描述了早期 Internet Transmission Control Program;1980 年的 RFC 760 与 1981 年的 RFC 793 则分别规范了 IP 和 TCP。今天的 TCP 规范已经由 RFC 9293 更新。
重点不在背年份,而在设计取舍:核心网络只管尽力转发,复杂的可靠性放在真正需要它的两端。 中间设备因此可以服务海量、彼此不认识的应用,新应用也不必先请求全世界的路由器升级。
二、IP:地址、数据报与尽力而为
IP 地址不是设备身份证
IP(Internet Protocol,网际协议)提供两项核心能力:给接口分配可路由的地址,以及让数据报跨越多个网络逐跳转发。IPv4 地址长 32 位,例如 192.0.2.10;IPv6 地址长 128 位,例如 2001:db8::10。示例中的两个网段都专门用于文档,不应该拿来假装真实公网地址。
把 IP 地址叫作「电脑的身份证」很方便,也很容易带偏。地址通常属于一个网络接口在特定网络中的位置,而非某台设备永久不变的身份。笔记本从家里连到公司,地址会变;手机在 Wi-Fi 与蜂窝网络间切换,地址也会变;同一台服务器可以拥有多个地址,同一个公网地址后面也可以藏着许多设备。
IP 地址由网络前缀和接口部分组成。192.0.2.0/24 中的 /24 表示前 24 位是网络前缀,路由器可以据此前缀汇总路由,不必为全球每一台主机维护一行记录。IPv6 同理,只是地址空间大得多,局域网中常见 /64 前缀。
IPv4 与 IPv6 也不是「地址长度翻四倍」这么简单。IPv6 去掉了首部校验和,不再允许沿途路由器分片,并以扩展首部承载可选功能;邻居发现取代了 IPv4 世界常见的 ARP。完整规范见 RFC 8200。不过它们解决的核心问题仍然一致:让无连接的数据报穿过一系列异构网络。
路由器到底做什么?
发送主机先判断目的地址是否位于本地链路。如果在,就寻找对方的链路层地址并直接交付;如果不在,就把帧交给默认网关。路由器收到数据报后,大致会执行这些动作:
- 检查 IP 首部,并把 IPv4 的 TTL 或 IPv6 的 Hop Limit 减一;
- 在转发表中寻找与目的地址匹配的最长前缀;
- 选择下一跳和出口接口,重新封装链路层帧;
- 若没有可用路由、数据报太大或生存时间归零,则丢弃,并可能返回 ICMP 错误。
TTL 名叫 Time To Live,实际在今天的 IP 转发里按跳数递减。它防止路由环路让数据报永远兜圈。traceroute 正是利用逐渐增大的 TTL 或 Hop Limit,以及路由器返回的 ICMP 超时报文,探测路径上的节点。
转发表回答的是「下一跳是谁」,路由协议则负责让这些表如何形成。一个组织管理的网络通常称为自治系统(Autonomous System,AS),拥有自治系统号 ASN。AS 内部可以使用 OSPF、IS-IS 等协议计算路径;AS 之间通常借助 BGP 交换可达前缀与策略。BGP 选择的不一定是物理距离最短的路径,商业关系、流量工程与策略同样会影响结果。互联网不是一张由中央调度的完美地图,而是许多网络彼此协商出来的结果。
尽力而为,不等于随缘摆烂
IP 是无连接、尽力而为的数据报服务。它不保证送达、不保证只送一次、不保证顺序,也不保证时延上限。路由器仍会认真转发,只是协议没有对结果作出这些承诺。
这条边界极其重要。若应用必须精确复制一份数据库备份,它要在上层确认完整性并处理重试;若视频会议更在意「现在」而不是补齐三秒前的一帧,就可能主动容忍少量丢包。可靠并非越多越好。迟到的数据有时已经失去价值,而重传会占用带宽、扩大延迟。
数据报还受 MTU(Maximum Transmission Unit,最大传输单元)限制。以太网常见 MTU 是 1500 字节,但隧道、VPN 与其他链路可能更小。IPv4 路由器在特定条件下可以分片,IPv6 路由器则不会;发送端需要根据 ICMP 反馈等信息调整包大小。若防火墙粗暴地屏蔽所有 ICMP,路径 MTU 发现可能失效,连接便会出现「握手成功,小包能过,大包卡死」的古怪症状。安全策略若只会一刀切,最后往往先切到自己。
三、TCP:把不可靠的数据报变成可靠字节流
一条连接由什么识别?
IP 把数据送到主机,传输层还要把数据交给具体进程。TCP 与 UDP 都使用 16 位端口号。一条 TCP 连接通常由源 IP、源端口、目的 IP、目的端口这个四元组识别;再加上传输协议,网络设备可以区分同样地址和端口上的 TCP 与 UDP 流量。
端口不是服务器,也不是应用本身,只是操作系统用于分用报文的编号。服务端通常监听固定端口,客户端则由操作系统分配临时端口。443 常用于 HTTPS,不代表经过它的内容天然安全;同样,把 SSH 换到一个冷门端口只能减少扫描噪声,不能代替认证与补丁。
TCP 面向连接,但这里的「连接」不是先拉出一根专用电线,而是通信双方在内存中维护一组状态:序列号走到哪里,哪些字节已经确认,窗口还有多大,哪些数据需要重传。
为什么要三次握手?
建立连接时,客户端发送 SYN 并带上初始序列号;服务端以 SYN-ACK 确认它,同时给出自己的初始序列号;客户端再发 ACK。三次交换让双方确认彼此的收发路径可用,协商部分参数,并同步两个方向各自独立的序列号空间。
所以「两次不就够了吗」漏掉了服务端也需要知道自己的 SYN 已被客户端收到。至于为什么不是四次,是因为服务端可以把确认客户端 SYN 的 ACK,与自己的 SYN 合并发送。三次并非某种神秘仪式,而是双向状态同步后的自然结果。
握手也意味着成本。新连接至少需要一个往返时间才能确认建立,在高延迟网络里相当显眼。TCP Fast Open 可以在特定条件下减少重复连接的等待,TLS 也有会话恢复,但这些优化都有部署、安全和兼容性边界,不能把 RTT 施法消失。
连接并不只有「建立」和「关闭」
三次握手图很直观,却容易让人误以为 TCP 连接只有开、关两档。实际上,每个端点都运行着一套状态机。服务器调用 listen() 后进入 LISTEN;客户端发出 SYN 后进入 SYN_SENT;服务端收到请求并回送 SYN-ACK 时进入 SYN_RECEIVED;直到双方确认序列号,才进入 ESTABLISHED。
关闭过程又会经过 FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、LAST_ACK、CLOSING 或 TIME_WAIT。状态不是为了折磨背面试题的人,而是在回答三个具体问题:这个方向还能不能发数据?已经发送的控制报文是否得到确认?延迟到达的旧报文是否可能污染下一条连接?
状态机里还有两个不常见、但很能说明 TCP 对称性的场景。
同时打开 (Simultaneous Open) 是双方几乎同时主动发送 SYN。这要求两端事先知道对方地址和端口,现实中很少见,但 TCP 明确支持。双方会从 SYN_SENT 转到 SYN_RECEIVED,各自用 SYN-ACK 确认对方的初始序列号,最终只建立一条连接,并不会因为两边都主动连接就长出两条。
同时关闭 (Simultaneous Close) 则是双方几乎同时发送 FIN。两端都会先进入 FIN_WAIT_1,收到对方的 FIN 后进入 CLOSING 并回复 ACK,确认到达后各自进入 TIME_WAIT。所谓「四次挥手」只是最常见路径,不是状态机唯一允许的剧本。
这些边角场景平时不必死记。真正排查故障时,状态却很有用:大量 SYN_RECEIVED 可能意味着握手没有完成或遭遇 SYN Flood;大量 CLOSE_WAIT 往往说明对端已经关闭,而本地应用迟迟没有关闭 socket;大量 FIN_WAIT_2 则要检查另一端为何一直不发送 FIN。状态描述的是事实,先找谁在等待谁,再决定是否需要调参数。
TCP 可靠的到底是什么?
TCP 提供的是可靠、有序、全双工的字节流。应用写入的两段数据,在接收端未必以两次读取原样出现。一次 write() 可能被拆进多个 TCP 段,多次 write() 也可能被一次 read() 读到。TCP 保证字节顺序,不保留应用消息边界。
因此应用协议必须自己定义边界,例如固定长度、分隔符、长度前缀,或者像 HTTP 那样通过首部和帧格式描述内容。把「一个 TCP 包」当成「一条业务消息」,是经典且顽固的粘包误解。所谓粘包不是 TCP 把消息粘坏了,而是应用从一开始就没有告诉字节流哪里算一条消息。
可靠性主要来自几套配合的机制:
- 序列号标记字节在流中的位置,使接收端能够排序和发现缺口;
- 确认应答告诉发送端哪些字节已经收到;
- 校验和发现传输中的部分损坏,但它不是密码学完整性保护;
- 重传计时与快速重传在确认迟迟不来或出现丢包迹象时重新发送;
- 接收窗口限制未确认数据量,避免发送端淹没接收端缓冲区。
确认丢了,不代表数据丢了。发送端超时后可能重发一份,接收端依靠序列号识别重复数据,并再次确认。反过来,TCP 收到 ACK 也只说明对端 TCP 栈接收了相应字节,不代表对方业务已经处理、数据库已经提交,更不代表用户看见了结果。需要「订单只创建一次」之类的业务保证,应用仍要设计请求 ID、幂等、事务与结果查询。
TCP 的校验和同样不是安全功能。它旨在检测偶发传输错误,无法抵挡主动篡改。机密性、身份认证与防篡改通常由 TLS 等上层协议提供。
流量控制与拥塞控制不是一回事
这两个名字经常被搅在一起。
流量控制保护接收端。接收端通过通告窗口告诉发送端:「我的缓冲区还装得下多少。」应用读取太慢时,窗口会缩小,发送方随之减速。
拥塞控制保护网络。即使接收端有 64 MB 空闲缓冲,也不代表中间那条共享链路吞得下这些流量。发送端维护拥塞窗口 cwnd,依据确认、丢包、时延等信号估计网络承载能力。实际可在途发送的数据量大致受接收窗口与拥塞窗口中较小者限制。
TCP 连接通常从较谨慎的速率开始,通过慢启动快速探测可用容量;到达阈值后进入更克制的拥塞避免。检测到拥塞时则减少发送。经典原则由 RFC 5681 描述,现代操作系统还会实现 CUBIC、BBR 等算法。CUBIC 主要根据丢包调整窗口,BBR 则尝试估计瓶颈带宽与往返传播时间。它们不是简单的「新算法一定更快」:吞吐、排队延迟、公平性、链路特征和实现版本都会改变结果。
这也解释了「TCP 加速」为何不能等价为把 cwnd 调大。若所有人遇到拥塞都拒绝减速,结果不是大家一起起飞,而是队列爆满、丢包增加、重传挤占链路,最终一起堵死。好的拥塞控制会在能快时充分利用带宽,也会在该慢时老实让步。
带宽与速度也不能画等号。单条长肥管道上的 TCP 连接,需要足够大的在途数据量才能填满链路。其带宽时延积约为:
BDP = 带宽 × 往返时间一条 1 Gbit/s、RTT 为 100 ms 的路径,带宽时延积约为 100 Mbit,即 12.5 MB。若窗口远小于它,链路有再高的标称带宽也跑不满;若窗口够大但丢包频繁,拥塞控制又会收缩。性能排查不能只盯着测速数字,还要看 RTT、丢包、窗口、应用读写和中间设备。
关闭连接为什么看起来麻烦?
TCP 是全双工的,两个方向可以独立关闭。常见的「四次挥手」来自一方发送 FIN,另一方先确认;等它也没有数据要发时,再发送自己的 FIN,最初的一方再确认。ACK 与 FIN 若时机合适也可能合并,所以抓包不保证永远整整齐齐四个包。
主动关闭的一方通常会进入 TIME_WAIT,等待一段时间,以便重传最后的 ACK,并避免旧连接中的延迟报文污染后来复用相同四元组的新连接。服务器上看到大量 TIME_WAIT 不等于 TCP 坏了,应先判断谁主动关闭、连接是否被无谓地频繁创建,以及端口和资源是否真的耗尽。把状态持续时间随手改成极小值,属于尚未确诊就先给协议做截肢。
这里的等待通常写作 2MSL。MSL(Maximum Segment Lifetime,最大报文段生存时间)表示一个 TCP 报文段在网络中被认为仍可能存活的最长时间。等待两倍 MSL,主要解决两个问题。
其一是可靠结束。如果主动关闭方发送最后一个 ACK 后立刻删除连接状态,而这个 ACK 恰好丢失,被动关闭方会重发 FIN。保留 TIME_WAIT 状态,主动关闭方才能识别这个重发并再次回复 ACK,让对方正常收尾。
其二是隔离旧连接。四元组未来可能被重新使用;如果旧连接的延迟报文还在网络里漂,新连接又已经建立,这些旧字节就可能撞进新的序列号空间。2MSL 给两个方向的遗留报文留出过期时间。
不同操作系统对 MSL 和 TIME_WAIT 的具体实现时间并不完全相同,应用也不应该假设一个跨平台固定秒数。工程上更重要的是减少无谓短连接、正确复用连接、让服务端和客户端的主动关闭策略符合负载。看到 TIME_WAIT 就清理,像是因为门卫在核对退场名单,就认定门卫阻碍了散场。
四、UDP:少做一些,反而留下更多可能
UDP 的报文头很小,只包含源端口、目的端口、长度和校验和。它提供面向数据报的分用,却不建立连接,不保证送达、顺序和去重,也不内置流量控制与拥塞控制。RFC 768 对它的定义短得惊人,像一张不愿加班的协议说明书。
「UDP 比 TCP 快」只说对了一小半。UDP 没有 TCP 握手与队头等待,协议机制更轻,应用能够自行决定哪些数据值得重传,因此适合实时音视频、在线游戏、DNS 查询等场景。但如果应用同样需要可靠、有序、拥塞友好的传输,就得自己补上相应机制。自己补得不成熟,UDP 并不会替你背锅。
UDP 保留消息边界:一次发送对应一个数据报。不过数据报过大可能触发 IP 分片,而任意一个分片丢失都可能导致整份数据报无法重组。工程上通常应控制报文大小,结合路径 MTU 设计,不能仗着 UDP 长度字段够大就发一个硕大的包。
更重要的是,不用 TCP 不等于可以无视拥塞控制。一个不顾网络反馈、全速喷射 UDP 的应用,可能伤害同一链路上的所有流量。QUIC 正是一个很好的反例:它运行在 UDP 之上,却自己实现可靠传输、流量控制、拥塞控制、连接迁移与加密。UDP 提供的是用户态构建新传输协议的空间,不是逃离公共秩序的许可证。
如何选?若需要兼容广泛、可靠有序的字节流,TCP 通常仍是朴素而稳妥的默认项。若应用天然按消息工作,更在意时效,能够容忍丢失或有能力自己定义恢复策略,UDP 才可能更合适。先列需求,再选语义,别因为「快」这个字就冲动下单。
五、NAT 与现实中的互联网
私有地址为什么能访问公网?
IPv4 地址有限,家庭和企业网络普遍使用 RFC 1918 定义的私有地址,例如 10.0.0.0/8、172.16.0.0/12 与 192.168.0.0/16。这些地址不会在公网中全局路由。出口路由器通过 NAT(Network Address Translation,网络地址转换)改写地址,通常还结合端口转换,让许多内网连接共享一个公网 IPv4 地址。
例如内网主机从 192.168.1.20:53142 访问服务器 203.0.113.8:443,出口设备可能将源地址改写为 198.51.100.7:40001,并记下一条映射。响应回来后,它再根据映射把数据交还给内网主机。
NAT 缓解了 IPv4 地址压力,也顺手隐藏了内部拓扑,但它打破了原本更直接的端到端可达性。外部主机通常无法凭空发起到内网节点的连接;P2P、语音通话和游戏需要端口映射、STUN、TURN、ICE 等穿透或中继手段。运营商级 NAT(CGNAT)还会让更多用户共享公网地址,使端口资源、日志追踪和入站服务部署更加麻烦。
NAT 也不是防火墙的同义词。状态映射常带来类似「拒绝意外入站」的效果,但真正的访问控制应该由明确的防火墙策略承担。IPv6 提供充足地址,通常不需要为了节省地址部署 NAT;这也不意味着 IPv6 主机应当裸奔,入站过滤、最小暴露和主机防火墙照样不能少。
一次连接为什么会被中途掐掉?
教科书里的 TCP 似乎只有两端,现实路径上却可能经过 NAT、防火墙、负载均衡器、代理、VPN 和运营商设备。这些中间设备往往保存连接状态并设置空闲超时。双方 TCP 栈以为连接还在,中间的 NAT 映射却可能已经消失;下一次写入才发现连接早成了「半死不活」。
因此长连接需要同时考虑应用心跳、TCP keepalive、中间设备超时、重连退避和请求幂等。心跳太慢,状态可能被回收;太快,则移动设备耗电、服务器负担和网络开销都会上涨。没有万能的 30 秒,只能依据实际路径与业务容忍度权衡。
同理,公网看到的源 IP 也未必对应一个人或一台设备。NAT、代理、VPN 与移动网络都可能让多人共用地址,IPv6 隐私地址又可能定期变化。把 IP 当作稳定用户 ID,会误伤,也容易被绕过。它可以是风控信号,却很少适合充当唯一身份依据。
六、几件最容易说错的事
讲完机制,再清理几句流传很广的「网络常识」。
「TCP 不丢包,UDP 才丢包」
底层网络对两者都可能丢包。TCP 通过确认与重传,尽量向应用隐藏丢失;UDP 默认把处理权留给应用。若 TCP 连接最终超时或重置,应用一样收不到完整数据。区别在传输语义,不在网络是否突然对 TCP 格外善良。
「Ping 不通,就是服务器挂了」
ping 使用 ICMP Echo。目标主机、防火墙或路径设备可能过滤它,但仍允许 TCP 443。反过来,能 ping 通只证明某种 ICMP 往返成功,不证明 Web 服务、数据库或业务逻辑正常。诊断应当测试真正关心的协议与端口,同时观察 DNS、路由和应用日志。
「TCP 是一问一答」
TCP 是全双工字节流,双方可以同时连续发送。请求—响应是许多应用协议选择的交互模型,不是 TCP 强制的礼仪。WebSocket、SSH 等连接就可以让任意一端主动发数据。
「一个连接只走一条固定路线」
TCP 用端点与序列号维持状态,不要求每个 IP 数据报经过完全相同的路由。路由变化可能导致乱序、RTT 改变乃至临时中断,只要数据最终抵达,TCP 仍可重组。现实中的 NAT 和状态防火墙可能要求双向流量经过同一设备,那是中间设备引入的约束,不是 IP 的根本承诺。
「丢包一定说明带宽不够」
拥塞会丢包,但无线干扰、物理链路错误、设备故障、MTU 黑洞和策略限速也会丢包。反过来,队列缓冲很大时,链路拥塞可能暂时不丢包,却产生极高延迟,也就是常说的 Bufferbloat。只看丢包率会漏掉一半故事,应结合 RTT 分布、抖动、重传、队列和路径变化判断。
「TCP 保证业务只执行一次」
TCP 只保证连接内字节流的可靠有序交付。客户端发送请求后连接中断,它可能不知道服务端是否已经执行。此时重试可能造成重复扣款、重复发消息。所谓「恰好一次」往往需要应用层幂等键、去重记录、事务边界与可查询结果共同完成,不能甩给 TCP。
七、工程上真正该关心什么?
网络问题最爱伪装成别的问题:数据库查询突然慢了,可能是丢包触发重传;HTTPS 偶尔卡死,可能是路径 MTU 发现失败;长连接半夜集体断开,可能是 NAT 超时;吞吐上不去,可能是接收窗口、RTT 或应用读取速度,而不只是出口带宽。
排查时,我更倾向从边界出发,而不是一上来魔改内核参数:
- 先定义现象。 是无法解析、无法建连、握手慢、首字节慢、吞吐低,还是连接被重置?
- 再定位层次。 DNS 有结果吗?路由可达吗?TCP 握手完成吗?TLS 失败在哪里?应用是否及时响应?
- 保留两端证据。 单边抓包只能看到单边事实。条件允许时,对照客户端、服务端与中间设备的时间线。
- 观察分布而非均值。 P50 很漂亮,P99 可能已经烂透。慢连接常被平均值悄悄埋掉。
- 最后再调参数。 缓冲区、队列、拥塞算法和 keepalive 都有代价。先证明瓶颈在哪,再动手。
协议选型也一样。TCP 省下了大量可靠传输工作,却可能因丢失造成连接内的等待;UDP 自由、轻量,却把恢复、公平与安全责任推给应用;更多并发连接可能提高短期吞吐,也会增加握手、状态与拥塞竞争;长连接摊薄握手成本,却必须处理失效检测与状态回收。
没有「最好」的传输协议,只有与你的失败模型相匹配的语义。文件传输不能少一个字节,晚一点通常还能接受;实时语音宁愿丢掉过期音频,也不愿为它停下来重传;交易请求不仅要送达,还要在不确定结果时安全重试。这些差异才是选型的起点。
回到开头的问题:页面为什么能出现在眼前?不是因为网络从不出错,而是因为每一层都承担了有限而清楚的责任。链路把帧送到邻居,IP 尽力把数据报送往目的主机,TCP 在不可靠网络上整理出可靠字节流,TLS 保护通信,HTTP 再定义请求与响应。某一层没有承诺的东西,就由需要它的上一层补上。
TCP/IP 的强大,不在于它替应用解决了所有问题,而在于它把问题切成了可以独立演进的边界。 看懂这些边界,遇到网络故障时就不会只剩一句「可能是网不好」;做工程取舍时,也不会把可靠、快速和安全当成协议白送的赠品。
