9.3 传输层协议
传输层运行在网络层之上,负责端到端的通信,为应用层提供端到端的通信服务,屏蔽下层网络的细节,让应用层不需要关心数据包怎么在网络中传输。传输层有两个核心协议:TCP和UDP。
传输层的作用
网络层负责把数据包从源主机送到目标主机,但数据包到达目标主机后,还需要交给对应的应用程序处理。传输层的作用就是:
- 端口寻址:通过端口号区分不同的应用程序,把数据交给正确的应用程序
- 提供端到端的通信服务:为应用程序提供不同质量的通信服务,包括可靠的字节流服务(TCP)和不可靠的数据报服务(UDP)
- 流量控制和拥塞控制:控制发送速率,避免网络拥塞和接收方处理不过来
端口与套接字
端口号
端口号是传输层的地址,用来标识主机上的不同应用进程,长度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的特点
- 无连接:通信前不需要建立连接,发数据的时候直接封装成UDP包发出去就可以,不需要握手,延迟低
- 不可靠:不保证数据包一定到达,不保证顺序,不重传,出错了直接丢弃
- 面向数据报:应用层交下来的数据直接封装成UDP包,一次发送一个完整的UDP包,不会拆分也不会合并,保留报文边界
- 没有流量控制和拥塞控制:不管网络拥塞情况,想发多少就发多少
- 头部开销小:UDP头部只有8字节,比TCP的20字节小很多,传输效率高
UDP头部格式
┌──────────┬──────────┐
│ 源端口 │ 目标端口 │ 各2字节
├──────────┼──────────┤
│ 长度 │ 校验和 │ 各2字节
└──────────┴──────────┘
- 长度:UDP数据报的总长度,包括头部和数据
- 校验和:检测UDP数据报在传输过程中是否出错,出错就丢弃
UDP的适用场景
UDP虽然不可靠,但传输效率高,延迟低,适合对实时性要求高、可以容忍少量丢包的场景:
- 实时音视频通话:微信电话、视频会议等,丢个几帧不影响观看,但是延迟高了就会卡
- 直播、流媒体
- 游戏:特别是多人在线游戏,低延迟比可靠性更重要,丢包可以通过重传来补救,但延迟高了会严重影响体验
- DNS查询:小数据包,一次请求一响应,不需要建立连接,效率高
- 物联网设备:带宽小、功耗低的场景
TCP协议(传输控制协议)
TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议,是现在互联网应用最广泛的协议,HTTP、HTTPS、FTP、SSH等大部分应用层协议都基于TCP。
TCP的特点
- 面向连接:通信前需要先通过三次握手建立连接,通信结束后需要四次挥手释放连接
- 可靠传输:保证数据不丢、不重复、按序到达,出错会重传
- 面向字节流:没有报文边界,应用层的数据被看成一连串无结构的字节流,TCP可以拆分和合并,应用程序需要自己处理消息边界
- 流量控制:根据接收方的接收能力调整发送速率,避免接收方处理不过来
- 拥塞控制:根据网络拥塞情况调整发送速率,避免网络拥塞
- 全双工通信:连接建立后,双方都可以同时发送和接收数据
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 │
│ ───────────────────────────────────────────────────▶ │
│ │
│ 连接建立成功 │
过程说明:
- 第一次握手:客户端发送SYN报文,SYN=1,序列号SEQ=x,客户端进入SYN_SENT状态,请求建立连接
- 第二次握手:服务器收到SYN后,回复SYN+ACK报文,SYN=1,ACK=1,确认号ACK=x+1,自己的序列号SEQ=y,服务器进入SYN_RCVD状态
- 第三次握手:客户端收到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 │
│ ───────────────────────────────────────────────────▶ │
│ │
│ 连接释放完成 │
过程说明:
- 第一次挥手:客户端发送FIN报文,FIN=1,序列号SEQ=u,客户端进入FIN_WAIT_1状态,表示客户端没有数据要发送了,请求关闭连接
- 第二次挥手:服务器收到FIN后,回复ACK报文,ACK=1,确认号ACK=u+1,序列号SEQ=v,服务器进入CLOSE_WAIT状态,客户端收到ACK后进入FIN_WAIT_2状态,此时客户端到服务器的通道关闭,但服务器还可以发送数据
- 第三次挥手:服务器数据发送完后,发送FIN报文,FIN=1,ACK=1,确认号ACK=u+1,序列号SEQ=w,服务器进入LAST_ACK状态
- 第四次挥手:客户端收到FIN后,回复ACK报文,ACK=1,确认号ACK=w+1,序列号SEQ=u+1,客户端进入TIME_WAIT状态,等待2MSL(最长报文寿命,通常1-2分钟)后进入CLOSED状态,服务器收到ACK后进入CLOSED状态,连接释放完成
为什么需要TIME_WAIT状态?
- 保证最后一个ACK报文能到达服务器,如果服务器没收到ACK会重发FIN,TIME_WAIT状态可以处理重发的FIN
- 让本次连接的所有报文都从网络中消失,避免新连接收到旧连接的报文
TCP的可靠传输机制
TCP通过以下机制保证可靠传输:
- 序列号和确认应答:每个字节都有序号,接收方收到后回复确认号,发送方如果在超时时间内没收到确认就重传
- 超时重传:发送方发送报文后启动定时器,超时没收到确认就重传报文
- 流量控制:滑动窗口机制,接收方通过TCP头的窗口字段告诉发送方自己还能接收多少数据,发送方根据窗口大小调整发送量,避免接收方处理不过来
- 拥塞控制:发送方根据网络拥塞情况调整发送速率,避免网络拥塞,主要有四个算法:慢启动、拥塞避免、快重传、快恢复
TCP的适用场景
TCP适合对可靠性要求高,对延迟要求不高的场景:
- Web服务(HTTP/HTTPS)
- 文件传输(FTP、SFTP)
- 邮件(SMTP、POP3)
- 远程登录(SSH)
- 数据库访问
- 所有不允许丢包的场景
TCP vs UDP对比和选型
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠,保证不丢、不重复、有序 | 不可靠,不保证交付,不保证顺序 |
| 传输方式 | 面向字节流,无报文边界 | 面向数据报,有报文边界 |
| 性能 | 传输效率低,头部开销大,延迟高 | 传输效率高,头部开销小,延迟低 |
| 流量控制/拥塞控制 | 有 | 无 |
| 连接数量 | 一对一全双工 | 一对一、一对多、多对多 |
| 适用场景 | 对可靠性要求高的场景,Web、文件传输、邮件等 | 对实时性要求高的场景,音视频、游戏、DNS等 |
选型建议:
- 需要可靠传输优先选TCP
- 实时性要求高、可以容忍少量丢包选UDP
- 现在很多应用会在UDP之上自己实现可靠传输机制,比如QUIC、WebRTC,兼顾UDP的低延迟和TCP的可靠性
思考问题
- TCP和UDP各有什么优缺点?分别适合什么场景?
- TCP建立连接为什么需要三次握手?两次握手会有什么问题?
- TCP断开连接为什么需要四次挥手?TIME_WAIT状态的作用是什么?
- TCP是怎么保证可靠传输的?有哪些机制?