Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

9.3 传输层协议

传输层运行在网络层之上,负责端到端的通信,为应用层提供端到端的通信服务,屏蔽下层网络的细节,让应用层不需要关心数据包怎么在网络中传输。传输层有两个核心协议:TCP和UDP。

传输层的作用

网络层负责把数据包从源主机送到目标主机,但数据包到达目标主机后,还需要交给对应的应用程序处理。传输层的作用就是:

  1. 端口寻址:通过端口号区分不同的应用程序,把数据交给正确的应用程序
  2. 提供端到端的通信服务:为应用程序提供不同质量的通信服务,包括可靠的字节流服务(TCP)和不可靠的数据报服务(UDP)
  3. 流量控制和拥塞控制:控制发送速率,避免网络拥塞和接收方处理不过来

端口与套接字

端口号

端口号是传输层的地址,用来标识主机上的不同应用进程,长度16位,范围0-65535:

  • 0-1023:知名端口,分配给常用的服务,比如HTTP=80,HTTPS=443,SSH=22,FTP=21等,需要root权限才能绑定
  • 1024-49151:注册端口,分配给普通应用程序使用
  • 49152-65535:动态端口/私有端口,客户端发起连接时随机使用的源端口

套接字(Socket)

网络通信的两端需要用套接字来唯一标识:

  • 套接字 = IP地址 + 端口号
  • 一个TCP连接由两端的套接字唯一标识:源IP:源端口 <-> 目标IP:目标端口
  • 同一个主机上,不同的连接不能有完全相同的四元组(源IP、源端口、目标IP、目标端口)

UDP协议(用户数据报协议)

UDP(User Datagram Protocol)是一种无连接的、不可靠的传输层协议。

UDP的特点

  1. 无连接:通信前不需要建立连接,发数据的时候直接封装成UDP包发出去就可以,不需要握手,延迟低
  2. 不可靠:不保证数据包一定到达,不保证顺序,不重传,出错了直接丢弃
  3. 面向数据报:应用层交下来的数据直接封装成UDP包,一次发送一个完整的UDP包,不会拆分也不会合并,保留报文边界
  4. 没有流量控制和拥塞控制:不管网络拥塞情况,想发多少就发多少
  5. 头部开销小:UDP头部只有8字节,比TCP的20字节小很多,传输效率高

UDP头部格式

┌──────────┬──────────┐
│ 源端口   │ 目标端口  │  各2字节
├──────────┼──────────┤
│ 长度     │ 校验和    │  各2字节
└──────────┴──────────┘
  • 长度:UDP数据报的总长度,包括头部和数据
  • 校验和:检测UDP数据报在传输过程中是否出错,出错就丢弃

UDP的适用场景

UDP虽然不可靠,但传输效率高,延迟低,适合对实时性要求高、可以容忍少量丢包的场景:

  1. 实时音视频通话:微信电话、视频会议等,丢个几帧不影响观看,但是延迟高了就会卡
  2. 直播、流媒体
  3. 游戏:特别是多人在线游戏,低延迟比可靠性更重要,丢包可以通过重传来补救,但延迟高了会严重影响体验
  4. DNS查询:小数据包,一次请求一响应,不需要建立连接,效率高
  5. 物联网设备:带宽小、功耗低的场景

TCP协议(传输控制协议)

TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议,是现在互联网应用最广泛的协议,HTTP、HTTPS、FTP、SSH等大部分应用层协议都基于TCP。

TCP的特点

  1. 面向连接:通信前需要先通过三次握手建立连接,通信结束后需要四次挥手释放连接
  2. 可靠传输:保证数据不丢、不重复、按序到达,出错会重传
  3. 面向字节流:没有报文边界,应用层的数据被看成一连串无结构的字节流,TCP可以拆分和合并,应用程序需要自己处理消息边界
  4. 流量控制:根据接收方的接收能力调整发送速率,避免接收方处理不过来
  5. 拥塞控制:根据网络拥塞情况调整发送速率,避免网络拥塞
  6. 全双工通信:连接建立后,双方都可以同时发送和接收数据

TCP头部格式

TCP头部最小20字节,最大60字节(包含选项字段):

┌──────────┬──────────┬──────────┬──────────┐
│ 源端口             │ 目标端口            │  各2字节
├──────────┴──────────┴──────────┴──────────┤
│ 序列号(SEQ)                              │  4字节
├───────────────────────────────────────────┤
│ 确认号(ACK)                              │  4字节
├────┬────┬────┬──────────┬─────────────────┤
│ 头长│保留│ 标志位 │ 窗口大小              │  头长4位,保留6位,标志位6位,窗口2字节
├──────────┬──────────┬─────────────────────┤
│ 校验和             │ 紧急指针              │  各2字节
├───────────────────────────────────────────┤
│ 选项(可选,最大40字节)                  │
└───────────────────────────────────────────┘
  • 序列号(SEQ):本报文段第一个数据字节的序号,用来解决乱序问题
  • 确认号(ACK):期望收到对方下一个报文段的第一个字节的序号,确认号之前的字节都已经正确收到
  • 标志位:
    • SYN:同步标志,建立连接时使用
    • ACK:确认标志,确认号字段有效,连接建立后所有报文都必须把ACK置1
    • FIN:结束标志,释放连接时使用
    • RST:复位标志,重置连接
    • PSH:推送标志,接收方应该尽快把数据交给应用层
    • URG:紧急标志,紧急指针有效
  • 窗口大小:用来实现流量控制,表示接收方现在能接收的字节数

三次握手:TCP连接建立过程

TCP建立连接需要三次握手,也就是三个数据包交换:

客户端                                                 服务器
   │  SYN=1, SEQ=x                                       │
   │ ───────────────────────────────────────────────────▶ │
   │  SYN=1, ACK=1, ACK=x+1, SEQ=y                        │
   │ ◀─────────────────────────────────────────────────── │
   │  ACK=1, ACK=y+1, SEQ=x+1                             │
   │ ───────────────────────────────────────────────────▶ │
   │                                                     │
   │                     连接建立成功                     │

过程说明:

  1. 第一次握手:客户端发送SYN报文,SYN=1,序列号SEQ=x,客户端进入SYN_SENT状态,请求建立连接
  2. 第二次握手:服务器收到SYN后,回复SYN+ACK报文,SYN=1,ACK=1,确认号ACK=x+1,自己的序列号SEQ=y,服务器进入SYN_RCVD状态
  3. 第三次握手:客户端收到SYN+ACK后,回复ACK报文,ACK=1,确认号ACK=y+1,序列号SEQ=x+1,客户端进入ESTABLISHED状态,服务器收到ACK后也进入ESTABLISHED状态,连接建立成功

为什么需要三次握手,而不是两次?

  • 防止已经失效的连接请求报文突然又传到服务器,导致服务器建立不必要的连接,浪费资源
  • 双方都要确认对方的发送和接收能力是正常的,三次握手可以保证双方都知道对方有正常的收发能力
  • 同步双方的初始序列号,序列号是可靠传输的基础,需要双方确认对方的初始序列号

四次挥手:TCP连接释放过程

TCP断开连接需要四次挥手,因为TCP是全双工的,双方都需要单独关闭发送通道:

客户端                                                 服务器
   │  FIN=1, SEQ=u                                       │
   │ ───────────────────────────────────────────────────▶ │
   │  ACK=1, ACK=u+1, SEQ=v                              │
   │ ◀─────────────────────────────────────────────────── │
   │  客户端到服务器的方向关闭,服务器还可以继续发数据     │
   │  FIN=1, ACK=1, ACK=u+1, SEQ=w                       │
   │ ◀─────────────────────────────────────────────────── │
   │  ACK=1, ACK=w+1, SEQ=u+1                            │
   │ ───────────────────────────────────────────────────▶ │
   │                                                     │
   │                     连接释放完成                     │

过程说明:

  1. 第一次挥手:客户端发送FIN报文,FIN=1,序列号SEQ=u,客户端进入FIN_WAIT_1状态,表示客户端没有数据要发送了,请求关闭连接
  2. 第二次挥手:服务器收到FIN后,回复ACK报文,ACK=1,确认号ACK=u+1,序列号SEQ=v,服务器进入CLOSE_WAIT状态,客户端收到ACK后进入FIN_WAIT_2状态,此时客户端到服务器的通道关闭,但服务器还可以发送数据
  3. 第三次挥手:服务器数据发送完后,发送FIN报文,FIN=1,ACK=1,确认号ACK=u+1,序列号SEQ=w,服务器进入LAST_ACK状态
  4. 第四次挥手:客户端收到FIN后,回复ACK报文,ACK=1,确认号ACK=w+1,序列号SEQ=u+1,客户端进入TIME_WAIT状态,等待2MSL(最长报文寿命,通常1-2分钟)后进入CLOSED状态,服务器收到ACK后进入CLOSED状态,连接释放完成

为什么需要TIME_WAIT状态?

  1. 保证最后一个ACK报文能到达服务器,如果服务器没收到ACK会重发FIN,TIME_WAIT状态可以处理重发的FIN
  2. 让本次连接的所有报文都从网络中消失,避免新连接收到旧连接的报文

TCP的可靠传输机制

TCP通过以下机制保证可靠传输:

  1. 序列号和确认应答:每个字节都有序号,接收方收到后回复确认号,发送方如果在超时时间内没收到确认就重传
  2. 超时重传:发送方发送报文后启动定时器,超时没收到确认就重传报文
  3. 流量控制:滑动窗口机制,接收方通过TCP头的窗口字段告诉发送方自己还能接收多少数据,发送方根据窗口大小调整发送量,避免接收方处理不过来
  4. 拥塞控制:发送方根据网络拥塞情况调整发送速率,避免网络拥塞,主要有四个算法:慢启动、拥塞避免、快重传、快恢复

TCP的适用场景

TCP适合对可靠性要求高,对延迟要求不高的场景:

  • Web服务(HTTP/HTTPS)
  • 文件传输(FTP、SFTP)
  • 邮件(SMTP、POP3)
  • 远程登录(SSH)
  • 数据库访问
  • 所有不允许丢包的场景

TCP vs UDP对比和选型

特性TCPUDP
连接性面向连接无连接
可靠性可靠,保证不丢、不重复、有序不可靠,不保证交付,不保证顺序
传输方式面向字节流,无报文边界面向数据报,有报文边界
性能传输效率低,头部开销大,延迟高传输效率高,头部开销小,延迟低
流量控制/拥塞控制
连接数量一对一全双工一对一、一对多、多对多
适用场景对可靠性要求高的场景,Web、文件传输、邮件等对实时性要求高的场景,音视频、游戏、DNS等

选型建议

  • 需要可靠传输优先选TCP
  • 实时性要求高、可以容忍少量丢包选UDP
  • 现在很多应用会在UDP之上自己实现可靠传输机制,比如QUIC、WebRTC,兼顾UDP的低延迟和TCP的可靠性

思考问题

  1. TCP和UDP各有什么优缺点?分别适合什么场景?
  2. TCP建立连接为什么需要三次握手?两次握手会有什么问题?
  3. TCP断开连接为什么需要四次挥手?TIME_WAIT状态的作用是什么?
  4. TCP是怎么保证可靠传输的?有哪些机制?