3. TCP/IP协议栈回顾与Socket关联

说实话,很多搞了几年网络开发的朋友,被问到「Socket到底是什么」时,还是会愣一下。我面试过不少人,能背出七层模型的不少,但能把Socket和TCP/IP协议栈真正串起来的,真不多。

这一章,咱们就把这些基础概念彻底捋一遍。不是为了背书,是为了后面用Wireshark抓包时,你能一眼看出每一层在干什么。

3.1 OSI模型与TCP/IP模型:别被七层吓住

OSI七层模型,教科书上的标准答案。但实际互联网用的是TCP/IP四层模型。我个人的习惯是,把OSI当作「理论地图」,把TCP/IP当作「实战工具」。

OSI七层 TCP/IP四层 核心协议举例 Wireshark中看到的
应用层 应用层 HTTP, FTP, DNS GET /index.html
表示层 SSL/TLS Client Hello
会话层 SOCKS, RPC 会话建立
传输层 传输层 TCP, UDP 源端口、目的端口
网络层 网络层 IP, ICMP 源IP、目的IP
数据链路层 网络接口层 Ethernet, PPP MAC地址
物理层 RJ45, 光纤 比特流
我的经验: 抓包分析时,我只看TCP/IP四层。OSI的会话层和表示层在实际TCP/IP协议栈里被合并了。你非要在Wireshark里找「表示层」,只会把自己绕晕。

3.2 Socket定义:IP + Port = 通信的门牌号

Socket到底是什么?说白了,就是IP地址 + 端口号的组合。一个Socket唯一标识了网络上的一台主机上的一个进程。

举个例子:192.168.1.100:443 这个Socket,代表的是IP为192.168.1.100的机器上,那个监听443端口的HTTPS服务进程。

我记得刚入行时,有个同事把Socket理解成「一根网线」,调试了三天没找到bug。后来我告诉他:Socket不是线,是门牌号。数据包到了你家楼下(IP),还得知道敲哪个门(端口),才能找到你这个人(进程)。

核心公式:
Socket = IP地址 + 端口号
一个TCP连接 = (源Socket, 目的Socket) 的四元组

3.3 TCP与UDP核心区别:可靠 vs 快速

这两个协议的区别,面试必问。但实战中怎么选?我直接给结论:

特性 TCP UDP
连接 面向连接(三次握手) 无连接
可靠性 可靠(重传、确认) 不可靠(尽力而为)
顺序 保证顺序 不保证顺序
速度 较慢(有开销) 快(无开销)
典型应用 HTTP, FTP, SMTP DNS, 视频直播, 游戏
避坑指南: 我曾经在一个物联网项目里,用TCP传传感器数据。结果设备多了,服务器连接数暴涨,内存直接爆了。后来换成UDP + 应用层ACK,问题解决。记住:不是所有场景都需要TCP的可靠性,有时候「丢了就丢了」反而是最优解。

3.4 三次握手与四次挥手:连接的生与死

这是TCP最核心的机制。咱们用Wireshark抓包时,最先看到的就是这三个包。

三次握手(建立连接)

  1. 客户端 → 服务端: SYN(seq=x)—— 「我要连你」
  2. 服务端 → 客户端: SYN+ACK(seq=y, ack=x+1)—— 「收到,我也连你」
  3. 客户端 → 服务端: ACK(seq=x+1, ack=y+1)—— 「好,开始传数据」

为什么是三次?不是两次?我简单解释:两次握手的话,服务端无法确认客户端收到了自己的SYN。万一第一个SYN延迟了,客户端重传,服务端就会建立两个连接,浪费资源。

四次挥手(断开连接)

  1. 主动方 → 被动方: FIN —— 「我发完了」
  2. 被动方 → 主动方: ACK —— 「知道了,但我还有数据要发」
  3. 被动方 → 主动方: FIN —— 「我也发完了」
  4. 主动方 → 被动方: ACK —— 「好,断开」
实战观察: 用Wireshark抓三次握手时,注意看SEQ和ACK号的变化。第一次握手的SEQ是随机数(不是0!),第二次的ACK是第一次SEQ+1。这个规律能帮你快速定位连接问题。

3.5 TCP状态转换图:一张图看懂连接生命周期

下面这张图,是我自己画的状态转换图。你把它存下来,以后抓包分析时对照着看,比翻书快多了。

TCP状态转换图 TCP状态转换图 CLOSED SYN_SENT ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 LISTEN SYN_RCVD ESTABLISHED CLOSE_WAIT LAST_ACK ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (seq=x+1, ack=y+1) ④ FIN ⑤ ACK ⑥ FIN ⑦ ACK 左侧:客户端状态(主动方) 右侧:服务端状态(被动方)

这张图里,我特意把客户端和服务端分开画。你注意看:

  • 三次握手:CLOSED → SYN_SENT → ESTABLISHED(客户端);LISTEN → SYN_RCVD → ESTABLISHED(服务端)
  • 四次挥手:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT(客户端);ESTABLISHED → CLOSE_WAIT → LAST_ACK(服务端)
避坑指南: 我曾经在生产环境遇到过一个问题:服务器上有大量TIME_WAIT状态的连接。原因是客户端频繁短连接,服务端主动关闭后,客户端进入TIME_WAIT,要等2MSL(约2分钟)才能释放端口。解决方案:要么让客户端复用连接,要么调整内核参数net.ipv4.tcp_tw_reuse。嗯,这个坑我踩过,你注意。

3.6 Socket与TCP状态的关系

Socket编程里,你调用的每个API,背后都对应着TCP状态的变化:

Socket API 对应TCP状态变化 Wireshark中看到的包
socket() CLOSED 无
bind() CLOSED(绑定端口) 无
listen() CLOSED → LISTEN 无
connect() CLOSED → SYN_SENT → ESTABLISHED SYN → SYN+ACK → ACK
accept() LISTEN → SYN_RCVD → ESTABLISHED 收到SYN后返回SYN+ACK
close() ESTABLISHED → FIN_WAIT_1 → ... → CLOSED FIN → ACK → FIN → ACK
我的习惯: 调试网络问题时,我第一件事就是看netstat -an | grep TIME_WAIT。如果TIME_WAIT数量超过几千,基本可以断定是连接管理出了问题。这时候用Wireshark抓一下,看看是谁先发的FIN,问题就清楚了。