0%

计网之传输层(TCP/IP)详解

[toc]

1 传输层和网络层的区别:#

1.1 负责对象的不同#

  • 传输层负责建立进程间的通信(即只关心进程A和进程B的消息传递,不考虑底下的东西)
  • 网络层负责建立主机间的通信(即只关心主机A怎么发给主机B)

同理链路层就是物理上相邻的2个机器如何通信,物理层是电路上的通信

1.2 校验上的不同#

  • 传输层会对数据和首部一起做校验
  • 网络层只对首部做校验,数据报部分不会关心。

2 端口号#

端口号是传输层重要的概念。

2.1 端口号的意义#

① 为进程适应不同操作系统提供一个复用和分用的软件

复用:发送方不同进程都用同一种协议传输,目的端口号承担了这个工作
分用:接收方把IP报文组装交付时,通过端口号确认交付给谁

② 端口是进程的重要交互地址。
即由端口确定交给主机上的哪个进程。
③ 只具有本地意义,只对本机有效

2.2 端口号范围#

端口号共16位,因此最大为65535

  • 服务器端口号(做listen用的端口号)有2类:

    1. 熟知端口号:0~1023

    熟知端口号有以下:
    HTTP:80
    HTTPS:443
    DNS:53
    FTP:21
    TFTP:69
    SNMP:161
    TELNET:23
    SMTP:25

    1. 注册端口号: 1024~49151,也叫系统端口号。有许多服务绑定于这些端口。
    2. 动态和/或私有端口:49152~65535,理论上,不应为监听服务分配这些端口,一般是用于客户端的短暂端口号。
  • UDP和TCP有各自的专属端口号,因此不会重复和干扰。

3 TCP#

TCP全称
Transmission Control Protocol, 即传输控制协议。

基于套接字传输
套接字:IP+端口号

tcp的可靠性如下:

  1. 数据被分隔成很多份合适的块来发送
  2. 有超时重传机制
  3. 会校验首部和数据内容。
  4. 会对收到的数据报文做排序,按顺序组装上交
    5.会丢弃重复的数据
    6.提供流量控制,发送主机不会一下子发送太快
    7.每次传输都有序号和ack应答,全双工。

3.1 TCP首部#

位数 含义 详情
16 源端口 因此端口号最多16位
16 目的端口
32 报文序号 该报文的序号
32 确认号 期望收到的下一个报文序号。如果确认号位N,说明N-1都已经收到
4 数据偏移 数据部分的起始位置,可以理解为首部长度。单位是4字节。
6 保留位 没用,全部置成0
1 URG紧急指针标志 是否存在紧急数据
1 ACK标志 是否是确认报文
1 PSH推送标志 是否需要尽快上交进程
1 RST复位标志 是否需要重建连接
1 SYN同步建立连接标志 是不是连接建立期间的报文
1 FIN终止标志 是否是终止连接期间的报文
16 窗口 收到ack前对方可发送回来的数据量
16 校验和 校验首部+数据+伪首部(伪首部指携带了ip的首部)
16 紧急指针位置 紧急数据放在末尾,需要给出紧急数据的长度,便可推断位置
0-320 可变选项 一些TCP选项,例如 最大报文长度、时间戳等。最长40字节
? 填充位 保证首部长度为4字节整数倍

从上面可以看到:

  1. TCP报文中不包含ip信息,只包含端口信息。
  2. 校验和中使用到了伪首部来做校验,即实际上是有ip信息被处理后包含在校验和中了。
  3. TCP首部最小长度为20字节,最大长度为60字节,取决于可变选项。
  4. 首部长度必定是4字节整数倍,不足会填充,因为数据偏移里的单位设置是4字节。

Q: 讲一下TCP报文都有啥,可以不按顺序讲#

A:

  1. 首先是首部长度(数据偏移),确定读到哪
  2. 重要的源端口 目的端口
  3. TCP 可靠性机制关键的一些数字:
    报文号、ACK号、窗口、 校验和
  4. 建连接、关连接用的标记
    一堆标记(SYNC、FIN、RST之类的)
  5. 其他无关紧要的紧急指针位置、可变选项,填充位

3.2 滑动窗口#

在TCP的发送端和接收端,有一个窗口的概念,直接用图片的方式简单明了回忆一下:

3.2.1发送端窗口#

在这里插入图片描述


3.2.2 接收端窗口#

在这里插入图片描述

这里可以看到接收端一次性接收的缓存是有限的,所以进程出现问题迟迟没有接收数据,那么会在ack里告知还能发多少份。
这个叫通告窗口,即告知接收端还能收几份数据(TCP报文里的窗口位就是这个)

3.2.3 坚持定时器#

如果接收端的接收缓存用完,导致返回的ack报文里提示窗口为0,则发送端无法发送数据,此时会启动坚持定时器:
每隔5s发送1个字节的小报文,来查看对方窗口响应。当窗口不再为0,则结束坚持定时器

3.2.4 糊涂窗口#

上面提到的坚持定时器机制里,导致了每次只发一小点数据。
避免措施:
1. 接收方设定一个最小窗口阈值,不通告小窗口
2. 发送方设定一个最小窗口阈值,每次发送满一定长度的报文
3. 发送手头所有数据切不接收ack

3.3 TCP连接流程#

TCP通过三次握手建立连接,四次挥手结束连接

3.3.1 握手和挥手流程#

把这个图牢记于心就不会有问题:
在这里插入图片描述

CLOSED状态:建立连接前的初始状态即关闭状态在建立连接前,先从CLOSED状态变成LISTEN状态(监听状态,表示可以传信号了)

  1. 当客户首先发送SYN后,客户变成SYN_SENT状态。
  2. 当服务器接收到SYN后,服务器变成了SYN_REVD状态。
  3. 当服务器传给客户一个SYN和ACK后,变成了ESTABLISHED状态(表述开始进行数据传输)
  4. 当服务器接受到客户传来的ACK后,也变成ESTABLISHED状态。
  5. 当客户发送FIN(主动关闭)后,客户变成了FIN_WAIT1状态。服务器收到FIN后,执行被动关6. 闭,服务器变成了CLOSEWAIT状态。
  6. 服务器先发送ACK,客户收到ACK后变成FIN_WAIT2状态。
  7. 过了一段时间,服务器才发送FIN,这时候服务器变成LAST_ACK状态客户收到FIN后,变成了TIME_WAIT状态,同时发送ACK,进行2MSL等待。之后2者一起变为CLOSED状态

为什么建立要3次握手?#

建立握手3次原因:一方接收到syn报文后,需向对方回应一个ack。三次握手中,第一个是sync报文,第二个是ack、sync报文合在一起,第三个ack报文。这样就都回应了ack,需要3次。
关键在于最后一次需要一个对 接收端sync的ack响应。


为什么结束连接要4次挥手?#

TCP中发送端发送fin后,就会将自己关闭。
但是接收端一方接收到fin报文后,数据可能还没发送完成。
所以需要先发完ack,再发fin,所以这里会多一次挥手。 最后ack是对fin的确认


能否挥手3次#

。收到第一个fin报文后,它可能刚好没有数据要传输了,fin和ack报文一起回应,对方再回应ack,总共三次,挥手完毕。实际中抓报文,有很多这样的情况。


四次挥手中的TIME_WAIT状态作用?#

A:

1.若最后一步客户发出的ACK丢失了,那么服务器将重发FIN,所以必须维持TIME_WAIT状态到可能的第二次重发FIN的时间。

2.为了避免在终止连接后再次重新建立新连接时,收到之前那次连接“迷路”的分组,要维持一个TIME_WAIT状态以便吸收掉迷路的分组。


两边同时相互建立连接会发生什么?#

2边同时发送SYNC
2边同时收到后, 就会发现自己在还没收到SYNC-ACK的情况下收到了新的SYNC,说明发生了"同时打开"的情况。
此时他会直接发送ack,并且不再等待sync-ack响应了,直接进入ESTABLISHED状态。所以此时仅需2次握手(当然整体上看是4次,一边各2次)

两边同时关闭连接会发生什么#

当2者发生同时关闭,即同时发出FIN时,会进入CLOSING状态,收到相互的ACK后进入TIME_WAIT状态. 即此时需2次挥手(整体上看是4次)

3.3.4 交互数据#

在这里插入图片描述

每一个交互按键都会产生一个数据分组,每次从客户传到服务器的是一个字节的按键。而Rlogin需要远程系统回显客户键入的字符,这样就会产生4个报文段:
(1)来自客户的交互按键
(2)来自服务器的按键确认
(3)来自服务器的按键回显
(4)来自客户的按键回显确认

  • nagle算法#

    当数据交互很快时, 可能会有很多小分组。
    开启nagle后,会把小分组做合并一起发送。


Q: 什么是TCP粘包?#

A:
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包
从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾
出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。


Q: 造成粘包的原因是什么?#

A:
发送方原因: 开启了nagle算法
接收方原因:如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包


Q:什么时候需要处理粘包现象?#

A:
如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
如果多个分组毫不相干,甚至是并列关系,导致应用层取出时无法识别各份数据,那么这个时候就一定要处理粘包现象了


Q: 如何处理粘包现象?#

A:

  1. 发送方
    对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。
  2. 接收方
    接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
  3. 应用层
    解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
  • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束
  • 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置

Q:UDP会不会产生粘包问题呢?#

A:
UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。

3.3.5 异常情况#

  • 异常情况时,会把报文里的复位表示RST置为1。有3种情况会发送复位报文
    ①端口不存在
    ②进程异常终止
    ③客户端异常退出, 服务器没有收到任何fin,此时称为TCP半打开状态。如果TCP配置了心跳,则可以检测

  • 半关闭
    TCP的半关连接是指:TCP连接只有一方发送了FIN,另一方没有发出FIN包,仍然可以在一个方向上正常发送数据。

  • 半连接
    三次握手中,主动发起握手的一方不发最后一次ACK,使得服务器端阻塞在SYN_RECV状态半连接攻击(SYN攻击):会耗尽服务器资源,使得真正的请求无法建立连接。

3.4 拥塞避免机制#

这块概念很多很乱,我按问题整理了一下,一步步来
TCP的拥塞避免等机制对于初学者来说还是比较复杂的,工作中如果开发时偏应用层,那么大部分时候就会摸不到这个机制,感受也就没那么深了。
但如果你的开发过程涉及数据传输,一直在重传、超时之类的方案里有困惑的话,不妨重新学一学可靠性最精致的TCP协议。

所以这里我抛去死记硬背的那堆概念,用10个连续的问题来学习这个机制,注意看的时候先自己思考一下如果是自己,会怎么设计,再去看实际的TCP设计,来理解它的精妙之处。


Q: 建立连接后,每次发送的报文数量是固定的吗?即每次都发1条或者10条?#

A:
不是。
建立连接后,会先只发1条, 然后发2条,接着再发4条,逐步增加。
这个过程叫 “慢启动”
这个1、2、4递增的数量被称之为 拥塞窗口 cwnd

可以理解为TCP希望刚开始,可以大胆点,不断加数量。但为了保险期间还是从1条还是倍增。


Q: 慢启动过程中,那么发送数量(拥塞窗口)什么时候不再倍增?是无限倍增吗?#

A:
不会无限倍增。
当到达慢启动门限ssthreshold时,会变成每次都增加1条。
这个过程叫拥塞避免过程, 也有叫他拥塞避免算法的

可以理解为tcp感觉到有风险了,于是开始慢慢地、小心翼翼地1条1条地添加发送条数。


Q:那么,当进入拥塞避免,每次+1时,什么时候才会不再继续加?#

A:
随着每次发送的数量越发越多, 最终会超出带宽限制,于是就会有某条报文发生超时。
有可能是发的中途丢了, 亦或者是返回的数据全阻塞住了,一条都回不来。

当发送端检测到发生超时时,就会让 慢启动门限ssthreshold = 当前拥塞窗口cwnd/2
接着cwnd 重新置为1,从新开始 慢启动算法。

这样的好处在于可以检测到每次发送的上限,动态调整发送窗口。
上面的过程叫做 超时重传
注意发生超时重传时, cwnd会重置成1。


Q: 上面提到了超时, 那么TCP客户端是怎么判断报文发送超时的呢?#

A:
每次发送数据包的时候, 都会有一个相应的计时器,一旦超过 RTO(超时时间) 而没有收到 ACK, TCP就会重发该数据包。
没收到 ACK 的数据包都会存在重传缓冲区里,等到 ACK 后,就从缓冲区里删除。


Q:上面提到的超时时间RTO是怎么来的?万一设得太大可能导致很迟才能反应过来, 设得太小则可能导致每条都超时#

A:
通过“每次报文的往返时间样本”和“之前样本的偏差值”动态计算出来的。

  • RTT : 报文往返时间(指从发送到收到ack的时间)。每个报文发出后都有个定时器,收到后都会计算出一个RTT样本

  • RTTs: 加权平均往返时间,类似于一个估算的往返时间,实时在变。
    RTTs = (1-a) * RTTs + a * RTT最新样本
    即每次得到RTT样本后, ?都会使用a这个占比去更新RTTs。

  • RTTd: ?RTT偏差加权平均值(就是用来计算超时时间应该比RTT多多少)
    RTTd = (1 - b) * RTTd + b*RTTs - RTT最新样本
    即每次会用新的RTTs以b的占比去更新一下RTTd,并减去RTT样本

  • RTO : 超时重传时间
    等于平均往返时间 加上 4倍偏差值
    RTO = RTTs + 4*RTTd?

Q: 如果发生重传,却还是没有收到ack,那么最新的RTT样本应该怎么算?即你都收不到最新的ack了, RTT难道取超时时间吗?#

A:
会使用karn算法: 发生重传时,不更新这次的RTT样。选用后面收到的ack
修正karn: 为了避免发生重传后,实际RTT都变慢了,导致一下子所有请求都超时, 会在发生重传时,把RTO假大1倍。


Q: 上面提到的ACK超时判断会不会太久了? 假如只是发的时候丢了中间部分报文而已, 但大部分报文ACK还能正常返回,也要一直等超时吗?#

A:
如果能正常接收其他报文的ACK, 只是中间的部分报文丢了, 则有另一个办法。

接收端有一个冗余确认机制:

  1. 发送端A 发送 1、2、3、4、5四条
  2. 但是B接收端只收到1、2、4、5,而3因为网络拥塞丢了。
  3. 于是B会发送ack=3而不是ack=5 给A。 这就是冗余确认机制,只发送缺失那部分的ack,后面的4和5都不管。
  4. A收到ack=3后, 继续发送3、4、5、6、7, 结果3还是丢了。
  5. 于是B又发送ack=3。

当A发现连续3次收到了ack=3时,就会觉察到不对劲,我都发3次了你还是说没收到,可你又能正常返回其他ACK给我,是不是我发的太多了?

上面这个判断3次的重传算法叫“快重传”。

于是A会马上进入 “快速恢复”。
和之前类似,慢启动门限ssthreshold = 当前拥塞窗口cwnd/2
但是!! 新的拥塞窗口cwnd会设置成ssthreshold/2, 而不是1。
而且不会走慢启动倍增的那种,而是走拥塞避免, 逐步+1的那种。
image.png


Q: 前面“超时重传”的时候,是变成从1开始慢启动, 为什么这个“快重传”却是从ssthreshold/2开始,并且走拥塞避免? 为什么会有这个区别?#

A:
因为前面发生超时重传时, 是比较严重的情况, 超时时间内一个ACK都没收到。就好像来回数据都凭空消失了。

而快速重传发生时, 还是能收到部分ack的, 只是丢失了部分数据, 说明拥塞没那么严重,于是可以大胆一点将cwnd削减到1/4, 而不是直接从1开始。

到了这里,基本就能理清楚超时重传和快重传的区别了,重点是理解这2个区别是怎么来的。后面再补几个问题,避免你和其他概念搞混,但不会说得太深,具体需要你自己去扩展学习了。

Q: 为啥发送地多了,数据就会部分丢失?这个是怎么个原理?#

A:
路由器有缓存,IP分组接收过多时就会耗尽空间,丢弃数据。详细可以看路由器的数据转发原理。


Q: TCP除了上面的重传定时器, 好象还有个坚持定时器?区别是啥?#

A:
坚持定时器和超时、网络拥塞没有关系, 和通告窗口即对端的接收能力有关。
简单来说, 就是对方的传输层缓冲区(接收端窗口)满了,告诉你别发了,我吃不下了,于是返回通告窗口为0。
但你想知道啥时候可以发,于是就启动一个坚持定时器,每隔5s发送1个字节的小报文,小小地试探下。当通告窗口不为0了,就重新开始发。

4 UDP#

网络层的多播和广播机制,需要依赖传输层的UDP。

4.1 TCP和UDP的区别:#

  • TCP有连接, UDP无连接
  • TCP可靠, UDP不可靠,发出去不管了。也没有拥塞控制等机制。 不过UDP会做数据正确性校验。
  • UDP会一次性交付一个完整报文,不会做拆分,TCP可能会有小的分组。
  • UDP首部比较简单, 只有源端口、目的端口、报文长度、校验和、填充位。

4.2 UDP的一些特点#

  • 每次调用程序里多播的接口时,都会产生1个UDP消息,没有那种可以复用的UDP连接。

  • UDP数据报的最大长度,和应用程序可读写的数据报最大长度有关,和TCP/IP内核有关。
    当数据报长度大于程序可读写长度,会引发 数据截断。所以udp数据的长度必须要控制好,毕竟他无法根据MTU做分片。

  • 怎么确认MTU多大?
    可以用taceroute命令检测MTU, 本质上是把TCP报文设置成不分片,然后逐步增大,直到发生了ICMP不可达的报错。

  • 如果一次性发送了6个UDP数据报, 并且在链路层有6次ARP请求, 接收端收到6个UDP后,只会发送一个ARP响应。

  • UDP一般用于本地小范围通信, 所以差错其实相比TCP还小一点。

4.3 UDP的应用#

TFTP(小文件传输)
DNS(域名解析)
SNMP(简单网络管理协议)
IGMP
BOOTP(无盘系统引导)
RTP(实时传输协议)
多媒体应用


Q: UDP可以实现可靠传输吗?#

A:
可以实现,但必须在应用层做改造。

  1. 添加seq/ack机制,确保数据发送到对端
  2. 添加发送和接收缓冲区,主要是用户超时重传。
  3. 添加超时重传机制。
  • 详细说明:送端发送数据时,生成一个随机seq=x,然后每一片按照数据大小分配seq。数据到达接收端后接收端放入缓存,并发送一个ack=x的包,表示对方已经收到了数据。发送端收到了ack包后,删除缓冲区对应的数据。时间到后,定时任务检查是否需要重传数据。

  • 目前有多种开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT