网络基础(五)传输层
传输层
传输层主要负责主机的总体的数据传输和控制。应用层的协议的数据统一经过运输层进行传输。
传输层的协议有2个,分别是TCP 和UDP。
TCP
TCP(Transmission Control Protocol) 传输控制协议,tcp是面向连接的。
TCP 的数据格式
tcp 的数据格式比udp要复杂一些。

tcp头部结构
tcp 协议同样分为头部和数据部分。数据部分是应用层传输过来的数据。
tcp 协议头部的大小 为 20 到 40 字节。 固定部分 20 + 可变部分 20 = 20 到 40 字节.
源端口(16)
发送方的端口,如果是客户端是随机生成的,如果是服务器一般是固定的。
目的端口(16)
接收方的端口
序号(4)
sequence number:TCP 为每个数据定义了一个编号,此序号表示传给对方的数据中的第一个字节数据的编号。
确认号(4)
acknowledgment number:表示希望对方下一次发送哪个编号的数据。比如我已经接受到编号2的数据了,确认号设置为3,表示接下来我需要3编号的数据。
数据偏移(4)
数据偏移指的是当前的tcp数据包的 数据部分的偏移量,相对整个数据包的偏移量 等于 数据包的头部。
此数据偏移存储的并不是真实的值 数据偏移 = tcp头部 / 4 ; tcp 头部 = 数据偏移 * 4;
tcp 头部并没有记录整个数据包的长度,仅仅记录了头部的长度。TCP 的数据长度可以推算出来
TCP 数据长度 = 网络层总长度 - 网络层的首长度 - TCP头部长度(0);
保留(6)
没啥用,目前都是0
URG(1)
urgen:此标记为 1的时候,头部有个紧急指针的数据才会有效,表示优先发送的数据。
ACK(1)
acknowledgment:当ACK 为1的时候,确认号才有效。
psh(1)
push:发送方的数据再设置这个标志位后, 接受方接受到此标记的报文段后,会立即将数据交给应用层,而不是等着缓冲去区满了。
rsh(1)
reset:当此标记有数据的时候,表示连接中出现错误,要释放连接了。
syn(1)
synchronization: 表示是一个建立连接的请求标记。
fin(1)
finish:表示是结束连接的请求,当要断开连接的时候,此位为 1。
URG ACK psh rsh syn fin 都是占用一个字节的标志位,表示一种状态,值只有0 或者1
窗口(2)
window:发送窗口大小,告诉对方下次发送多少的数据,可以进行流量的控制。
校验和(16)
用于校验数据是否被修改 = 伪首部 + 首部 + 数据,算出来的一个值。

伪首部 = 原始ip + 目的ip + 填充0 +协议代表值 +tcp长度 仅仅用在计算校验和不会放到计算中。
选项部分(0-20)
在特殊的时候传递额外的信息。
在握手阶段会有
- Maximum segment size
- No-Operation
- Window scale
- SACK permitted
等
TCP 可靠传输
ARQ自动重传请求
ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模型中的错误纠正协议之一。
ARQ协议通过使用确认和重传这两个机制,在不可靠服务的基础上实现可靠的信息传输。
如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。

当请求数据方迟迟得不到对象的响应后,就认为请求已经超时了,在一段时间后重传请求。

当客户端请求到数据但是未得到响应,执行了再次发送,接收方会将重复的请求丢弃。
当重传了多次还是失败后,就会发送RST 断开tcp连接,表示此连接出现了异常
TCP 的发送和确认过程
在tcp数据的发送和确认中,有2个头部信息是非常重要的,一个是 序号seq,一个是确认号 ack。
序号 和确认号都是一个数字,代表的是数据包中的数据序号,可以理解为偏移量,因为大多数情况下,一个请求需要多个数据包发送。
序号表示了 我当前的携带的数据从哪个编号开始,确认号表示我已经确认了你多少个数据包,并希望你发编号为多少的数据包。

将发送请求和响应请求简化的来看
- 客户端发送一个数据包,seq 表示数据从 0 的位置开始,ack 表示服务端也还没响应数据,也希望从0 开始,len 表示客户端发送的数据包大小
- 服务端接收到数据后,已经收到客户度的 数据为10 个长度,希望下次能获取到从11 开始的数据,所以ack = 11,而因为服务端还没发送过数据,所以seq 从0开始。len 表示服务端发送给客户端的数据大小是 50
- 客户端接收到服务端的数据,通过ack 可以确认服务端已经接收了ack = 11 之前的数据,所以从11你开始发送,而已经接收了服务端的数据 50 ,所以ack 期望下次发送 51.
- 服务端接受到第一次和第二次的数据请求,共接收到序号到 20 的数据,下次希望接收到21 的数据,所以ack = 21.因为上次客户端明确ack = 51 的数据,所以数据从 seq = 51开始。
连续ARQ协议 + 滑动窗口协议
首先因为数据包的大小的限制,当发送的内容比较多的时候,是需要分为多个包进行传输的。

比如第一个包 1-100
第二个包 101-200
第三个包 201-300
…..
因为TCP要保证可靠传输,如果每次都一个包一个包传,一个包一个包确认,效率是非常低下的。
所以TCP使用多个包并行传输,使用滑动窗口协议 来控制每次发送的数据量的大小。

如上图,A 和B 两个客户端方。 都有一批待发送的数据。
假设从A 发送到B,那么A 有一个发送窗口,表示一次发送的数据范围。B 有一个接收窗口,表示B 接收的数据范围。发送窗口从数据的头部 开始向右移动,假设窗口的大小是 10 ,那么发送当前窗口的10个数据,当当前窗口中的数据都发送并接收到确认,窗口继续向未发送的数据区移动一个窗口单位。
假设 接收窗口B 中的部分数据没有收到,B 会将窗口移动到第一个未接收到数据的位置,并且ACK的值为第一个未收到数据的序号。
滑动窗口发送流程
在客户端和服务端执行数据发送的时候,客户端和服务端都会在内存开辟一块空间,用来缓存一部分发送数据和接收数据。
发送窗口 和发送窗口在各自的缓存中移动。

数据发送流程
- 发送缓存和接收缓存都是数据发送的中转站。分别存在发送窗口和接收窗口,发送数据的时候,应用将数据的一部分写入到发送缓存,发送完毕的数据一部分数据会被释放空间,发送缓存一边发送,应用数据一边写入。接收数据的时候,应用从接收缓存中读取数据,读取完的缓存数据会被释放以腾出空间接收数据。
- 在建立tcp连接的时候,由接受方在tcp头部的可选项部分将接收窗口大小告诉发送方,发送方根据接收方的窗口大小设置发送方的发送窗口大小。(客户端将自己的接收窗口大小告诉服务端,服务端将自己的接收窗口大小告诉客户端)
- 假设上图 中每个数据包是 100字节。窗口大小 300字节,每个窗口发送3个数据包。
- 窗口数据头部开启,窗口位置在 数据包 1到 3 之前,客户端一次性发送 3个数据包。
- 服务端窗口初始化在0 的位置,接收到3个数据包,数据接收成功后,接收窗口向左移动窗口大小。
- 服务端对客户端做出ack响应,因为已经接收了3个数据包,希望后面接收到第4个数据包,所以ack = 301
- 客户端接收ack响应后,知道前3个数据包已经发送成功了,就讲发送窗口向左移动到 ack = 301 ,即4 号数据表的位置。
- 客户端接着发送 4,5 ,6 3个数据包,假如其中一个数据包丢失。最终服务端接收到了 4 和 6 号数据包,5号数据包丢失。
- 因为服务器能够接收到的连续的数据包的位置在4号数据包位置,5号数据包没收到,必须收到5号数据包才能数据完整,就讲窗口移动到5号的位置,为接收5号数据包做准备,然后向客户端发送ack 告诉客户端下次要给我发5号的数据包。
- 客户端接收到服务端的ack ,移动到5 的位置,虽然5 和 6 已经发送过了,但是对方需要的是5号,所以发送窗口还是移动到5号,理论上要发送 窗口内 5 6 7 3个数据包的数据,因为6号已经发送过了,在服务端对客户端的响应中会记录6号已经接收完毕的信息,客户端只发送窗口内没发送成功的 5 号和7号数据。
在重发的时候只发送未成功的,而已经成功的数据包并不会重复发送的特性叫做选择性确认。见下方。
SACK (选择性确认)
在TCP 通信过程中,如果发送队列中其中的一个数据包丢失了,比如 1 2 3 个数据包,2 号数据包丢失了,TCP会通过重传最后确认的分组后续的分组,会发送 2 和3 ,但是3 是已经成功的,为了减少非必要数据,发送的时候只发送 2 这个数据包。不用将整个组的数据都发送。
简单的说就是发生中间的数据包丢失的时候,接受方会告诉发送方 在确认点之后的包有那个是成功了的,是不用重传的。
SACK存储结构
TCP 的头部选项(可选部分)放SACK数据信息。

在一个接收窗口中有多个字节块,有的接收成功,有的接收失败。在SACK中将接收成功的字节段的信息返回给发送方。

tcp sack option 结构
假如一个Option 是一个SACK选项。那么Kind = 5 ,表示是一个sack。
kind 占用1个字节。
length 占用1个字节,表示 sack占用多少个字节。
left edge (4字节) 和 right edge (4字节) 是成对出现的并且有多个,表示的是在窗口中接收成功的字节块的位置。每一个对表示一个成功接收的字节块。
因为tcp头部容量的限制,sack最多携带4个边界信息。最大占用字节数 4 * 8 = 2 = 34
序号和确认号
序号和确认号是tcp分包可靠传输的关键。
seq 和ack 的序号在真实中并不是从0 开始的,而是从一个比较大的数字的开始值开始的。

通过抓包工具可以查看到有2个值
一个是 relative sequence number 相对值,是一个比较小的数字。
一个是 sequence number (raw) 是一个真实的值。
在tcp建立连接的时候。 生成一个初始值的值 比如 s1 ,那么在后续的请求中,在初始值的基础上 加上数据的大小进行类加,获得后续的值。后续的值减去初始值可以看做相对值。
下图说明传输过程中序号和确认号的关系

在建立连接的时候,序号和确认号用于3次握手的确认信息。
在建立连接的时候,因为数据部分都是空的,只有头部信息,所以seq 的值就等于相对开始的值。主机A的seq 初始值为 s1 ,主机B的seq的初始值为s2。
随着已经传输的数据的增加,各自的seq的值随着对方的ack的增大在逐渐的增大。
在数据传输的过程中,一次性发送多个数据包,多个数据包的ack 是一样的,因为是针对已经收到的数据来确定ack的值的。
假设接收窗口能够接收到4个包,但是发送方只发送了2个包,接收方等待一段时间后,就会确认这2个包给发送方。
TCP 流量控制
如果接收方的缓存满了,发送方还在发送数据,接收方只能丢弃一部分数据包,这样会造成网络资源的浪费。
所以需要对传输流量进行控制。
发送窗口大小是流量控制的关键,窗口越小,那么同时间内发送的数据量就少,传输的就慢,
发送方的窗口大小是根据接收方的窗口大小进行调整的,那么通过tcp头部的窗口字段可以告诉放松发送方的窗口大小。
如果接收方给到发送方的窗口大小为 0 ,那么发送方将不再发送数据。
TCP拥塞控制
拥塞控制是指的是如果在传输过程中出现了拥塞,使用怎么的一种方式来控制拥塞,使传输怎么慢下来,慢下来后怎么重新恢复。
几个概念:
MSS(Maximum segment size): 每个段的最大的数据部分大小。在建立tcp连接的时候会确定此值是多少。会在可选部分传输此值。
cwnd(congestion window): 拥塞窗口,拥塞窗口是当网络状况不好的时候发送方的窗口大小。此窗口大小是随着网络的状况而不断变化的。
rwnd (receive window): 接收窗口,既接收端的缓存数据窗口大小。
swnd(send window): 发送窗口。发送端的窗口。虽然在tcp连接的时候已经确定了发送窗口大小,但是在传输过程中会随着拥塞窗口 和 初始的发送窗口进行变化,那个小以哪个值为准。 swnd = MIN(swnd ,cwnd).比如初始发送窗口是 1000,当网络通畅的时候就是1000,而出现网络拥塞的时候,接收方告诉发送方的拥塞窗口为 500,那么发送方将以 拥塞窗口和初始发送窗口的最小值来发送,当网络恢复不拥塞的时候,将恢复使用初始窗口大小。
拥塞控制-慢开始(slow start)
为了防止网络拥塞,网络发送的时候从拥塞窗口从一个比较小的窗口开始慢慢的增大,直到到达指定大小的发送窗口。
比如建立连接的时候 确认的窗口大小是 100 。拥塞窗口首先从 1 开始,当着一个数据包发送完后,再变为 2,确认后,再变为 4 ,确认后,再变为8.

拥塞窗口大小成倍进行增长
拥塞窗口的增长 也代表着实际发送窗口的慢慢增长,可以理解为一个对接收方的可承载数据量的试探的作用,可以很多的避免一发送就出现网络拥塞的情况。
拥塞控制-拥塞避免 (congestion avoidance)
因为拥塞窗口呈指数级的增长,会可能因为增长的过快而导致出现拥塞的情况。
为了避免这种情况,需要在增加的过程中做一些控制避免增长过快,达到拥塞避免的作用。

ssthresh: ssthresh 是慢开始的一个界限值,当通过慢开始到指数级的增长的时候,如图示是 1600.当拥塞窗口到达此阈值 的时候,表示将增长变的慢下来,将不再指数级的增加,而是通过 每次增加一个固定的值,呈一个线性的增长,能够对拥塞得到有效的避免增长过快。(变为加法增加)
当经过加法增大到最大的值,结果出现了网络拥塞,那么会将ssthresh 的值 减小,同时拥塞窗口呈快速的减少.乘法级减少。直到减少到慢开始的位置。接着又执行慢开始的指数级增长,因为ssthresh 的值已经变小,所以加法增大也会提前来到。
拥塞控制-快速重传 (fast retransmit)
对于需要重传的数据包及时的让对方的知道是比较重要的,可以防止丢失的数据包一直未发送,而后面已经发送了许多,已经让重发丢失数据包处于一个比较高的优先级。

对于接收方: 每收到一个失序的分组就立即发出重复确认,让发送方及时得知有没有分组到达,而不等待自己需要发送数据的时候才确认。
对于发送方: 只要连续收到3个重复确认,(共4个相同的确认),就应该立即重传对方没有收到的报文段。而不必等待重传计时器到期后再重传。丢失分组内的数据后,接收方会连续发送确认消息催促丢失数据包的发送。一定时间间隔,连续发送,最多4次。
拥塞控制-快重传 + 快速恢复 (fast recovery)
当发送方连续收到重复确认,既出现了包丢失了,出现了网络拥塞,就执行了乘法减少,并且ssthresh减半。
而慢开始并不是直接将拥塞窗口减少到慢开始的状态,而是减少到ssthresh的值的大小就不再减少了。

拥塞后减少到慢开始的方式已经被废弃,而是用性能更加号的快速恢复模式,可能对流量有更高效的控制
TCP 三次握手
tcp 的三次握手,也就是建立连接的过程需要发送3次请求。
当发起连接的请求时,tcp头部的标志位SYN 标记为1 ,表示要跟对方连接。
发起方的第一次要发起的连接请求,因为之前对方还未发数据,所以不需要ACK 回复,ACK标识位 为0.
建立连接过程

客户端(CLOSED关闭状态) 开始向服务器建立连接请求
SYN 设置1 表示是连接请求,ACK 设置0 表示不需要确认对方的请求(对方还没发送呢),seq 给定一个客户端的序号的初始值。
服务端(LISTEN监听状态) 服务端监听到客户端发来的连接请求
因为还未建立连接,服务端向客户端发送SYN 为1 的连接请求,同时对客户端的连接请求进行确认,所以ACK 标识位 为1 。序号值为自己的初始序号值,ack 对客户端的请求进行响应,只有头部,所以ack的值 是上次客户端发送的seq的下一个字节,既 客户端的初始值 + 1
当客户端已经发送同步请求后,状态就变为 (SYN_SENT同步已经发送状态)
当客户端接受到服务端的确认后,客户端就可以确认服务端和自身的连接正常。自身状态就为ESTABLISHED连接已建立
同时对服务端的同步请求进行确认。客户端对服务端的同步请求已经完成,所以最后一次发送数据的SYN = 0,表示仅仅是对服务端的同步请求的回应,自身不需要和服务端同步了,因为已经完成了。
服务端在发送完客户端的响应后,状态就变为 (SYN_RCVD同步已接收)
只要等待客户端把自身的同步请求确认后,自身状态就成为了(ESTABLISHED连接已建立)
- 在SYN = 1 的请求的时候,在tcp头部的可选位置存储一些额外的交换信息,比如MSS window scale 信息.
连接确认的可选信息
通过抓包工具查看连接信息.


这是3次握手信息的网络包
其中前2次的tcp头部的options 是有一些数据值。

- Maximum Segment Size 数据包的最大值的大小,这个值是跟网络层的MTU 有关的,这里是65495,下面会详细说明这里的值为什么这么大。
- window scale 窗口比例,window 的值 乘以 window scale = 窗口的真实值。这里为8 ,表示传输值的时候除以了8.
- SACK permitted 表示支持SACK
Maximum Segment Size 值的问题
Maximum Segment Size 的值 = MTU - TCP 的头部部分大小。 之前说到MTU的最大值是 1460 ,而这里是 65495。
这是因为MTU的值是跟网络环境有关系的,这里是本地的访问环境。

MTU 是数据链路层的每个数据帧的单位,
所以 MMS = MTU - 网络层的首部 - 运输层的首部。网络层的首部是和运输层首部固定部分都是20;
1 | 65535 - 20 -20 = 65495 |
为什么必须有三次握手
防止已经释放的连接请求到又达到了服务端,产生错误,让服务端又以为有客户端来请求了连接。
防止服务端长时间等待,造成资源浪费。
如果只需要2次握手
客户端的第一个请求报文段,因为网络延迟,在连接释放后的一个时间才到达server。
如果这个请求是一个已经释放的请求,服务端接收到此请求后,认为客户端要建立连接,就直接向这个客户端发送确认报文段,同意连接,这时候客户端不会处理此请求,但是服务端在发出确认请求的时候就认为连接已经建立。
服务端就一直在等待客户端的请求了,但是客户端是不会发送的,这样server的很多资源就白白浪费了。
如果使用三次握手,那么如果客户端没有再向服务端发送响应报文段,此连接就建立不起来,防止了此情况的产生。
TCP 四次挥手
当连接一方向另一方发送 FIN 报文段 (FIN = 1)的请求后,表示要断开连接。
释放连接的过程

客户端和服务端都处于 ESTABLISHED连接已建立状态
客户端要释放连接,向服务端发送FIN 报文,ACK 表示对服务端之前的数据的确认
服务端接收到客户端的释放连接后,向客户端发送 连接释放的确认响应。服务端进入 CLOSE-WAIT 关闭等待
但是服务端如果有正在进行的连接,需要等待数据都传输完毕。等传输完毕后,给客户端发送一个FIN 的释放连接报文。服务端进入 LAST-ACK最后确认 状态
客户端接收 在发送FIN报文后,进入 FIN-WAIT-1 终止等待1 状态,表示结束请求发出了,但是还没收到响应,当收到服务端的释放确认后,状态就变为 FIN-WAIT-2 状态,此时客户端不发送数据了,但是还能接收数据。等待服务端对自己释放连接。
客户端收到服务端的FIN报文,并发出ACK 报文后,将进入到 TIME-WAIT 时间等待,进入时间等待后,客户端等待2MSL后进入到CLOSED 关闭
服务端收到客户端的确认后,将处于CLOSED 关闭
client 发送ACK 后,时间等待的大小一般是 2倍的 MSL (Maximum Segment Lifetime)最大生存周期.
MSL 是tcp报文在网络上的最长生存时间,每个TCP实现都有一个固定的值,RFC_1122 建议是 2分钟。
为什么必须四次分手
因为tcp是全双工通信,在一方断开连接的时候,可能另一方还有数据在发送数据。
而如果使用四次的方式,是各个控制各个的发送释放,自己只要没数据发送了,就告诉对方一下,连接是否要断开,由发送方决定,不至于数据的丢失。
第一次挥手的时候,主机1 告诉主机2,主机1 没有数据发送了,但是主机1 仍然可以接收主机2正在发送的数据。
第二次挥手的时候,主机2知道了主机1 没有数据发送了,可能主机2 已经关闭了接收缓存,但是如果有数据还未发送完毕,还可以继续发送。
第三次挥手的时候,主机2 告诉了主机1,主机2也没有数据发送了。
第四次挥手的时候,主机1确认返回确认信息,主机1也知道了主机2没有数据发送了,互相都没有数据发送,可以安全的断开连接了。
注意:如果第一次挥手的时候,服务端也没有数据要发送了,服务端会通过一个请求确认 客户端的释放,同时请求服务端的释放,看起来好像3次挥手,其实是第二次和第三次的合并到一个请求了。
UDP
UDP(User DataGram Protocol) 用户数据报协议,udp是面向流的。
UDP 是没有连接的传输,在传输的过程中不会保持连接状态,只是直接将数据包发送到目标地址。同时因为没有连接,所以没有建立连接和释放连接的性能开销。
缺点:可能丢失数据包。
优先:性能好,适合大数据量的传输。
UDP的格式

UDP 的数据格式 分为头部和数据部分。
头部包含了4个部分。
16位源端口号(2)
端口占用2个字节(0-65535)
客户端发送数据的时候会开启一个随机的端口号,用完即关闭。
16位目的端口号(2)
目标主机的端口号,比如服务器的tomcat端口号 8080。
16位UDP长度(2)
存储UDP的头部 + 数据部分的总长度
16位校验和(2)
校验和计算 = 伪首部 + 首部 +数据

在计算首部的时候会根据 源ip + 目的ip + udp长度 + 填充0 + 17 表示udp 得到一个伪首部。
在计算校验和的时候 会加上伪首部的信息进行计算。
伪首部并不会被传输,仅仅用来计算校验和信息
TCP和UDP的比较
| TCP | UDP | |
|---|---|---|
| 连接性 | 面向连接 | 没有连接 |
| 可靠 | 可靠传输,数据安全 | 非可靠,可能丢包 |
| 首部空间 | 占用大 (20 - 40 字节) | 占用小 (8) |
| 传输速率 | 慢 | 快 |
| 资源消耗 | 消耗大 | 消耗小 |
| 应用场景 | 浏览器,文件,邮件 | 音视频 |
| 应用层协议使用 | http https smtp | dns |


