c++tcp接收文件缓存多大合适_TCP/IP 精彩回顾必看

TCP/IP 协议出现的原因是互联网世界各个主机作为一个个独立的个体,如何制定统一的规则让他们互相通信是达成万物互联的纽带。基于此,设定了 TCP/IP 协议来规范网络访问行为。TCP/IP 并不是一个具体的协议,而是表示一系列协议的统称,包括 IP、ICMP、TCP 以及 HTTP、FTP、POP3 等等。个人主机遵循对应的协议就能与同样遵循该协议的第三方主机进行通信。

1.分层模型

网络协议通常分成不同的层次进行开发,每一层负责不同的通信功能。数据传输的过程就像发快递一样,每经过一个邮局,都加上当前邮局的印戳,告知邮差当前快递经过了哪里,下一个邮局又是哪。TCP/IP 通常被认为是一个四层的协议系统,由上至下为:应用层、传输层、网络层和链路层。

  • 应用层:定义上层应用可以直接使用的高级协议,如 HTTP、FTP 等;
  • 传输层:定义控制数据传输的协议,用以保证数据的可靠性和顺序到达性等,如 TCP、UDP 协议;
  • 网络层:定义不同网络类型间通信的协议,如 IP 协议用于实现网际路由,ICMP 协议用于检测网络的畅通性,ARP 协议用于获取设备 MAC 地址等;
  • 链路层:定义网络介质上的传输协议,和电气相关,如 Ethernet 协议、802.3 协议等,主要由操作系统的网卡驱动程序实现。

说到 4 层模型,我们自然联想到 OSI 的 7 层模型,有大佬总结了一个图,我就拿过来用:

d0e85857862979fbb49b100e05080fe3.png
7层模型

我们拿 TCP/IP 四层模型具体分析一下每一层都会做什么事情:

1.1 链路层

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

链路层做的事情很简单:把 0、1 按照字节为单位进行传输。以太网规定一组电信号就是一个数据包,一个数据包被称为一帧, 制定这个规则的协议就是以太网协议。一个完整的以太网数据包如下图所示:

04a869504a108f43621dd74d2ceaae86.png

整个数据帧由首部数据尾部三部分组成,首部固定为 14 个字节,包含了目标 MAC 地址、源 MAC 地址和类型;数据最短为 46 个字节,最长为 1500 个字节,如果需要传输的数据很长,就必须分割成多个帧进行发送;尾部固定为 4 个字节,表示数据帧校验序列,用于确定数据包在传输过程中是否损坏。因此,以太网协议通过对电信号进行分组并形成数据帧,然后通过物理介质把数据帧发送给接收方。那么以太网如何来识接收方的身份呢?

以太网规协议定,接入网络的设备都必须安装网络适配器,即网卡, 数据包必须是从一块网卡传送到另一块网卡。而网卡地址就是数据包的发送地址和接收地址,也就是帧首部所包含的MAC 地址,MAC 地址是每块网卡的身份标识,就如同我们身份证上的身份证号码,具有全球唯一性。MAC 地址采用十六进制标识,共 6 个字节, 前三个字节是厂商编号,后三个字节是网卡流水号,例如 5D-3E-11-3F-05-E1

有了 MAC 地址以后,以太网采用广播形式,把数据包发给该子网内所有主机,子网内每台主机在接收到这个包以后,都会读取首部数据中的目标 MAC 地址,然后和自己的 MAC 地址进行对比,如果相同就做下一步处理,如果不同,就丢弃这个包。

所以链路层的主要工作就是对电信号进行分组并形成具有特定意义的数据帧,然后以广播的形式通过物理介质发送给接收方。

1.2 网络层

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

链路层提到定位唯一一个设备的方式是查询网卡地址。那么在网络层要做的事情是:根据 IP 找到 IP 对应的设备。

我们可以思考一下:

IP 是如何和 MAC 地址对应上的呢?网络层主要提供以下协议进行传输和定位。

IP 协议

网络通信是基于 IP 协议来进行,MAC 地址不具备规律性所以无法用来作为通信协议,但是它是唯一的所以可以用来确定一台设备。

IP 地址目前有两个版本,分别是IPv4IPv6,IPv4 是一个 32 位的地址,常采用 4 个十进制数字表示。IP 协议将这个 32 位的地址分为两部分,前面部分代表网络地址,后面部分表示该主机在局域网中的地址。由于各类地址的分法不尽相同,以 C 类地址192.168.24.1为例,其中前 24 位就是网络地址,后 8 位就是主机地址。因此, 如果两个 IP 地址在同一个子网内,则网络地址一定相同。为了判断 IP 地址中的网络地址,IP 协议还引入了子网掩码, IP 地址和子网掩码通过按位与**运算后就可以得到网络地址。

由于发送者和接收者的 IP 地址是已知的(应用层的协议会传入), 因此我们只要通过子网掩码对两个 IP 地址进行 AND 运算后就能够判断双方是否在同一个子网。

IP 地址的组成:

IP 地址 = 网络地址 + 主机地址,比如:

86184f593aa089f0ce239f2f09d52829.png

IP 地址分类:

最初设计互联网络时,为了便于寻址以及层次化构造网络,每个 IP 地址包括两个标识码(ID),即网络 ID 和主机 ID。同一个物理网络上的所有主机都使用同一个网络 ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机 ID 与其对应。Internet 委员会定义了 5 种 IP 地址类型以适合不同容量的网络,即 A 类~E 类。

其中 A、B、C 3 类由 InternetNIC 在全球范围内统一分配,D、E 类为特殊地址。

IP 地址分为五大类:A 类、B 类、C 类、D 类和 E 类,如下图所示:

751c3b5968c3955e528829d5fd0fca93.png
IP地址分类
类别最大网络数IP 地址范围单个网段最大主机数私有 IP 地址范围
A126(2^7-2)1.0.0.0-127.255.255.2551677721410.0.0.0-10.255.255.255
B16384(2^14)128.0.0.0-191.255.255.25565534172.16.0.0-172.31.255.255
C2097152(2^21)192.0.0.0-223.255.255.255254192.168.0.0-192.168.255.255

IP 路由选择

当一个 IP 数据包准备好了的时候,IP 数据包(或者说是路由器)是如何将数据包送到目的地的呢?它是怎么选择一个合适的路径来"送货"的呢?

最特殊的情况是目的主机和主机直连,那么主机根本不用寻找路由,直接把数据传递过去就可以了。至于是怎么直接传递的,这就要靠 ARP 协议了,后面会讲到。

稍微一般一点的情况是,主机通过若干个路由器(router)和目的主机连接。那么路由器就要通过 IP 包的信息来为 IP 包寻找到一个合适的目标来进行传递,比如合适的主机,或者合适的路由。路由器或者主机将会用如下的方式来处理某一个 IP 数据包。

如果 IP 数据包的 TTL (生命周期)已到,则该 IP 数据包就被抛弃。

搜索路由表,优先搜索匹配主机,如果能找到和 IP 地址完全一致的目标主机,则将该包发向目标主机。

搜索路由表,如果匹配主机失败,则匹配同子网的路由器,这需要“子网掩码”的协助。如果找到路由器,则将该包发向路由器。

搜索路由表,如果匹配同子网路由器失败,则匹配同网号路由器,如果找到路由器,则将该包发向路由器。

搜索路由表,如果以上都失败了,就搜索默认路由,如果默认路由存在,则发包。

如果都失败了,就丢掉这个包。

以上过程说明:IP 包是不可靠的,因为它不保证送达。

9236879403003b58f8ae73e2b83ac4b3.png

内网 IP(内网保留地址)

Internet 设计者保留了 IPv4 地址空间的一部分供专用地址使用,专用地址空间中的 IPv4 地址叫专用地址。这些地址永远不会被当做公用地址来分配,所以专用地址永远不会与公用地址重复。

IPv4 专用地址如下:

IP 等级IP 段位默认子网掩码
A 类10.0.0.0 - 10.255.255.255255.0.0.0
B 类172.16.0.0 - 172.31.255.255255.255.0.0
C 类192.168.0.0 - 192.168.255.255255.255.255.0

特殊的网址

  1. 每一个字节都为 0 的地址0.0.0.0对应于当前主机;
  2. IP 地址中的每一个字节都为 1 的 IP 地址255.255.255.255 是当前子网的 广播地址;
  3. IP 地址中凡是以 11110 开头的 E 类 IP 地址都保留用于将来和实验使用;
  4. IP 地址中不能以十进制 127 作为开头,该类地址中数字 127.0.0.1127.255.255.255 用于回路测试,如:127.0.0.1 可以代表本机 IP 地址,用 “http://127.0.0.1” 就可以测试本机中配置的 Web 服务器;
  5. 网络 ID 的第一个 8 位组也不能全置为 0,全 0 表示本地网络。

ARP 协议

ARP 叫做地址解析协议,是根据IP 地址获取MAC 地址的一个网络层协议。其工作原理如下:

ARP 首先会发起一个请求数据包,数据包的首部包含了目标主机的 IP 地址,然后这个数据包会在链路层进行再次包装,生成**以太网数据包,**最终由以太网广播给子网内的所有主机,每一台主机都会接收到这个数据包,并取出标头里的 IP 地址,然后和自己的 IP 地址进行比较,如果相同就返回自己的 MAC 地址,如果不同就丢弃该数据包。ARP 接收返回消息,以此确定目标机的 MAC 地址;与此同时,ARP 还会将返回的 MAC 地址与对应的 IP 地址存入本机 ARP 缓存中并保留一定时间,下次请求时直接查询 ARP 缓存以节约资源。

4e291983388bf51e784aad98cc99ba84.png

一个典型的 ARP 缓存信息如下,在任意一个系统里面用“ arp -a ”命令:

root: $ arp -a
? (10.32.0.253) at ee:ff:ff:ff:ff:ff [ether] on eth0
? (10.32.0.38) at ee:ff:ff:ff:ff:ff [ether] on eth0
? (10.32.0.107) at ee:ff:ff:ff:ff:ff [ether] on eth0

RARP 协议

反向地址转换协议(RARP:Reverse Address Resolution Protocol)。反向地址转换协议(RARP)允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址。

RARP 工作原理:

  1. 将源设备和目标设备的 MAC 地址字段都设为发送者的 MAC 地址和 IP 地址,发送主机发送一个本地的 RARP 广播,能够到达网络上的所有设备,在此广播包中,声明自己的 MAC 地址并且请求任何收到此请求的 RARP 服务器分配一个 IP 地址;
  2. 本地网段上的 RARP 服务器收到此请求后,检查其 RARP 列表,查找该 MAC 地址对应的 IP 地址;
  3. 如果存在,RARP 服务器就给源主机发送一个响应数据包并将此 IP 地址提供给对方主机使用;如果不存在,RARP 服务器对此不做任何的响应;
  4. 源主机收到从 RARP 服务器返回的响应信息,就利用得到的 IP 地址进行通讯;如果一直没有收到 RARP 服务器的响应信息,表示初始化失败。

ARP 和 RARP 是一对协议,对比一下:

  1. ARP 广播后,只会有一个主机进行回应,就是携带某个 IP 的物理地址回应;而 RARP 由于有很多 RARP 服务器保存的有请求主机的 IP 和物理地址对应信息,所以只要保存有这个信息的主机都会回应;
  2. ARP 请求物理地址,即广播后想要得到的是物理地址;RARP 请求的是 IP 地址,即广播后想要得到的是 IP 地址;
  3. ARP 解析直接是内核实现,RARP 的解析主要是读写存放 IP 和物理地址映射的磁盘文件;
  4. 由于每个主机的 TCP/IP 都实现了 ARP 解析,也包含了本身的物理地址和 IP 地址,所以当收到 ARP 请求包,就知道是否要回送信息;
  5. 而 RARP 不同,为了给无盘主机进行系统引导,所以必须要有实现了 RARP 服务器的主机进行解析;在一般的主机 TCP/IP 实现中并没有实现 RARP 服务器,由于其本省的复杂性,而且又和链路层的广播有关,所以实现也不好,不实现也不好,最后由于内核一般不对磁盘文件进行读写,所以就没有实现。

ICMP 协议

上面说到 IP 协议并不是一个可靠的协议,它不保证数据被送达,那么保证数据送达的工作应该由其他的模块来完成,其中一个重要的模块就是ICMP (网络控制报文)协议

当传送 IP 数据包发生错误:比如主机不可达,路由不可达等等,ICMP 协议将会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这也就是为什么说建立在 IP 层以上的协议是可能做到安全的原因。ICMP 数据包由 8bit 的错误类型8bit 的代码16bit 的校验和组成。而前 16bit 就组成了 ICMP 所要传递的信息。

ICMP 协议大致分为两类:一种是查询报文,一种是差错报文。其中查询报文有以下几种用途:

  1. Ping 查询;
  2. 子网掩码查询(用于无盘工作站在初始化自身的时候初始化子网掩码);
  3. 时间戳查询(可以用来同步时间)。

而差错报文则产生在数据传送发生错误的时候。

Route 协议(路由协议)

通过 ARP 协议的工作原理可以发现,ARP 的 MAC 寻址还是局限在同一个子网中,因此网络层引入了路由协议,首先通过 IP 协议来判断两台主机是否在同一个子网中,如果在同一个子网,就通过 ARP 协议查询对应的 MAC 地址,然后以广播的形式向该子网内的主机发送数据包;如果不在同一个子网,以太网会将该数据包转发给本子网的网关进行路由。网关是互联网上子网与子网之间的桥梁,所以网关会进行多次转发,最终将该数据包转发到目标 IP 所在的子网中,然后再通过 ARP 获取目标机 MAC,最终也是通过广播形式将数据包发送给接收方。

而完成这个路由协议的物理设备就是路由器,在错综复杂的网络世界里,路由器扮演者交通枢纽的角色,它会根据信道情况,选择并设定路由,以最佳路径来转发数据包。

IP 数据包

在网络层被包装的数据包就叫IP 数据包,IPv4 数据包的结构如下图所示:

308ad18585235766229d73e7750a7b46.png

IP 数据包由 首部 和 数据 两部分组成,首部长度为 20 个字节,主要包含了目标 IP 地址和源 IP 地址,目标 IP 地址是网关路由的线索和依据;数据部分的最大长度为 65515 字节,理论上一个 IP 数据包的总长度可以达到 65535 个字节,而以太网数据包的最大长度是 1500 个字节,如果超过这个大小,就需要对 IP 数据包进行分割,分成多帧发送。

所以,网络层的主要工作是定义网络地址,区分网段,子网内 MAC 寻址,对于不同子网的数据包进行路由。

1.3 传输层

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

链路层定义了主机的身份,即 MAC 地址, 而网络层定义了 IP 地址,明确了主机所在的网段,有了这两个地址,数据包就从可以从一个主机发送到另一台主机。但实际上数据包是从一个主机的某个应用程序发出,然后由对方主机的应用程序接收。而每台电脑都有可能同时运行着很多个应用程序,所以当数据包被发送到主机上以后,是无法确定哪个应用程序要接收这个包。

因此传输层引入了 UDP 协议 来解决这个问题,为了给每个应用程序标识身份,UDP 协议定义了端口,同一个主机上的每个应用程序都需要指定唯一的端口号,并且规定网络中传输的数据包必须加上端口信息。这样,当数据包到达主机以后,就可以根据端口号找到对应的应用程序了。UDP 定义的数据包就叫做 UDP 数据包,结构如下所示:

c9761bb7dbaac220dccba04a549ac860.png

UDP 数据包由首部和数据两部分组成,首部长度为 8 个字节,主要包括源端口和目标端口;数据最大为 65527 个字节,整个数据包的长度最大可达到 65535 个字节。

UDP 协议比较简单,实现容易,但它没有确认机制, 数据包一旦发出,无法知道对方是否收到,因此可靠性较差,为了解决这个问题,提高网络可靠性,TCP 协议 就诞生了,TCP 即传输控制协议,是一种面向连接的、可靠的、基于字节流的通信协议。简单来说 TCP 就是有确认机制的 UDP 协议,每发出一个数据包都要求确认,如果有一个数据包丢失,就收不到确认,发送方就必须重发这个数据包。

为了保证传输的可靠性,TCP 协议在 UDP 基础之上建立了三次对话的确认机制,经过三次对话之后,主机 A 才会向主机 B 发送正式数据,而 UDP 是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发过去了。所以 TCP 能够保证数据包在传输过程中不被丢失,但美好的事物必然是要付出代价的,相比 UDP,TCP 实现过程复杂,消耗连接资源多,传输速度慢。

TCP 数据包和 UDP 一样,都是由首部和数据两部分组成,唯一不同的是,TCP 数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常 TCP 数据包的长度不会超过 IP 数据包的长度,以确保单个 TCP 数据包不必再分割。

总结一下,传输层的主要工作是定义端口,标识应用程序身份,实现端口到端口的通信,TCP 协议可以保证数据传输的可靠性

1.4 应用层

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

理论上讲以上三层协议已经可以完全支持数据从一台主机传输到另一台主机。但是考虑到数据传输的安全性,数据流粘包拆包相关,数据格式规范化等等问题,在应用层定义了各种各样的协议来规范数据传输的标准和基础的校验,常见的有 HTTP、FTP、SMTP 等,通过这些规范化协议约定数据传输双方的行为。

deb6647483b46f4dd2556eb13fba2a5e.gif

努力

飞翔

BUILD

YOUR

DREAM

2. TCP/UDP详解

2.1 TCP 协议

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

TCP 是面向连接的传输层协议。

每一条 TCP 连接只能是点对点的(一对一),提供可靠交付的服务。

TCP 面向字节流,提供全双工通信。

下面我们会对 TCP 的这些特性一一讲解。

2.1.1 TCP首部格式

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png
93bb1dca614352cfe1df31daf3460ca7.png
21

TCP 首部数据通常包含 20 个字节(不包括任选字段):

  1. 第 1-2 两个字节:源端口号;
  2. 第 3-4 两个字节:目的端口号;
  3. 第 5-8 四个字节:32 位序号。TCP 提供全双工服务,两端都有各自的序号。编号:解决网络包乱序的问题;基于时钟生成一个序号,每 4 微秒加一,到 `2^32- 时又从 0 开始;
  4. 第 9-12 四个字节:32 位确认序列号。上次成功收到数据字节序号加 1,ack 为 1 才有效。确认号:解决丢包的问题
  5. 第 13-14 位字节:前 4bit 为首部长度,包括 TCP 头大小,指示何处数据开始;后面 6bit 为保留位,这些位必须是 0,为了将来定义新的用途而保留;最后面 6bit 为标志域,表示为:紧急标志、有意义的应答标志、推、重置连接标志、同步序列号标志、完成发送数据标志。按照顺序排列是:URG、ACK、PSH、RST、SYN、FIN;
  6. 第 15-16 两个字节:窗口大小,接收端期望接收的字节数。窗口大小是一个 16 字节字段,因而窗口大小最大为 65535 字节;
  7. 第 17-18 两个字节:校验和。由发送端计算和存储,由接收端校验;
  8. 第 19-20 两个字节:紧急指针。指向后面是优先数据的字节,在 URG 标志设置了时才有效。如果 URG 标志没有被设置,紧急域作为填充。加快处理标示为紧急的数据段;
  9. Options:这个是额外的功能,提供包括安全处理机制、路由纪录、时间戳记、 严格与宽松之来源路由等;
  10. Padding:由于 Options 的内容不一定有多大,但是我们知道 IP 每个数据都必须要是 32 bits, 所以,若 Options 的数据不足 32 bits 时,则由 padding 主动补齐;
  11. Data :该 TCP 协议包负载的数据。

2.1.2 TCP中常见的位码表示

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

位码即 TCP 标志位,有 6 种标示:

  • SYN:synchronous 建立连接,连接建立时用于同步序号。当 SYN=1,ACK=0 时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得 SYN=1,ACK=1。因此,SYN=1 表示这是一个连接请求,或连接接受报文。SYN 这个标志位只有在 TCP 建立连接时才会被置 1,握手完成后 SYN 标志位被置 0;

  • ACK:acknowledgement 确认,占 1 位,仅当 ACK=1 时,确认号字段才有效,ACK=0 时,确认号无效;

  • PSH:push 传送,提示接收端立即从缓冲区把数据取走;

  • FIN:用来释放一个连接。FIN=1 表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接;

  • RST:reset 重置,要求重新建立连接。

    假设有一个合法用户 1.1.1.2 已经同服务器建立了正常的连接,攻击者构造攻击的 TCP 数据,伪装自己的 IP 为 1.1.1.2,并向服务器发送一个带有 RST 位的 TCP 数据段。服务器接收到这样的数据后,认为从1.1.1.2 发送的连接有错误,就会清空缓冲区中建立好的连接。这时,如果合法用户 1.1.1.2 再发送合法数据,服务器就已经没有这样的连接了,该用户就必须重新开始建立连接。因此在面试中经常会问产生 RST 包的各种原因:

    • RST=1:复位,表示当前连接存在问题需要重新建立连接,有时也用来拒绝非法报文段或拒绝打开一个连接。当发送 RST 包关闭连接时,无需通过四次挥手,可以直接关闭。而由于这并不是 TCP 连接中必须的一部分,因此不需要将 ACK 置 1(对应上边连接建立后,ACK 必须置 1),但可以置 1。

    • 这是个很危险的字段,可能被用来实现 RST 攻击:

  1. 服务器端口未打开而客户端来连接。很常见,特别是服务器程序 core dump 之后重启之前连续出现 RST 的情况会经常发生。但是某些 OS 的原因,可能不会出现这种状况,如向一台 WIN 7 主机发送一个连接不存在的端口的请求,这台主机就不会响应:
  2. 连接超时。例如 11, 12 两台主机,11 主机用 setsockopt 的 SO_RCVTIMEO选项设置了 recv 的超时时间,然后 11 向 12 发送 SYN 包请求连接,12 回应了一个 SYN 表示可以连接,但是回应太慢,超过了那个 recv 时间限制,所以 11 就直接又发了个 RST 包,又拒绝连接了;
  3. 提前关闭。服务器就是想要尽快关闭连接,就可以发送一个 RST;
  4. 收到一个不存在的连接上的报文。如 TCP 收到一个报文,但是根据它的端点套接字,找不到一个符合要求的套接字对(表示一条连接,前文讲的),那说明此连接有错了,就发送一个 RST 包。

URG:urgent 紧急,紧急指针是否有效,为 1 表示某一位需要优先处理;

Sequence number:顺序号码,占 4 个字节,用来标记数据段的顺序,TCP 把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生。给字节编上序号后,就给每一个报文段指派一个序号,序列号 seq 就是这个报文段中的第一个字节的数据编号;

Acknowledge number:确认号码,占 4 个字节,期待收到对方下一个报文段的第一个数据字节的序号,序列号表示报文段携带数据的第一个字节的编号,而确认号指的是期望接收到下一个字节的编号,因此当前报文段最后一个字节的编号+1 即为确认号。

2.1.3 TCP 建立连接为什么需要 3 次握手,关闭连接需要 4 次挥手

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png
很多同学在回答这个问题:TCP 是如何保证请求的安全性,思考良久后给出的答案是通过 3 次握手和 4 次挥手来保证。这个问题给出这个答案说明很多同学对 TCP 协议只停留在简单面试的层面上。

建立连接的握手过程只是保证信道可用,信道可用的前提下才能谈请求的安全与否;关闭连接 4 次挥手是为了确保关闭之前数据传输完毕,以及确定关闭的是要关闭的连接。这些跟连接的安全性并无直接关系。

3 次握手

  1. 第一次握手:客户端将标志位 SYN=1,随机产生一个值 seq=x,并将该数据包发送给服务器端,客户端进入 SYN_SENT 状态,等待服务器端确认。
  2. 第二次握手:服务器端收到数据包后由标志位 SYN=1 知道客户端请求建立连接,服务器端将标志位 SYN 和 ACK 都置为 1,ack=x+1,随机产生一个值 seq=y,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN_RCVD 状态。
  3. 第三次握手:客户端收到确认后,检查 ack 是否为 x+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1, ack = y + 1,并将该数据包发送给服务器端,服务器端检查 ack 是否为 y+1,ACK 是否为 1,如果正确则连接建立成功,客户端和服务器端进入 ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
728199bd58ec110b99d91150cf5689fa.png
3次握手

4 次挥手

假设关闭连接请求是由客户端发起的:

  1. 第一次挥手:客户端发送 FIN=1,seq=x 的包给服务端,表示自己没有数据要进行传输,单面连接传输要关闭。发送完后,客户端进入 FIN_WAIT_1 状态。
  2. 第二次挥手:服务端收到请求包后,发回 ACK=1,ack=x+1 的确认包,表示确认断开连接。服务端进入 CLOSE_WAIT 状态。客户端收到该包后,进入 FIN_WAIT_2 状态。此时客户端到服务端的数据连接已断开。
  3. 第三次挥手:服务端发送 FIN=1,seq=w 的包给客户端,表示自己没有数据要给客户端了。发送完后进入 LAST_ACK 状态,等待客户端的确认包。
  4. 第四次挥手:客户端收到 FIN=N 报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送 ack=w+1 后进入 TIME_WAIT 状态,如果服务端没有收到 ACK 则可以重传。服务器端收到 ACK 后,就知道可以断开连接了。客户端等待了 2MSL 后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。
3c5e54795cf4a2b8970f12afe5df3dad.png
四次挥手

上图中有几个状态位有几个需要解释一下:

FIN_WAIT_1:FIN_WAIT_1 和 FIN_WAIT_2 状态的真正含义都是表示等待对方的 FIN 报文。而这两种状态的区别是:FIN_WAIT_1 状态实际上是当 SOCKET 在 ESTABLISHED 状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该 SOCKET 即进入到 FIN_WAIT_1 状态。而当对方回应 ACK 报文后,则进入到 FIN_WAIT_2 状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应 ACK 报文,所以 FIN_WAIT_1 状态一般是比较难见到的,而 FIN_WAIT_2 状态还有时常常可以用 netstat 看到。

TIME_WAIT: 表示收到了对方的 FIN 报文,并发送出了 ACK 报文,就等 2MSL 后即可回到 CLOSED 可用状态了。如果 FIN_WAIT_1 状态下,收到了对方同时带 FIN 标志和 ACK 标志的报文时,可以直接进入到 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。

MSL(最大分段生存期):指 TCP 报文在 Internet 上最长生存时间,每个具体的 TCP 实现都必须选择一个确定的 MSL 值。RFC 1122 建议是 2 分钟,但 BSD 传统实现采用了 30 秒。TIME_WAIT 状态最大保持时间是 2 * MSL,也就是 1-4 分钟。

1、 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的建连请求后,它可以把 ACK 和 SYN ( ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的 FIN 报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭 SOCKET,也即你可能还需要发送一些数据给对方之后,再发送 FIN 报文给对方来表示你同意现在可以关闭连接了,所以它这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的。

2. 为什么不能用两次握手进行连接?

我们知道,3 次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机 A 和 B 之间的通信,假定 B 给 A 发送一个连接请求分组,A 收到了这个分组,并发送了确认应答分组。按照两次握手的协定,A 认为连接已经成功地建立了,可以开始发送数据分组。可是,B 在 A 的应答分组在传输中被丢失的情况下,将不知道 A 是否已准备好,不知道 A 建立什么样的序列号,B 甚至怀疑 A 是否收到自己的连接请求分组。在这种情况下,B 认为连接还未建立成功,将忽略 A 发来的任何数据分组,只等待连接确认应答分组。而 A 在发出的数据分组超时后,重复发送同样的数据分组。这样就形成了死锁。

3. 为什么 TIME_WAIT 状态还需要等 2MSL 后才能返回到 CLOSED 状态?

什么是 2MSL?MSL 即 Maximum Segment Lifetime,也就是报文最大生存时间,引用《TCP/IP 详解》中的话:“它(MSL)是任何报文段被丢弃前在网络内的最长时间。”那么,2MSL 也就是这个时间的 2 倍,当 TCP 连接完成四个报文段的交换时,主动关闭的一方将继续等待一定时间(2-4 分钟),即使两端的应用程序结束。

为什么需要 2MSL 呢?

虽然双方都同意关闭连接了,而且握手的 4 个报文也都协调和发送完毕,按理可以直接回到 CLOSED 状态(就好比从 SYN_SEND 状态到 ESTABLISH 状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的 ACK 报文会一定被对方收到,因此对方处于 LAST_ACK 状态下的 SOCKET 可能会因为超时未收到 ACK 报文,而重发 FIN 报文,所以这个 TIME_WAIT 状态的作用就是用来重发可能丢失的 ACK 报文。

TIME_WAIT 状态存在的理由:

  1. 可靠地实现 TCP 全双工连接的终止在进行关闭连接四路握手协议时,最后的 ACK 是由主动关闭端发出的,如果这个最终的 ACK 丢失,服务器将重发最终的 FIN,因此客户端必须维护状态信息允许它重发最终的 ACK。如果不维持这个状态信息,那么客户端将响应 RST 分节,服务器将此分节解释成一个错误(在 Java 中会抛出connection reset SocketException)。因而,要实现 TCP 全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状态信息进入 TIME_WAIT 状态。

  2. 允许老的重复分节在网络中消逝TCP 分节可能由于路由器异常而 “迷途”,在迷途期间,TCP 发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个原来的迷途分节就称为 lost duplicate。在关闭一个 TCP 连接后,马上又重新建立起一个相同的 IP 地址和端口之间的 TCP 连接,后一个连接被称为前一个连接的化身 (incarnation),那么有可能出现这种情况,前一个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。为了避免这个情 况,TCP 不允许处于 TIME_WAIT 状态的连接启动一个新的化身,因为 TIME_WAIT 状态持续 2MSL,就可以保证当成功建立一个 TCP 连接的时 候,来自连接先前化身的重复分组已经在网络中消逝。

    防止 lost duplicate 对后续新建正常链接的传输造成破坏,lost duplicate 在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,导致一个 packet 在路由器 A,B,C 之间做类似死循环的跳转。IP 头部有个 TTL ,限制了一个包在网络中的最大跳数,因此这个包有两种命运,要么最后 TTL变为 0,在网络中消失;要么 TTL 在变为 0 之前路由器路径收敛,它凭借剩余的 TTL 跳数终于到达目的地。但非常可惜的是 TCP 通过超时重传机制在早些时候发送了一个跟它一模一样的包,并先于它达到了目的地,因此它的命运也就注定被 TCP 协议栈抛弃。另外一个概念叫做 incarnation connection,指跟上次的 socket pair 一摸一样的新连接,叫做 incarnation of previous connection 。lost duplicate 加上 incarnation connection,则会对我们的传输造成致命的错误。大家都知道 TCP 是流式的,所有包到达的顺序是不一致的,依靠序列号由 TCP 协议栈做顺序的拼接;假设一个 incarnation connection 这时收到的 seq=1000, 来了一个 lost duplicate 为 seq=1000, len=1000, 则 TCP 认为这个 lost duplicate 合法,并存放入了 receive buffer,导致传输出现错误。通过一个 2MSL TIME_WAIT 状态,确保所有的 lost duplicate 都会消失掉,避免对新连接造成错误。

该状态为什么设计在主动关闭这一方:

  1. 发最后 ack 的是主动关闭一方;
  2. 只要有一方保持 TIME_WAIT 状态,就能起到避免 incarnation connection 在 2MSL 内的重新建立,不需要两方都有。

对服务器的影响

当某个连接的一端处于 TIME_WAIT 状态时,该连接将不能再被使用。事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。某个端口处于 TIME_WAIT 状态(其实应该是这个连接)时,这意味着这个 TCP 连接并没有断开(完全断开),那么,如果你 bind 这个端口,就会失败。对于服务器而言,如果服务器突然 crash 掉了,那么它将无法在 2MSL 内重新启动,因为 bind 会失败。解决这个问题的一个方法就是设置 socket 的 SO_REUSEADDR 选项,这个选项意味着你可以重用一个地址。

如何正确对待 2MSL TIME_WAIT

RFC 要求 socket pair 在处于 TIME_WAIT 时,不能再起一个 incarnation connection。但绝大部分 TCP 实现,强加了更为严格的限制。在 2MSL 等待期间,socket 中使用的本地端口在默认情况下不能再被使用。若 A 10.234.5.5:1234B 10.55.55.60:6666 建立了连接,A 主动关闭,那么在 A 端只要 port 为 1234,无论对方的 port 和 ip 是什么,都不允许再起服务。显而易见这是比 RFC 更为严格的限制,RFC 仅仅是要求 socket pair 不一致,而实现当中只要这个 port 处于 TIME_WAIT,就不允许起连接。这个限制对主动打开方来说是无所谓的,因为一般用的是临时端口;但对于被动打开方,一般是 server,就悲剧了,因为 server 一般是熟知端口。比如 http,一般端口是 80,不可能允许这个服务在 2MSL 内不能起来。解决方案是给服务器的 socket 设置 SO_REUSEADDR 选项,这样的话就算熟知端口处于 TIME_WAIT 状态,在这个端口上依旧可以将服务启动。当然,虽然有了 SO_REUSEADDR 选项,但 sockt pair 这个限制依旧存在。比如上面的例子,A 通过 SO_REUSEADDR 选项依旧在 1234 端口上起了监听,但这时我们若是从 B 通过 6666 端口去连它,TCP 协议会告诉我们连接失败,原因为 Address already in use。

2.2 UDP协议

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

UDP 只在 IP 的数据报服务之上增加了很少一点的功能,即端口的功能和差错检测的功能,虽然 UDP 用户数据报只能提供不可靠的交付,但 UDP 在某些方面有其特殊的优点。

e92d7f98c0a018ec353c6a2446a93fe8.png
UDP首部

UDP 数据报文分为两个部分:首部和数据部分。

169564cdb53111c66a9e01cc84c07ded.png
UDP首部

UDP 首部字段很简单,由 4 个字段组成,每个字段的长度都是两个字节,共 8 字节。

  • 源端口 原端口号,在需要对方回信时选用,不需要时可全 0
  • 目的端口 目的端口号,这在终点交付报文时必须使用,不然数据交给谁呢?
  • 长度 UDP 的长度,最小值为 8 字节,仅有首部
  • 检验和 检测用户数据报在传输过程是否有错,有错就丢弃。

在传输的过程中,如果接收方 UDP 发现收到的报文中的目的端口不存在,会直接丢弃,然后由网际控制报文协议 ICMP 给发送方发送“端口不可达”差错报文。

伪首部

计算校验和时,需要在 UDP 之前增加 12 个字节的伪首部。这种首部并不是用户数据报的真正首部。伪首部并不在网络中传输,只是在计算检验和,临时添加在 UDP 用户数据报前,得到一个临时的用户数据报。

UDP 的校验和是把首部和数据部分一起校验,发送方计算校验和的一般步骤:

  1. 将首部的校验和字段填充为 0(零);
  2. 把伪首部和用户数据报 UDP 看出 16 位的字符串连接起来;
  3. 如果数据部分不是偶数字节,则填充一个全零字节(该字节不发送到网络层);
  4. 按二进制反码计算出这些 16 位字的和;
  5. 然后将和写入校验和字段,就可以发送到网络层了。

接收方收到用户数据报后,连同伪首部一起,按二进制反码求这些 16 位字的和,无差错结果是应全为 1.否则出错,直接丢弃该报文。

d03ecfe8daf552f628f2eb35e684009e.png

计算 UDP 校验和的例子

UDP 的主要特点:

  • UDP 是无连接的,即发送数据之前不需要建立连接。
  • UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
  • UDP 是面向报文的。UDP 没有拥塞控制,很适合多媒体通信的要求。
  • UDP 支持一对一、一对多、多对一和多对多的交互通信。
  • UDP 的首部开销小,只有 8 个字节。

面向报文的 UDP:

  • 发送方 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。
  • 应用层交给 UDP 多长的报文,UDP 就照样发送,即一次发送一个报文。
  • 接收方 UDP 对 IP 层交上来的 UDP 用户数据报,在去除首部后就原封不动地交付上层的应用进程,一次交付一个完整的报文。

2.3 TCP协议如何保证传输的可靠性

18a74a2fe25ce1b6dbf6a2ff9690e1f7.png

既然要解决可靠性,那么什么是可靠性,以及什么因素导致不可靠是首要回答的问题。

对于读取 TCP 数据流而言,能保证数据的读取是无损坏,不丢失,有序这三点就算解决了可靠性问题。

导致不可靠的因素有很多:

干扰

数据在物理线路上是以电子的方式传输,网络传输的不可靠因素会有很多,网络波动,线路抖动,硬件故障,电压波动,都会导致数据的某些 bit 位发生变化(0 成了 1 , 1 成了 0)。

乱序

发送方按照顺序接连发送两个数据包,但是可能因为第一个数据包在网络寻址的过程中突然遇到网络不稳定的问题耽误了一些时间,导致第二个数据包反而先到。这就需要协议去做区分谁是第一个包谁是第二个包。

丢包

丢包在网络传输中应该是很常见的现象。网络不稳定导数据包丢失,接收端收到的数据相当于丢失了部分数据。

冗余

遇到网络风暴或者延迟的时候,发送方发送的数据包迟迟没有收到接收方的返回确认,误以为接收方没有收到,把同样的包重新发送一次,网络风暴结束,这个数据包重新找到了地址又送达了。但是对于接收当来说,同一个包收到了两次,所以需要提供区分包是否重复接收的能力。

针对上面这些问题,TCP 约定了一些方案来解决传输可靠性问题,TCP 协议保证数据传输可靠性的方式主要有:

  1. 校验和;
  2. 序列号;
  3. 确认应答;
  4. 超时重传;
  5. 连接管理;
  6. 流量控制;
  7. 拥塞控制。

校验和

校验和是一个 16bit 长的字段,发送方在计算 checksum 时会先将报文中的 Checksum置零,然后基于整个报文(头部 + 数据部分)计算出 checksum。

d92653ce1ddd5ed944362b2f1910a180.png
TCP首部

校验和在 TCP 报文段中的位置位于上图的蓝色部分。

计算方式:将发送的数据段都当做一个 16 位的整数,将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。发送方:在发送数据之前计算检验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。

在发送数据时,为了计算数据包的校验和。应该按如下步骤:

  1. 把校验和字段置为 0;
  2. 把需要校验的数据看成以 16 位为单位的数字组成,依次进行二进制反码求和;
  3. 把得到的结果存入校验和字段中。

在接收数据时,计算数据报的校验和相对简单,按如下步骤:

  1. 把首部看成以 16 位为单位的数字组成,依次进行二进制反码求和,包括校验和字段;
  2. 检查计算出的校验和的结果是否等于零(反码应为 16 个 1);
  3. 如果等于零,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。

所谓的二进制反码求和,即为先进行二进制求和,然后对和取反。

序列号

TCP 将每个字节的数据都进行了编号,这就是序列号,序列号的作用:

  1. 保证可靠性(当接收到的数据少了某个序号的数据时,能马上知道);

  2. 保证数据的按序到达;

  3. 提高效率,可实现多次发送,一次确认;

  4. 去除重复数据;

数据传输过程中的确认应答处理、重发控制以及重复控制等功能都可以通过序列号来实现。

确认应答机制(ACK)

TCP 通过确认应答机制实现可靠的数据传输。在 TCP 的首部中有一个标志位 - ACK,此标志位表示确认号是否有效。接收方对于按序到达的数据会进行确认,当标志位 ACK=1 时确认首部的确认字段有效。进行确认时,确认字段值表示这个值之前的数据都已经按序到达了。而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。

超时重传机制

第一种情况:数据包丢失。当数据发出后在一定的时间内未收到接收方的确认,发送方就会进行重传(通常是在发出报文段后设定一个特定的时间间隔,到点了还没有收到应答则进行重传)。

第二种情况:确认包丢失。当接收方收到重复数据(通过序列号进行识别)的时就将其丢弃,重新发送 ACK。

重传时间的确定:报文段发出到确认中间有一个报文段的往返时间 RTT,显然超时重传时间 RTO 会略大于这个 RTT,TCP 会根据网络情况动态的计算 RTT,即 RTO 是不断变化的。在 Linux 中,超时以 500ms 为单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍。其规律为:如果重发一次仍得不到应答,就等待 2*500ms 后再进行重传,如果仍然得不到应答就等待 4*500ms 后重传,依次类推,以指数形式递增,重传次数累计到一定次数后,TCP 认为网络或对端主机出现异常,就会强行关闭连接。

连接管理

连接管理即我们上面讲过的 3 次握手和四次挥手。

流量控制

所谓流量控制,就是让发送端不要发送的过快,让接收端能来得及接收

假设没有流量控制发送端发送的速度太快导致接收端的接收缓冲区很快填满,此时发送端如果继续发送数据接收端处理不过来,这时接收端就会把本来应该接收的数据丢弃,这会触发发送端的重发机制,从而导致网络流量的无端浪费。

所以 TCP 需要提供一种机制:让发送端根据接收端实际的接收能力控制发送的数据量。这就是所谓的流量控制。

TCP 利用 滑动窗口 实现流量控制的机制, 而滑动窗口大小是通过 TCP 首部的窗口大小字段来通知对方。

TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

fe4a4c6bc60ddc2f61adc9cc8800d4ba.png

上图中我们可以看到:

  1. 接收端 LastByteRead 指向了 TCP 缓冲区中读到的位置,NextByteExpected 指向的地方是收到的连续包的最后一个位置,LastByteRcved 指向的是收到的包的最后一个位置,我们可以看到中间有些数据还没有到达,所以有数据空白区。
  2. 发送端的 LastByteAcked 指向了被接收端 Ack 过的位置(表示成功发送确认),LastByteSent 表示发出去了,但还没有收到成功确认的 ack,LastByteWritten 指向的是上层应用正在写的地方。

所以接收端在给发送端回 ACK 中会汇报自己的 AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1,而发送方会根据这个窗口来控制发送数据的大小,以保证接收方可以处理。

下面我们来看一下发送方的滑动窗口示意图:

58572394e213377916fdba34a288ee5c.png

上图中分成了四个部分,分别是:(其中那个黑模型就是滑动窗口)

  1. 已收到 ack 确认的数据;
  2. 已发出但还没收到 ack 的;
  3. 在窗口中还没有发出的(接收方还有空间);
  4. 窗口以外的数据(接收方没空间)。

注意:

滑动窗口里是已发出但未收到 ACK、还未发出的数据

下面是个滑动后的示意图(收到 36 的 ack,并发出了 46-51 的字节)

e27386bef90804e78044f32af282cf35.png

TCP 是以段为单位进行数据包的发送的。

  1. 在建立 TCP 连接的同时,也可以确定发送数据包的单位,称之为“最大消息长度”:MSS。最理想的情况是,最大消息长度 MSS 正好是 IP 层中不被分片处理的最大数据长度。
  2. TCP 在传送大量数据的时候,是以“段=MSS 的大小”将数据进行分割发送的,进行重发时也是以 MSS 为单位的。
  3. 最大消息长度——MSS 是在三次握手的时候,在两端主机之间被计算得出的。两端主机在发出“建立 TCP 连接请求的 SYN 包”时,会在 SYN 包的 TCP 首部中写入 MSS 选项,告诉对方自己所能够适应的 MSS 的大小,然后发送端主机会在两者之间选择一个较小的 MSS 值投入使用。

零处理的窗口

当左右边界相同时称为零窗口,一般由于接收方无法及时处理数据,为了阻止发送方继续发送数据而产生这一情况。当接收方可以接收数据时,会给发送端一个纯 ACK,称为 窗口更新。然而这一数据报并没有确认机制,所以要确保能处理这一丢包。

如果发生这一丢包,双方都会继续等待,产生死锁。发送方为了解决这一情况,有一个持续计时器来触发 窗口探测 机制。接收方收到后必须返回一个带有窗口大小的 ACK。RFC 中推荐计时器时间为一个 RTO,之后以指数级增长。

Negle 算法

Negle 算法主要为了解决 TCP 的传输效率问题。Negle 算法规定:若要把发送的数据逐个字节缓存起来,则发送方需要把第一个字节发送出去,然后缓存后面的字节,在收到接收方第一个字节的确认,再将现有缓存中所有字节组成一个报文段发送出去,继续缓存后续数据。只有在收到前一个报文的确认之后发送后面的数据。这是为了减少所用带宽。当发送数据到达 TCP 发送窗口的一半或已达到报文段的最大长度也会立即发送报文段,而不是等待接收方确认。这是为了提高网络吞吐量。

糊涂窗口综合征

TCP 接收方的缓存已满,若上层一次从缓存中读取一个字节,这样接收方就可以继续接纳一个字节的窗口,然后向发送方发送确认,把窗口设为 1 个字节(上文所讲,IP 数据报为 41 字节长)。如果这样持续下去,那么网络效率非常低。

所以有效的解决方法,就是让接收方等待一定时间,让缓存空间能够接纳一个最长的报文段,或者等待接收缓存已有一半的空闲空间,再发出确认报文和通知当前窗口大小。

拥塞控制

拥塞:即对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。

拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。

几种拥塞控制方法:

  • 慢开始( slow-start )
  • 拥塞避免( congestion avoidance )
  • 快重传( fast retransmit )
  • 快恢复( fast recovery )

慢开始和拥塞避免

发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。

发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

慢开始算法

当主机开始发送数据时,如果大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。

慢开始指主机由小到大逐渐增大发送窗口,即增大拥塞窗口的数值。初始拥塞窗口 cwnd 设置为不超过 2 到 4 个最大报文段 MSS 的数值,具体规定:

  • MSS ≤ 1095 字节,cwnd = 4 x MSS字节,不得超过 4 个报文段。
  • MSS > 1095≤ 2190 字节,cwnd = 3 X MSS 字节,不得超过 3 个报文段。
  • MSS > 2190 字节,则 cwnd = 2 x MSS 字节,不得超过 2 个报文段。

上面的规定限制了初始拥塞窗口的大小。慢开始在每收到一个对新的报文段的确认后,cwnd 就可以增加最多一个 MSS 的数值。

                 拥塞窗口 cwnd 每次的增加量 = min(N, MSS)

N 是刚收到确认的报文段所确认的字节数,当 N < MSS 时,拥塞窗口每次的增加量要小于 MSS。下文举例说明慢开始的原理(实际上,TCP 的窗口是以字节大小为单位,下文为了方便以报文端形容):

9ea756a90c6499add078023e02d66aa9.png

从图可知,初始化窗口为 1,所有发送 M1 报文段,收到确认号之后,发送 M2-M3 两个报文段,因为拥塞窗口增大了,后面的轮次也是这样翻倍增加的。随着轮次的增多,那么发送到网络的数据就会急剧增加,容易出现拥塞,因此需要慢开始门限(ssthresh)状态变量。

  • 当 cwnd < ssthresh 时,使用慢开始算法;
  • 当 cwnd > ssthresh 时,使用拥塞避免算法;
  • 当 cwnd = ssthresh 时,慢开始或者拥塞避免算法。

拥塞避免算法: 让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,而不是加倍。这样拥塞窗口 cwnd 按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

当网络出现拥塞时:

  • 无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有按时收到确认),就要把慢开始门限 ssthresh 设置为出现拥塞时的发送方窗口值的一半(但不能小于 2)。
  • 然后把拥塞窗口 cwnd 重新设置为 1,执行慢开始算法。
  • 这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积压的分组处理完毕。

快重传与快恢复

在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。

快恢复算法 :

  • 当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢开始门限 ssthresh 减半。但接下去不执行慢开始算法。
  • 由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,即拥塞窗口 cwnd 现在不设置为 1,而是设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值