用户数据报协议UDP
-
UDP数据报的特点
- 面向数据报
- 不提供可靠性——并不能保证能否到达目的地
- 应用程序必须关心
IP
数据报的长度——因为有可能要分片
-
UDP封装
-
UDP首部
- 源端口号字段:发送进程
- 目的端口号字段:接收进程
-
UDP
长度字段:UDP
首部和UDP
数据的字节长度,最小值为8
字节(也就是说数据段可以为空) -
UDP
校验和字段:覆盖UDP
首部和UDP
数据
-
TCP
端口号和UDP
端口号是相互独立的——即TCP
端口号由TCP
检查,而UDP
端口号由UDP
检查
UDP校验和
-
概念:由发送端计算,然后由接收端验证,其目的是为了发现
UDP
首部和数据在发送端到接收端之间发送的任何改动 -
TCP
的校验和是必需的,但UDP
的校验和是可选的 -
UDP
数据报的长度在检验和计算过程中出现了两次 -
填充:为了计算校验和——因为
UDP
数据报长度可以为奇数,但校验和的算法以16-bits
为单位(即校验长度必须为偶数),所以可能要在尾部填充0
传输控制协议TCP
协议概念
-
TCP提供一种可靠的面向连接的字节流服务
-
面向连接
- 客户端和服务器彼此交换数据之前必须先建立一个
TCP
连接——类似于打电话(先拨号,接通之后再对话) - 在一个
TCP
连接中,仅有两方进行通信——这意味着多播和广播不能用于TCP
- 客户端和服务器彼此交换数据之前必须先建立一个
-
可靠
- 应用数据被分割成合适长度的数据块(报文段或段)发送——
UDP
数据长度可变 - 超时重传——发送之后启动定时器,若不能及时到达受到确认信息,则重新发送该
TCP
报文 - 接收端收到报文后会发送一个确认信息
- 校验和——对首部和数据都进行校验,若数据在传输过程中发送任何变化,都会引起校验和的差错,
TCP
检查出错误后会丢弃这些报文段,并希望发送端进行超时重发 -
TCP
会对收到的数据重新排序——因为IP
不能保证数据顺序到达 -
TCP
丢弃重复的数据——因为IP
数据报会重复到达 - 流量控制——
TCP
连接的每一方都有固定大小的缓冲空间,接收端只允许另一端发送接收缓冲区所能接纳的数据
- 应用数据被分割成合适长度的数据块(报文段或段)发送——
- 全双工:表示数据能在两个方向上独立的传输——因此连接的每一方必须保持每个方向上的传输数据序号
-
面向连接
TCP封装
-
TCP首部
- 源端口号字段:源端的端口号——用于寻找发送端的应用程序
- 目的端口号字段:目的端的端口号——用于寻找接收端的应用程序
-
序号字段:用于标识该报文段中的字节流的起始位置——即数据序号(可以进行重新排序),还包括初始序号(建立连接时使用,且只有
SYN
标志为1
时,初始序号才有效) -
确认序号字段:表示期望收到的下一个字节的序号——即上一个收到的字节的序号加
1
(只有ACK
标志为1
时,确认序号才有效) -
首部长度字段:首部的长度(以
4
字节为单位)——需要该字段是因为任选字段的长度是可变的,由于该字段共4
位,因此有20字节⩽TCP首部长度⩽60字节 20字节⩽TCP首部长度⩽60字节 -
保留位字段:后两位
CWR
和ECE
用于实现显示拥塞控制ECN机制-
CWR
(拥塞窗口减少标志位):由发送端设置,若为1
,则表示发送方接收到了接收端发出的设置了ECE
标志的TCP包,并且通知接收方:发送发已按拥塞控制机制进行了回应(可能减少拥塞窗口) -
ECE
(ECN及拥塞通知标志位):由接收端设置若为1
,则表示在TCP三次握手时一个TCP端是具备ECN功能的,并且表明接收到的TCP包的IP头部的ECN被设置为11
(即接收端发现了拥塞) —— 用以通知发送方网络上发生了拥塞(若ECE
置为1
)。-
显示拥塞通知ECN:路由器在出现拥塞时通知TCP。当TCP段传递时,路由器使用IP首部中的2位来记录拥塞,当TCP段到达后,接收方知道报文段是否在某个位置经历过拥塞。然而,需要了解拥塞发生情况的是发送方,而非接收方。因此,接收方使用下一个ACK通知发送方有拥塞发生(
ECE
置为1
),然后,发送方做出响应(CWR
置为1
),缩小自己的拥塞窗口。
-
显示拥塞通知ECN:路由器在出现拥塞时通知TCP。当TCP段传递时,路由器使用IP首部中的2位来记录拥塞,当TCP段到达后,接收方知道报文段是否在某个位置经历过拥塞。然而,需要了解拥塞发生情况的是发送方,而非接收方。因此,接收方使用下一个ACK通知发送方有拥塞发生(
-
-
标志位(有效位)字段:用于判断
TCP
首部中的对应字段是否有效-
URG
:若为1
,则表示紧急指针字段有效 -
ACK
:若为1
,则表示确认序号字段有效 -
PSH
:若为1
,则表示接收方应尽快将这个报文段交给应用层 -
RST
:若为1
,则表示重新连接 -
SYN
:同步序号,用于发起一个连接——若为1
,则表示正在建立连接 -
FIN
:若为1
,则表示发送端完成发送任务
-
-
窗口大小字段:用于流量控制(表示接收方还能下一次还能接收多少数据,若为
0
,则发送方会停止发送数据,知道窗口大小字段大于0
为止),初始值确认序号字段指明的值(表示接收端正期望接收的字节),由于该字段占16
位,因此窗口大小最大为216−1=65535字节 216−1=65535字节,且该字段的值可变 -
校验和字段:对整个
TCP
报文(TCP
首部和TCP
数据)进行校验 - 紧急指针字段:一个正的偏移量,和序号字段中的值相加表示数据最后一个字节的序号
-
任选字段字段:通常是最长报文大小
MSS
- 数据字段:
四元组——
(源IP地址,源端口号,目的IP地址,目的端口号) (源IP地址,源端口号,目的IP地址,目的端口号)用于识别唯一的一个TCP
连接-
最大报文长度
MSS
:默认的TCP
最大分段大小是536
(TCP
报文的数据区最大长度)——当一个主机想要把MSS
设置为一个非默认的值时,MSS
大小会以一个TCP
可选项的方式在握手时的SYN
包中定义。由于最大分段大小被一个TCP
参数控制,主机可以在接下来的任意分段中改变它,且每个数据流的方向都可以使用不同的MSS
-
MTU
和MSS
的区别-
MTU
:IP
数据报的最大长度——包括IP
首部、TCP
首部、TCP
数据区最大长度(MSS
),超过MTU
的IP
数据包会被分片 -
MSS
:TCP
数据区最大长度,数据区部分超过MSS
大小的TCP
报文会被丢弃 -
MTU=IP首部+TCP首部+MSS MTU=IP首部+TCP首部+MSS
-
-
超时重传:
TCP
通告在发送时设置一个定时器来解决这种问题,如果当定时器溢出时还没有收到确认,它就重传数据 -
往返时间
RTT
:往返时间=发送延迟时间+确认延迟时间 往返时间=发送延迟时间+确认延迟时间
TCP连接的建立和终止
TCP建立连接(三次握手)
-
图示
-
步骤
- 客户端发送一个同步报文,该报文标志字段中的
SYN
位置为1
,设置客户端的初始序列号为seq=x
,并指明客户端打算连接的服务器端口 - 服务器返回一个确认报文,该报文标志字段中的
SYN
位和ACK
位都置为1
,并设置确认序列号为(设置为ack=x+1
——客户端的初始序列号加1
,以对客户端的SYN
报文段进行确认),服务器的初始序列号为seq=y
- 客户端返回一个确认报文,该报文标志字段的
SYN
位置为1
,且确认序列号置为ack=y+1
(设置为ack=y+1
——服务器的初始序列号加1
,以对服务器的SYN
报文段进行确认),同时设置客户端的初始序列号为seq=x+1
- 客户端发送一个同步报文,该报文标志字段中的
-
报文布局
-
SYN
位为1
、序号字段为x
(发送端⟹ ⟹ 接收端) -
SYN
位和ACK
位都为1
、序号字段为y
、确认字段为x+1
(发送端⟸ ⟸ 接收端) -
ACK
位为1
、序号字段为x+1
、确认字段为y+1
(发送端⟹ ⟹ 接收端)
注:传输过程中还包括了通信双方的
MSS
以进行协商 -
- 为什么TCP连接需要三次握手,两次不可以吗?:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
已失效的连接请求报文段产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。
TCP连接终止序列(四次握手)
-
图示
-
步骤:首先执行关闭操作的是主动关闭方(可能为客户端,也可能为服务器)
- 某一端首先执行主动关闭(调用
close
)——发送一个FIN
分节(表示数据发送完毕)——此时主动关闭方处于FIN_WAIT_1
状态(等待服务器端的ACK
和FIN
) - 另一端的
TCP
端口接收到FIN
分节后,返回一个确认信号ACK
(对接收到的FIN
进行确认,并将其作为文件结束符放入接收队列中)——此时主动关闭方处于FIN_WAIT_2
状态(等待服务器端的FIN
),被动关闭方处于CLOSE_WAIT
状态(等待应用程序发送FIN
) - 一段时间后,从接受队列中获得文件结束符的应用程序发送一个自己的
FIN
分节(表示数据接收完毕),并执行被动关闭(调用close
))——此时主动关闭方处于TIME_WAIT
状态(等待两个MSL
的时间 —— 为了处理超时重传),被动关闭方处于LAST_ACK
状态(等待客户端的最后一个ACK
) - 主动关闭方发送一个确认信号
ACK
(对接收到的FIN
进行确认)——之后主动关闭方和被动关闭方都处于CLOSED
状态
- 某一端首先执行主动关闭(调用
-
报文布局
-
FIN
位和ACK
位都为1
、序号字段为x
、确认字段为y
(主动关闭端⟹ ⟹ 被动关闭端) -
ACK
位为1
、确认字段为x
(主动关闭端⟸ ⟸ 被动关闭端) -
FIN
位为1
、序号字段为y
(主动关闭端⟸ ⟸ 被动关闭端) -
ACK
位为1
、确认字段为y+1
(主动关闭端⟹ ⟹ 被动关闭端)
-
-
注
-
半关闭状态(
FIN_WAIT_1
和FIN_WAIT_2
):TCP
提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力,接口通过这种方式说明:我已经完成了数据传送,因此发送一个
FIN
给另一端,但我还想接收另一端发来的数据,直到他给我发来一个FIN
,之后我将进入TIME_WAIT
状态 -
主动关闭的一方不直接进入
CLOSED
状态,而是进入TIME_WAIT
状态,并且停留两倍的MSL
时长的原因:这是因为TCP
是建立在不可靠网络上的可靠的协议。例子:主动关闭的一方收到被动关闭的一方发出的
FIN
包后,回应ACK
包,同时进入TIME_WAIT
状态,但是因为网络原因,主动关闭的一方发送的这个ACK
包很可能延迟,从而触发被动连接一方重传FIN
包。极端情况下,这一去一回,就是两倍的MSL
时长。如果主动关闭的一方跳过TIME_WAIT
直接进入CLOSED
,或者在TIME_WAIT
停留的时长不足两倍的MSL
,那么当被动关闭的一方早先发出的延迟包到达后,就可能出现类似下面的问题:- 旧的
TCP
连接已经不存在了,系统此时只能返回RST
包 - 新的
TCP
连接被建立起来了,延迟包可能干扰新的连接
不管是哪种情况都会让
TCP
不再可靠,所以TIME_WAIT
状态有存在的必要性。 - 旧的
第二步和第三步不是同步发生的,处于这两步之间的主动关闭方仍在接收数据
-
TCP数据交互
Nagle算法
- 目的:尽可能发送大块数据,避免网络中充斥着许多小数据块 —— 为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。
- 定义:Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓小段,指的是小于MSS尺寸的数据块,所谓未被确认,是指一个数据块发送出去后,没有收到对方发送的ACK确认。
-
规则:
- 若包长度达到
MSS
,则允许发送 - 若该包含有
FIN
,则允许发送 - 若设置了
TCP_NODELAY
选项,则允许发送 - 若未设置
TCP_CORK
选项,但所有发出去的小数据包(包长度小于MSS
)均被确认,则允许发送 - 若上述条件都未满足,但发生了超时(一般为
200ms
),则立即发送
- 若包长度达到
流量控制
基本原理
-
流量控制的定义:TCP利用滑动窗口协议来完成流量控制 —— 即接收方通过告知发送方自己的滑动窗口大小,从而控制发送方的发送速度,以防止发送方发送速度过快。
接收方通过ACK数据报告知发送方自己还能接收多少字节的数据,其基本原理如下:
-
ACK数据报中通常包含两方面的信息:
-
确认序列号
n n:表示接收方期望接收到的下一字节的序号为n n —— 该n n代表接收方已经接收到了前n−1 n−1字节数据,此时如果接收方收到第n+1 n+1字节数据而不是第n n字节数据,则接收方会继续发送确认序列号为n n的ACK的。 -
接收方当前窗口大小
m m:表示接收方从序列号n n开始,还能接收m m个字节的数据
-
确认序列号
- 假设当前发送方已发送到第
x x字节(其中x⩾n x⩾n),则发送方还可以发送的字节数为y=m−(x−n) y=m−(x−n),这就是滑动窗口控制流量的基本原理。
如下图所示,其中确认序列号为
n=36 n=36,发送端当前已将第x=51 x=51个序列号发送出去,而当前接收方的滑动窗口大小为m=55−36 m=55−36,因此发送方还能将序号为[52,55] [52,55]的数据发送出去。 -
ACK数据报中通常包含两方面的信息:
快发送和慢接收
- 发送方连续发送
4
个背靠背的数据报文段去填充接收方的窗口,然后停下来等待一个ACK
。 - 接收方先发送一个
ACK
,但通告其窗口大小为0
,说明接收方已收到所有数据,但这些数据都在接收方的缓冲区中——因为应用程序还没有来得及读取这些数据,此时发送方不能再发送数据。 - 接收方再发送一个
ACK
(窗口更新),表明接收方现在可以接收另外的4096
字节的数据,此时发送方可以继续发送数据。
滑动窗口
- 如上图所示:当前窗口覆盖了从第
4
字节到第9
字节的区域,表明接收方已经确认了包括第3
字节在内的数据,且通告窗口大小为6
- 当接收方确认数据后,这个窗口不时地向右移动
- 当左边沿到达右边沿时,发送方将不能继续发送数据
-
窗口两个边沿的相对运动增加或减少了窗口的大小
- 窗口合拢:左边沿向右边沿靠近,这种现象发生在数据被发送但未被确认时——表示还可以发送的数据减少了
- 窗口收缩:右边沿向左边沿靠近
-
窗口张开:右边沿向右移动,这种现象发生在接收端已经确认数据并释放
TCP
接收缓存时,表示允许发送方发送更多的数据
拥塞控制
慢启动和拥塞避免(已淘汰)
执行慢启动的条件:如果发送方设置的超时计时器时限已到但还没有收到确认,那么很可能是网络出现了拥塞,致使报文段在网络中的某处被丢弃。这时,TCP马上把拥塞窗口
cwnd cwnd减小到1,并执行慢开始算法,同时把慢开始门限值ssthresh ssthresh减半。-
慢启动和拥塞避免算法:对每个连接都维护两个变量——拥塞窗口大小(
cwind
)和慢启动门限(ssthresh
),当cwind<ssthresh cwind<ssthresh时,使用上述慢启动算法;当cwind>ssthresh cwind>ssthresh时,停止使用慢启动算法,改用拥塞避免算法-
慢启动:让拥塞窗口
cwind
较快地增大,每经过一个往返时间RTT
就把发送方的拥塞窗口cwind
加倍 -
拥塞避免:让拥塞窗口
cwind
缓慢地增大——每经过一个往返时间RTT
就把发送方的拥塞窗口cwind
加1
-
规则:无论慢启动开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢启动门限
ssthresh
设置为当前出现拥塞时的发送方窗口值的一半(但不能小于2
),然后重新执行慢启动算法(即把拥塞窗口cwind
重新设置为1
,并执行慢启动算法)
-
慢启动:让拥塞窗口
快重传和快恢复
-
快重传和快恢复的基本原理:一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。在快重传和快恢复算法中,发送端要求接收方每收到一个失序的报文段后就立即发出重复确认 —— 目的是使发送方及早知道有报文段没有到达对方,这样发送端就可以通过接收端发来的重复确认信息代替自己的超时重传机制来判断是否有网络拥塞发生。
- 当发送方连续收到三个重复确认(确认序列号相同,表示报文失序,而该序列号对应的报文未能及时到达)时,执行“乘法减小”算法,慢启动门限减半
- 由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢启动算法,而是执行快恢复算法——即把
cwind
值设置为慢启动门限减半后的值,然后开始执行拥塞避免算法,拥塞窗口cwind
值线性增大。避免了当网络拥塞不够严重时采用”慢启动”算法而造成过大地减小发送窗口尺寸的现象。
- 当发送方连续收到三个重复确认(确认序列号相同,表示报文失序,而该序列号对应的报文未能及时到达)时,执行“乘法减小”算法,慢启动门限减半
总结
流量控制和拥塞控制的区别
- 流量控制: 所谓流量控制就是让发送方的发送速率不要太快,使接收方来得及接收 —— 如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失,利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制。
- 拥塞控制:所谓拥塞控制就是防止过多的数据注入到网络中,使网络中的路由器或链路不致过载。