数据是怎么从你家电脑跑到服务器的呢?

26 阅读8分钟

你在浏览器敲了一个回车,数据是怎么跑到千里之外的服务器的?中间经历了什么?为什么有的快有的慢?

今天用寄快递打电话的故事,聊聊 TCP/IP 协议栈。


原文地址

墨渊书肆/数据是怎么从你家电脑跑到服务器的呢?


没有协议栈,网络请求会怎样?

你寄一个快递,没有快递公司行不行?

你:我要寄苹果给上海的朋友
快递员:苹果是什么?寄去哪?用什么车?
你:...我不知道怎么回答

网络请求也一样——没有协议栈,计算机之间根本"听不懂"对方说的话。

协议栈就是一套"快递公司规则":规定怎么打包、怎么运输、怎么签收。


TCP/IP四层模型

TCP/IP协议栈分为四层,从上到下:

TCP/IP 四层模型

┌───────────────────┐
   应用层            HTTP、FTP、SMTP、DNS
   传输层            TCP(可靠)、UDP(快速)
   网络层            IP 地址路由
   网络接口层        MAC 地址、网卡驱动
└───────────────────┘

比喻:
应用层  快递单上的寄件人/收件人
传输层  快递公司承诺"保证送达"
网络层  快递在机场按航班发货
网络接口层  快递小哥装车送到机场

数据封装过程

数据从上层到下层,每层都会"打包":

数据封装过程:

应用层:你好,我要访问 example.com
          [加上TCP头]
传输层:+ SYN标志 + 序列号
          [加上IP头]
网络层:+ 源IP + 目标IP
          [加上帧头帧尾]
网络接口层:二进制流 10101010...

如同快递打包:

你买的商品 → 商家加包装盒 → 快递公司加外包装 → 装车出发

TCP三次握手

为什么需要"握手"?

你打电话给朋友:

你:喂?(试探对方在不在)
对方:在的!(回应)
你:我要说事情了!(确认连接)

TCP三次握手就是这个道理——建立可靠连接需要双方确认对方"能收能发"

三次握手过程

三次握手过程:

Client ──── SYN ────→ Server
         ISN=100

Client ←── SYN+ACK ── Server
         ISN=300, ACK=101

Client ──── ACK ────→ Server
         连接建立完成
握手方向标志作用
第一次Client → ServerSYN发起连接请求
第二次Server → ClientSYN + ACK同意连接,告知自己的序列号
第三次Client → ServerACK确认收到,连接建立

序列号的作用

每个数据包都有一个序列号(Sequence Number),用于:

  1. 保证有序:接收方按序号重组数据
  2. 避免重复:如果数据包被重复发送,接收方能识别并丢弃
  3. 可靠性:丢包时能定位丢了哪个包

ISN(初始序列号)

ISN 不是固定的 0 或 1,而是动态生成的随机数,目的是防止历史数据包残留干扰新连接。

如果 ISN 是固定的,攻击者可能重放旧的数据包来伪造连接。动态 ISN 让攻击者难以预测每次连接的起始序列号。


UDP:更快但不保证

UDP和TCP的区别

特性TCPUDP
连接方式面向连接(三次握手)无连接
可靠性可靠(丢包重传)不可靠(不管丢不丢)
有序性保证有序不保证有序
传输速度较慢
数据包大小无限制(流式)有上限(一般64KB)
使用场景网页、邮件、文件传输视频通话、直播、DNS查询

UDP工作方式

你(北京)→ 发送UDP数据包  上海服务器
               
               
        发了就不管了
        丢没丢不知道
        到了没有不知道

如同发短信——发出去就完事了,对方收没收不知道。

UDP适用场景

  • 视频通话:偶尔卡顿比延迟好忍受
  • 直播:丢几帧画面无伤大雅
  • DNS查询:数据包小,丢了大不了重试一次
  • 在线游戏:实时性 > 可靠性

TCP四次挥手

为什么是四次?

打电话结束:

你:我说完了(我要挂电话了)
对方:好的,等我一下...(等对方处理完)
对方:我这边也好了(对方也准备挂)
你:好的,再见(确认关闭)

TCP四次挥手就是这个道理——双方都要确认数据发完了才能关闭

四次挥手过程

四次挥手过程:

Client ──── FIN ────→ Server
         我发完了

Client ←── ACK ──── Server
         收到,但等我发完

Client ←── FIN ──── Server
         我也发完了

Client ──── ACK ────→ Server
         再见
挥手方向标志作用
第一次Client → ServerFIN发起关闭请求
第二次Server → ClientACK确认收到,但可能还有数据要发
第三次Server → ClientFINServer也发完了,请求关闭
第四次Client → ServerACK确认关闭,连接正式断开

为什么第二次和第三次不合并?

TCP半关闭机制:

Client:我不发数据了,但还能收
Server:好的,你先别收,等我发完
(Server发完剩余数据)
Server:我也发完了
Client:好的

第二次只回复 ACK 表示"收到了,但我还没准备好",等数据发完才发 FIN。

为什么需要TIME_WAIT?

四次挥手完成后,客户端并不会立即关闭,而是进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,通常为60秒)才真正关闭。

为什么要等?

  1. 确保对方收到最后的ACK:如果第四次挥手的 ACK 丢失,对方会重发 FIN,客户端需要处于 TIME_WAIT 才能重新发送 ACK
  2. 让旧数据包在网络中消散:确保本次连接的所有数据包都在网络中消失,避免影响新连接
TIME_WAIT 流程:

Client ──── FIN ────→ Server
Client ←── ACK ──── Server
Client ←── FIN ──── Server
Client ──── ACK ────→ Server
   
   TIME_WAIT (等待2MSL)
   
   关闭

滑动窗口:提高传输效率

原始的停等协议

最早的TCP,每发一个包都要等对方确认:

发包1  等待ACK  发包2  等待ACK  发包3  ...

问题:大部分时间都在等ACK,效率很低

滑动窗口原理

滑动窗口允许一次发送多个包,不需要等每个都确认:

滑动窗口(窗口大小=3):

发送:[包1][包2][包3]  等待确认
收到ACK1后滑动:
发送:[包2][包3][包4]  继续发送
收到ACK2后滑动:
发送:[包3][包4][包5]  ...

窗口大小动态调整

TCP会根据网络状况动态调整窗口大小:

网络状况窗口大小说明
网络通畅大窗口一次多发,提高吞吐量
网络拥塞小窗口少发点,别堵住
丢包严重最小窗口逐个确认,确保可靠

这就是拥塞控制的核心思想。


TCP重传机制

超时重传

发送数据包后启动计时器,超过时间没收到ACK就重发:

超时重传:发包1  计时器  超时  重发包1  收到ACK

问题是:超时时间设太长会等很久,设太短会频繁重发。

快速重传

当收到3个重复的ACK时,说明后面的包丢了,不等超时直接重传:

发送:[包1][包2][包3][包4][包5]
接收:包1收到,包2收到,包3丢了
收到包4/5/6 → 都发ACK2(还是要包2)
发送方:收到3个ACK2 → 快速重传包2

SACK(Selective Acknowledgment)

传统ACK只能告诉你"我收到了包1,包2丢了"。

SACK可以告诉你"包1收到了,包2丢了,但包3、4、5也收到了":

收到SACK信息后,发送方只需要重传丢失的包2,无需重传包3、4、5,大幅减少不必要的重传。


TCP拥塞控制

慢启动

连接刚建立时,窗口从小开始指数增长:

窗口:1 → 2 → 4 → 8 → 16 → ...

如同新手开车:先慢慢开,熟悉路况后再加速。

拥塞避免

窗口达到阈值后,改为线性增长:

窗口:16 → 17 → 18 → 19 → ...

拥塞发生

事件处理方式
超时丢包窗口降回1,重新慢启动
快速重传窗口减半,然后线性增长

如同遇到堵车或事故:退回去重新规划路线。


总结:TCP核心知识点

概念说明
TCP/IP四层应用层→传输层→网络层→网络接口层
三次握手SYN→SYN+ACK→ACK,建立可靠连接
四次挥手FIN→ACK→FIN→ACK,优雅关闭连接
序列号保证有序、避免重复、定位丢包
ISN动态生成的初始序列号,防止历史数据包干扰
滑动窗口一次发送多个包,提高效率
重传机制超时重传、快速重传、SACK
拥塞控制慢启动→拥塞避免→丢包后调整
TIME_WAIT等待2MSL,确保对方收到最后的ACK,旧数据包消散

写在最后

现在应该明白了:

  • TCP/IP四层 = 快递公司的组织架构
  • 三次握手 = 打电话确认双方都能通话
  • 四次挥手 = 双方都说完了才能挂电话
  • 滑动窗口 = 同时发多个快递,不用等一个到了再发下一个
  • UDP = 发短信,发出去就不管了
  • ISN = 随机生成的"门牌号",防止被人伪造
  • TIME_WAIT = 挂电话后等一会儿,确认对方真的听完了

下次访问网页加载变慢时,想想背后可能是 TCP 在"打电话确认"。

技术不复杂,但让"地球另一边的服务器"能和你流畅通信。