万字长文,了解TCP/IP协议模型

167 阅读42分钟

世界广阔, 分布在世界各地的电脑是如何进行通信的呢? 首先用排除法, 不是眼神交流, 也不是用口头通信..... 接下来就是我们的主角了.TCP/IP 五层模型.

那 TCP/IP 四层协议 和 五层协议的体系, OSI(开放系统互联基本参考模型)有什么区别呢?

想当年,在网络战国时期, 诸侯争霸, 不同的公司推出了自己的私有网络协议, 相互之间并不能兼容,用了哪家的网络,就相当于上了哪家的贼船. 都是贼船....
于是有好事者 OSI(国际标准化组织)振臂一呼,我大家制定一个通用的网络通信协议吧.该协议是国际标准. 而由于 OSI 太过负责和爱揽活,取众家之长, 制定了一堆详细的,复杂的,繁琐的,精确的网络通信协议. 导致 OSI 协议过于复杂, 各个厂商都没法按照标准实现.就像我们常使用 windows, 也没有实现 OSI 协议栈. 可见这个协议有多么复杂. 记着, 太细 不好.

那咋办呢? OSI 代表官网和权威, 但是民间也有能人哪. 有群高人在民间自己捣鼓了另一套通信协议, 叫做 TCP/IP,也是借鉴了 OSI.这个小家碧玉有魅力所在,也能完成网络通信,实现难度要比 OSI 小多了.

结果不言而喻, 盗版干掉了权威, 现在实际应用都是使用 TCP/IP 四层协议. OSI 只存在教科书中, 用于学习概念.

而五层协议体系呢? OSI 在教科书中用来学习概念,还被我等嫌弃, 太过于复杂, 所以就出现了五层协议体系,用于介绍网络原理.

OSI协议 TCP/IP四层协议 五层协议体系
第七层 应用层 应用层 应用层
第六层 表示层
第五层 会话层
第四层 传输层 传输层 传输层
第三层 网络层 网络层 网络层
第二层 数据链路层 网络接口层 数据链路层
第一层 物理层 物理层

物理层

简单的说,物理层确保原始的数据可在各种物理媒体上传输

物理层主要功能是为数据端设备提供传送数据通路、传输数据. 关心用什么信号表示 0 和 1, 为用户提供传输和接收比特流的能力. 并且尽可能的屏蔽掉物理设备和传输媒体的差异,使数据链路层感觉不到这些差异.

什么是比特流呢? 什么是字节流?

  1. bit (比特)
    作为信息技术的最基本传输单元,比特实在太小了,所以大家在生活中并不经常听到,那么 bit 到底是什么呢? 电脑是以二进制存储以及发送数据的.二进制的一位就叫 1bit. 也就是说 bit 的含义表示二进制中的 0 或者 1.

  2. Byte (字节)
    英文字符通常是一个字节,也就是 1B,中文通常是 2 个字节,也就是 2B.
    字节与比特的换算关系是 1 Byte = 8 bit

数字信号的编码(传输的方案):

在真实的网络中, 会使用电缆或者光纤等物理设备,进行传输信号. 他们通过网卡,将数据转成电信号或者光信号,然后发送这些信号.

下面介绍两种将数字转成信号的码规则

不归零码.

将二进制转成一组电信号, 高电平表示 0, 低电平表示 1, 反之亦然. 1.phisical.png

在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。 而这个间隔被称为码元长度。

就比如上图中, 将一组二进制转成电流, 用高电平 5v 表示 0, 用低电平 0v 表示 1. 每 8 个码元表示一个文字(英文).

如果重复发送"0"码,势必要连续不送电流或连续发送负电流,这样使某一位码元与其下一位码元之间没有间隙,不易区分识别,导致发送端和接收端信息同步困难

此编码的优缺点: 优点: 编/译码简单。
缺点: 收/发端同步困难。
用途: 计算机内部使用,像 cpu 和寄存器,主板,显卡等交互, (或低速数据通信)

曼彻斯特编码

曼彻斯特又叫相位编码,双相码,它包含自同步信息,码型中同时包括数据和时钟信息。

曼彻斯特编码有两种定义, 一种是'低-高'表示 1,'高-低'表示 0. 在 802.3 中定义,另一种是相反的,'高-低'表示 1,'低-高'表示 0

2.phisical.png 上图中一个高电平和一个低电平才能表示一个比特.

优点:

  • 内部自含时钟,收/发端同步容易
  • 抗干扰能力强

缺点:

  • 编/译复杂
  • 占用更多的宽带.
  • 用途: 802.3 局域网

数据链路层

数据链路层介于物理层和网络层之间.定义了单个链路如何传输数据

  • 将上层传的数据包(ip 包)包装成数据帧(以太帧).
  • 控制数据帧在物理层的传输,处理传输差错,调整发送速率等.
  • 在两个网络实体间提供数据链路的建立,维持和释放

MAC 地址

MAC 地址是无层次的,它能表示当前主机在某一个局域网下的具体地址, 但是却无法知道具体是哪个局域网. 而通过 ip 可以知道属于哪个局域网.

就好比送快递, ip 呢只能表示住在哪个哪个省哪个市,不知道具体的信息, 而 mac 地址是具体的小区加姓名. ip + mac 地址能够快速的定位到计算机的具体信息

  • mac 地址是内置在网卡中的,厂商生产网卡,会为每个网卡生产一个的 mac 地址.
  • 每个网卡都有一个唯一的 mac 地址,全世界唯一(其实每个局域网下的 mac 地址不同即可, 不会影响数据传输).
  • mac 地址由 48 位的二进制组成,通常分为 6 段, 用 16 进制表示

比如 mac 地址 00-0d-28-be-b6-42, 前六位表示厂商, 即 00-0d-28 表示厂商 CISCO(思科),后面 3 位厂家自行分配,保持唯一

交换机 集线器

集线器

集线器的英文称为“Hub”。“Hub”是“中心”的意思,它与网卡,网线等传输介质一样, 属于局域网中的基本设备,属于纯硬件网络底层设备.集线器没有"学习"和"记忆"的功能,因此它在发送数据时没有针对性的,采用广播的方式发送给所有连接的主机

交换机

交换机 意为"开关",是一种用于电(光)信号转发的网络设备,他可以为接入交换机的任意两个网络节点提供独享的电路信号. 当计算机连接到交换机时,会记录计算机的 ip 和 MAC 地址, 用于以后的消息传递

ARP 协议(地址解析协议)

ARP 协议是根据 IP 地址获取物理地址的一个 TCP/IP 协议

主机发送信息时将包含目标 IP 地址的 ARP 请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址

到底是如何取到对应的 mac 地址呢? 举个例子, A 向 B 发送消息,并且在同一个局域网内.

  • A 先查看自己的 ARP 缓存表(ARP 表保存着 IP 地址和 MAC 地址的对应关系,arp -a 查看缓存列表), 看是否包含 B IP-MAC 的映射信息,如果包含就把 ip 包组装成数据帧,直接发送给 B.
  • 如果不存在 B 的 MAC 地址, A 就会在整个网络中广播, 问 谁的 ip 是 XXXXXX,你的 MAC 地址多少.
  • B 发现 IP 和自己相同, 就会回复 A 自己的 MAC 的地址. 顺便把 A 的 IP 和 MAC 地址保存到 ARP 列表中(一段时间内会过期,清除).当然,C,D,E 也都收到了消息, 但是它们发现不是自己的 IP,就直接忽略了此消息
  • 主机 A 收到 B 的响应后, 将 B 的 MAC 地址加入 ARP 表,用于后续报文的转发.同时将数据包发送出去

如果局域网内,有交换机,就会 A 就把消息发给交换机, 交换机查看 ARP 列表是否有 B 的 MAC 地址, 如果有的话,直接返回给 A. 如果没有,交换机在局域网中广播,等待 B 的回复.然后在把 B 返回的消息告知 A.

此过程叫做 ARP 地址解析.

地址解析协议是建立在网络中各个主机互相信任的情况下, 局域网内的各个主机可以自主的发送 ARP 应答消息. 其他主机收到报文时,不会检测报文的真实性,就将其计入本机的 ARP 缓存. 由此攻击者就可以向某一主机发送伪 ARP 报文,使其发送的信息无法到达预期的主机或者错误的主机,构成 ARP 欺骗

计算机可以通过 arp -a 查看电脑的 ARP 缓存列表, arp -d 删除 ARP 缓存

ARP 报文格式如下:

arppackage.jpg

字段说明
帧类型ARP 为 0x0806,RARP 为 0x8035
硬件类型1 表示以太网地址
硬件地址长度以字节为单位,对于以太网上的 IP 地址的 ARP 请求或应答来说,他的值为 6
协议长度以字节为单位,对于以太网上的 IP 地址的 ARP 请求或应答来说,他的值为 4
操作类型(op)用来表示这个报文的类型,ARP 请求为 1,ARP 响应为 2,RARP 请求为 3,RARP 响应为 4
发送端以太网地址发送端的 mac 地址
发送端 IP 地址发送端的 IP 地址
目标以太网地址接收方的 MAC 地址(在广播的时候,由于不知道对方地址,会填写 00:00:00:00:00:00)
目标 IP 地址接收方的 IP 地址

RARP 反向地址转换协议

RARP 协议. 允许局域网的物理机器从网关服务器的 ARP 列表或者缓存请求自己的 IP 地址.网络管理员在局域网网关路由器里创建一个表以映射物理地址(MAC)和与其对应的 IP 地址.

RARP 以与 ARP 相反的方式工作。RARP 发出反向解析的物理地址(MAC 地址)的消息并希望返回其对应的 IP 地址.

  • 网络上每台设备都有一个独立的硬件地址,通常是由设备厂商分配的 MAC 地址.主机从网卡上读取 MAC 地址,然后在网络上发送一个 RARP 请求的广播数据包.
  • RARP 服务器收到了 RARP 请求数据包,为其分配 IP 地址,并将 RARP 回应发给主机.
  • 主机 收到 RARP 回应后, 就使用得到的 IP 地址进行通讯.

RARP 的报文和 ARP 报文格式类似, 主要差别在于帧类型代码为 0x8035(ARP 为 0x0806),操作码为 3 请求(ARP 为 1),4 应答(ARP 为 2)。 [1]

网络层

位于传输层和数据链路层之间,主要功能是把数据从主机经过若干个中间节点传送到目标主机.并向传输层提供最基础的数据传输服务,并提供路由和选址的工作.

选址

交换机是靠 MAC 来寻址的, 而因为 MAC 是无层次的, 所以靠 IP 地址来确认计算机的位置,这就是选址.

路由

能够在多条道路之间选择一条最短路径就是路由的工作

IP 协议

IP 协议 是 Internet Protocol(网际互连协议)的缩写, 是 TCP/IP 体系中的网络层协议。设置 IP 的目的是提高网络的扩展性,分割顶层网络应用和底层网络技术直接的耦合关系, 以利于两者的独立发展

MTU 和 MSS

MTU,(Maximum Transmission Unit,MTU),最大传输单元, 数据链路层传输的最大单元,1500,如果算上以太网帧,为 1518
MSS, (Maximum Segment Size),最大传输报文段,tcp 层的概念,最大值为 1460,不包括头部, 即 MSS + tcp 头部长度(20)+IP 头部长度(20) = MTU

IP 报文格式

ipheader.png

字段说明
版本(version)4 比特, 用来表名 IP 协议实现的版本,当前一般是 IP4,即 0100
首部长度(Internet Header Length)4 比特. 因为 ip 报文首部长度不固定(Option 可选项字段不固定),此字段表示 ip 数据包的首部具体有多大,单位是 4 字节(4Byte), 即如果是 0011 即表示 3 * 4 = 12 个字节, 即当前的 ip 数据包的头部长度为 12 字节. 此值最小为 0101(大多数情况都是 0101) 即是 5,首部长度 20 字节,最大为 1111,即是 15,最大值为 15 * 4 = 60 字节. 即 ip 数据包头部固定长度是 20 字节, 可选性 Option 字段的大小为 0-40 字节.
优先级与服务类型(Differentiated Services)占 8 比特.其中前三位未为优先权子字段(现已经忽略).第 8 比特保留未使用. 第 4 至第 7 布特分别代表延迟,吞吐量,可靠性和花费.当他们取值为 1 时,分表代表要求最小延时,最大吞吐量,最高可靠性和最小费用.这 4 比特的服务类型中只能置其中 1 比特为 1.可以全部为 0.服务类型字段声明了数据包被网络系统传输时可以怎样被处理.telnet 协议可能要求有最小延迟,FTP 协议可能要求有最大吞吐量,SNMP 可能要求有最高靠靠性,NNTP 可能要求有最小费用.而 ICMP 协议可能无特殊要求(4 比特全为 0)
总长度(Total Length)占 16 比特,指整个数据包的长度(单位字节),不包括头部.最大长度为 65545,通常来说,这个值最大为 MTU(1500)-20 = 1480, 20 为首部长度.MTU 指最大传输单元,默认为 1500。在抓用 wireshark 抓包时,通常能够看到 Len = 1460, 这个值为 tcp 发送的数据大小,1460(MSS,TCP 最大报文长度)+20(tcp 报文首部长度)+20(ip 首首部长度) = 1500(MTU).MSS+20 就是当前的总长度
标识符(Identification)占 16 比特。用来唯一的标识主机发送的每一份数据。通常每发一次报文,它的值就会加 1
标志 (Flags)分为 3 个字段,依次为保留位、不分片位和更多片位。 保留位一般为 0(未用到)。不分片位表示该数据包是否被分片,如果被置为 1,表示不能对数据包进行分片,如果要对其进行分片,置为 0。更多片位:除了最后一个分片,其他每组数据包的片都要将该位置置为 0
段偏移量(Fragment Offset)占 13 比特.如果一份数据报要求分片的话,此字段知指明该段偏移距离原始数据的位置
TTL(time to Live 生存时间)该字段用于表示 ip 数据包的生命周期,可以防止一个数据包在网络中无限循环下去. TTL 的意思是一个数据包在被丢弃之前在网络中最大的周转时间.数据包每经过一个路由器都会检查该字段中的值.并减去 1, 当 TTL 的值为 0 时,则丢弃该包. 最大值为 256, 通常默认值为 64
协议号(protocol)占 8 比特. 指明 ip 层上一层(传输层)用的协议类型, ICMP(1), IGMP(2), TCP(6), UDP(7)
首部校验和(Header checksum)检验和是 16 位的错误检查字段.目的主机和网络中的每个网关都要重新计算报头的校验和, 一样表示没动过. 计算方法是:对头部中每个 16 比特进行二进制反码求和
源 IP 地址(source)表示数据包的源地址,即发送数据包的设备网络地址
目标 IP 地址该字段用于表示数据包的目标的地址,指的是接收节点的网络地址

ip 地址

ip 地址是一个网络编码,用来确定网络中的一个节点,由 32 位二进制组成,通常分为 4 段,每 8 个二进制为一段.为了方便阅读,通常将每段专为 10 进制来显示,比如

1921685139
11000000101010000000010110001011

ip 地址分为 网络 ID 和主机 ID,同一网络下,是可以直接通信的. 但是哪些是网络 ID,哪些是主机 ID,并没有规定.但可以通过子网掩码来区别网络和主机 绝大多数 IP 地址属于以下几类

  • A 类地址: IP 地址的前 8 位代表网络 ID,后 24 位代表主机 ID;
  • B 类地址: IP 地址的前 16 位表示网络 ID,后 16 位表示主机 ID;
  • C 类地址: IP 地址的前 24 位表示网络 ID,后 8 位表示主机 ID.

很明显的看出 A 类地址提供的网络 ID 较少,但是每个网络可以拥有非常多的主机.
到底怎么才能看出一个 IP 地址属于哪个分类呢?

10.ipclass.png

还有一些情况,需要从这些 IP 地址分离出来.

  • IP 地址中全为 1,即 255.255.255.255,它称之为限制广播地址,如果将其作为数据包的目标地址,可以理解为发送到所有网络的主机.
  • IP 地址中全为 0, 即 0.0.0.0,他表示启动时的 IP 地址,其含义就是尚未分配地址.
  • 127 是用来进行本机测试的,除了 127.255.255.255, 其他的 127 开头的地址都表示本机

共有 IP 和私有 IP

  • A 类私有地址: 10.0.0.0~10.255.255.255
  • B 类私有地址: 172.16.0.0~172.31.255.255
  • C 类私有 IP: 192.168.0.0~192.168.255.255

区别:
公有地址由 Inter NIC(Internet Network Information Center 因特网信息中心)负责。这些 IP 地址分配给注册并向 Inter NIC 提出申请的组织机构。通过它直接访问因特网
私有地址: 我们企业或家庭内部组建局域网用的 IP,一般都会用私有 IP。无法直接访问因特网,可以使用“NAT 技术”将私网 IP 转成公网 IP 才能正常的上网

子网掩码

子网掩码又叫网络遮罩,唯一的作用就是将某个 IP 地址划分成网络地址和主机地址两部分.它不能单独存在,必须和 IP 一起使用. 而网络地址 子网掩码也是一个 32 个二进制表示, 用 1 表示 IP 地址的网络地址,0 表示 IP 的主机地址.

IP 地址和子网掩码做逻辑与运算得到网络地址

  • 0 和任何数相与都是 0
  • 1 和任何数相与都等于任何数本身

ABC 三类地址都有自己默认的子网掩码
A 类地址:255.0.0.0(11111111 000000000 00000000 00000000),即 IP 地址的前 8 位是网络地址
B 类地址:255.255.0.0(11111111 11111111 00000000 00000000),即 IP 地址的前 16 位是网络地址
C 类地址:255.255.255.0(11111111 11111111 11111111 00000000),即 IP 地址的前 24 位是网络地址.(子网掩码 1 的对应 IP 地址的位置对应的就是网络地址)

正常来说子网掩码的 1 和 0 都是连续的,1 对应的 IP 位置就是 ip 地址的网络地址
当然,子网掩码还存在不连续的情况, 这种设置的方法是 RFC 不推荐的,但能使用. 和 IP 做逻辑与运算 得到网络地址

例如 IP 192.168.5.139(11000000 10101000 00000101 10001011), 子网掩码 255.255.255.103 (11111111 11111111 111111111 01100111); 将 IP 与 掩码进行逻辑与运算得到:11000000 10101000 00000101 00000011 即属于网段 192.168.5.3, 网段看起来怪怪的

为了简写子网掩码, 有时候会看到这样的 IP,192.168.5.139/24, 它表示 IP 地址为 192.168.5.139, 子网掩码前 24 位都是 1, 即 255.255.255.0,对应的网络地址为 192.168.5.0,它属于网段 192.168.5.0

传输层

传输层主要负责向两个主机中进程之间的通信提供服务.主要通过流量控制,分段/重组和差错控制来保证数据传输的可靠性.

传输层最常用的协议是 TCP 和 UDP 协议.

TCP

TCP(传输控制协议,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

  • 将数据进行分段打包传输。
  • 将每个包编号控制顺序
  • 运输中丢失,重发和丢弃处理
  • 流量控制避免堵塞

tcp 报文格式如下

TCPmessage.png

字段说明
16 位源端口号(Source Port)占 16 位,发送方端口,即端口最大为 2^16 -1 = 65535 个
16 位目的口号(Destination Port)占 16 位,接收方的端口,即端口最大为 2^16 -1 = 65535 个
32 位序列号(Sequence Number)数据包的标识,在接收方收到后,会对包按照这个序列号的顺序进行组装. 比如当前序列号为 s, 发送数据长度为 l,则下一次发送时序列号为 s+l; 初始化时随机取一个值(wireshark 抓包时看到的为 0,是因为它给默认替换成 0 了), 当序列号大于 2^32-1 时,会重新从 0 开始计算
32 位确定号(Acknowledgment Number,ack)等于他上次接收消息序列号。发送端发送完数据 seq=x,len(发送数据长度)=y,接收方要记住发送端发送了多少数据,并回复发送端 ack=seq+y,确认数据已收到。在控制位里有 ACK(Acknowledgment)字段,ACK 和 ack 表示的意思不一样,ACK 为 1 时,表示确认号(ack)可用,是正确的,ACK 未设置,确认号是错误的
控制位URG(Urgent 急迫位):为 1 时表示高优先级数据包,紧急字段(Urgent point)有效.
ACK(Acknowledge): 为 1 时表示确认号(ack)有效.
PSH(push 急迫位): 为 1 时表示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满(例如接口数据传输完毕后)
RST:(reset 重置位) 表示断开连接,要重新建立连接
SYN:表示建立连接,只在三次握手的时候置为 1
FIN: 断开连接,在四次挥手时用到
16 位窗口大小(Window)表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小,用于流量控制,options 选项字段 kind=3,可以修改 16 位窗口值。
16 位校验和(Checksum)用来做差错控制,TCP 校验和的计算包括 TCP 首部,数据和其他填充字节,在发送端计算校验和,接收端也计算一次校验和,判断两次校验和是否一致.如果一致说明数据是正确的,否则认为数据被破坏,接收端将该数据丢弃
16 位紧急指针(Urgent Pointer)在控制位 URG 为 1 时生效,表示紧急数据的末尾 在 TCP 数据部分中的位置.通常在暂停通信时使用(比如 ctrl+c 停止)

三次握手与四次挥手

三次握手就是建立 TCP 连接时,客户端和服务端总共发了三个包. 进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常,指定自己的初始序列号,为后面进行可靠性传送做准备. 实质上就是连接服务器的指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号.交换 TCP 窗口信息.

三次握手

在搞清三次握手前,先搞清几个 tcp 字段.

  • Seq(Sequence number) 序列号,表示发送数据的序号. seq = ack(上次接到消息的序列号)
  • ack(Acknowledgment number) 确认号, 发送端发送消息后, 接收端要告诉发送端消息已收到,下次通信时,要把 ack 带过去. 即 ack = seq(上次接收消息的序列号)+len(数据长度)
  • ACK(Acknowledgment), 在 tcp 的控制位里, 用来表示 ack 是否有效的. 除了第一次握手不会设置 ACK 外,正常情况下 ACK 一直会被设置成 1
  • SYN 表示我想和你建立连接,可不可以. 握手的过程中为 1, 传输数据的过程中不设置

seq 和 ack 不会计算?, 教你一个快速理解的方法.
发送方和接收方都会有 ack 和 Seq, ack 表示对方总共发送多少数据, Seq 表示自己总共发送了多少数据. len 表示当前发送的数据大小.

则当要发送消息时,ack 和 Seq 分别是多少呢?

  • Seq = ack(上一次接到消息的 ack,就是自己发送的所有数据) + len(上一次接收到消息数据长度)
  • ack = Seq(上一次接到消息的序列号)

思考中 ~~~~ 思考中~~~~~ 思考中 ~~~~~~
思考中 ~~~~ 思考中~~~~~ 思考中 ~~~~~~
思考中 ~~~~ 思考中~~~~~ 思考中 ~~~~~~

tcphandle.jpg

  • 第一次握手: 客户端向服务端发送 SYN 报文,并指明客户端的初始序列化号. 将 SYN 置为 1, 序列号 seq 置为随机数 J. 此时的话,不能传输数据,但会消耗一个序列号
  • 第二次握手: 服务端回复一个 SYN 报文, 并且也指明自己的序列号,并将客户端的序列号 J+1 作为 ack 的值.表明自己收到了客户端的报文 在报文段中. SYN=1;ACK=1, 确认号 ack=J+1; 初始化序列号 seq=k;
  • 第三次握手: 客户端收到 SYN 报文后, 会发送一个 ACK 报文. 同样是把服务的序列号 k+1 作为 ack, 序列号等于 服务端的 ack=J+1. 确认报文字段 ACK=1;确认号 ack = k+1; 序列号 seq=J+1; ACK 报文可以携带数据.

经典问题:
为什么要三次握手,两次不行吗?

假如去掉第三次握手,客户端向服务端发送报文 A, 由于网络拥堵的原因, 报文 A 未及时送到. 所以客户端又发了一个报文 B 建立连接. 服务端同意连接后, 就建立了连接了. 传送了数据.然后释放连接.
如果此时 A 报文到达了服务端, 服务端以为客户端又要建立新的连接,于是向客户端发送确认报文,并建立了连接.等待客户端的回复
但此时客户端并没有理睬服务端的确认.也不会向服务端发送数据. 服务端资源就这样白白浪费了

为什么初始序列号 ISN(inital sequence number),是一个随机数 这个问题在下面的滑动窗口的接收窗口解释。 三次握手可以携带数据吗
第三次握手的时候理论上是可以携带数据的. 因为第三次握手瞬间, 服务端就处于 ESTABLISHED(已连接)的状态,不会影响后续的接收数据的处理.

第一次不可以. 假如第一次可以携带数据的话, 如果有人恶意携带大量数据, 疯狂向服务端发送 SYN 报文, 服务端会消耗大量资源去处理 SYN 报文中的数据 服务器更容易遭到入侵.

四次挥手

四次挥手,即终止 TCP 连接,释放双方的资源. 终止连接时,双方总共需要发送四个包 TCPwave.jpg

  • 第一次挥手: 客户端发送一个 FIN 报文,并且胡指定一个序列号,此客户端处于 FIN_WAIT1 状态
    在报文段中,FIN=1, 序列号 seq=u; 并停止发送数据.主动关闭 TCP 连接,进入 FIN_WAIT1 状态,等待服务端的确认.
  • 第二次挥手: 服务端接到 FIN 后,会发送 ACK 报文,并把客户端的序列号+1 作为报文的 ack,表明服务端已经收到报文了. 此时服务端属于 CLOSE_WAIT 状态. 在报文段中,会发送 FIN=1; ACK=1; seq=k; ack=u+1;服务端进入 CLOSE_WAIT(关闭等待)状态, 客户端收到报文后会进入 FIN_WAIT2 状态.等待服务端发出的连接释放报文段.
  • 第三次挥手: 如果服务端也想断开连接,和第一次挥手一样,发送 FIN 报文,并制定一个序列号,此时服务处于 LAST_ACK 状态.
    服务端没有要向客户端发出的数据,就是发送报文段: FIN=1; ACK=1; seq=w;ack=u+1;服务端最后进入 LAST_ACK(最后确认状态);
  • 第四次挥手: 客户端收到 FIN 后,和一样发送一个 ACK 报文作为应答, 并且把服务端的序列号+1 作为 ack 的值. 此时客户端属于 TIME_WAIT 状态.需要过一段时间以确保服务端收到自己的 ACK 报文后才会进入 CLOSE 状态. 服务端收到 ACK 报文后,就处于 CLOSE 状态.

经典问题.

挥手为什么需要四次

服务端在收到 FIN 关闭连接的报文后, 有可能会有数据还未传输完毕,所以不能立即发送 FIN,需要先 ACK 客户端,告诉他知道了.
等到数据传输完毕,然后在回复客户端 FIN 报文, 关闭连接. 两次通信不能合并.

四次挥手释放连接时,等待 2MSL 的意义何在

首先说说 MSL, MSL 是 Maximum Segment Lifetime, 最长报文段寿命, 他是任何报文在网络上存在的最大时间,超过这个时间报文将被丢弃. 在 linux 中这个时间默认设置为 30s
为什么要 2MSL 呢? 客户端向服务端发送最后一个 ACK 报文, 如果此 ACK 未到服务端,那客户端在 2MSL 内必定能收到服务新的 FIN-ACK 报文. 然后客户端重新发送 ACK,并在次等待 2MSL.

  1. 为了保证客户端最后一个 ACK 能够到达服务器.

因为最后一个 ACK 有可能会丢失. 从而导致处于 LAST_ACK 状态的服务器一直得不到释放. 服务器会一直重发 FIN 报文.无法正常关闭.

  1. 为了保证"已失效的连接报文"不在出现在本连接中.

客户端在发送完最后一个 ACK 后, 经过 2MSL,就可以使本次连接产生的报文全都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段

重传机制

tcp 实现可靠传输的方式之一,是通过序列号确认应答。
在 TCP 传输中,当发送端发送数据到接收主机时,接收端就要回复一个 ACK 确认应答消息,表示已收到。

TCP 常用的重传机制:

  • 超时重传
  • 快速重传
  • SACK

超时重传
重传机制的其中一个方式,就是在发送数据时,设计一个定时器,当超过指定的的时间后,没有收到对方的 ACK 确认应答报文,就会再次发送该数据,也就是我们常说的超时重传。

TCP 报文会在下面几种情况发生超时重传:

  • 数据包丢失
  • 确认应答包丢失

missMessage.png 以上两种情况都会发生重传。

特定时间到底是多少呢?

下面说两个概念: RTT(Round-Trip Time 往返时间) 是指一个数据从一端发送到另一端,并接到确认应答的时间,也就是包的往返时间 超时重传时间 RTO(Retransmission out),也就是图中的特定时间。超过这个时间,会立即重传。

  • 如果 RTO 大于 RTT 非常多, 那丢了老半天才重发,那就没有效率。
  • 如果 RTO 小于 RTT, 那有数据没丢失,也进行了重发,会增加网络的拥塞,导致更多的超时。超时导致等多的重复。

由此可以得出来 RTO 应该是略大于 RTT。 真实的 RTO 是经过计算和大量实践得出来的,如果有兴趣可以看这篇文章

快速重传 快速重传,不以时间为驱动,而是已数据为驱动, 根据 ACK 确认报文的序列号以及数量决定是否重传。

quickReTransition.jpg

  • 发送方发送, Seq1, 接收方回复 ACK2. (ack = seq+1);
  • 发送方发送 seq2,seq2 未送到,
  • 发送方发送 seq3, 接收方收到 seq3,但是未收到 Seq2, Seq 不匹配,接收方不敢乱回复,依旧回复 ACK2
  • 发送 seq4, 接收方收到 seq4 后,继续回复 ACK2
  • 发送 seq5, 接收方收到 seq5 后,继续回复 ACK2,
  • 发送方连续收到同样的 ACK2, 知道数据包 Seq2 丢了,定时器未过期之前,然后进行重发 Seq2
  • 接收方收到 seq2,并且保存了之前发的 seq3,seq4,seq5, 然后就回复 Ack6,回复 ACK6 表示之前的 seq 包已全部收到

这个就叫快速重传, 在定时器过期之前,重传丢失的报文。

重传的时候,是传送 seq2 呢,还是传送所有的呢? 目前发送方只知道了 Seq2 丢失了, 但是 Seq3,Seq4,Seq5 有没有丢失呢,发送方不知道,因为接收方只回了 ACK2,发送方就悲观的把所有的包重新传输

SACK(选择性确认 Selective Acknowledgment) 如果只重传丢失的包呢? 这就用到 SACK 机制了。 SACK 使接收方告诉发送方哪些报文丢失,哪些报文段重传了,哪些报文段已经提前收到信息等。接收方只需要重发对应丢失的报文即可。

SACK 机制需要发送/接收双方全都开启此方法即可。 握手阶段时, 双方在 tcp 首部的 options 选项里进行开启

TCPSACK.png

SACK 机制如下图。发送方收到了三次同样的 ACK 确认报文,同时会触发快速重发机制,通过 SACK 信息知道数据缺少 200-299 的数据段。即重发 200-299 的数据包即可

tcpSackTransmission.jpg

滑动窗口

引入窗口的原因
我们都知道 TCP 每发一组数据,都要进行确认应答,当上一个包的收到了应答,再发送下一个。 这种串行的传输方式有一个缺点: 数据包往返的时间越长,通信的效率越低。

为了解决这个问题,TCP 引入了窗口的概念,即使在往返时间较长的情况下,他也不会降低通信的效率。

那么有了窗口,就可以指定窗口的大小,窗口大小就是无需等待确认应答,而可以继续发送数据,直到窗口的最大值

窗口实际上是操作系统开辟的一个缓存空间,发送方在等到确认应答之前,必须在缓存空间中保留已发的数据。如果按期收到确认应答,此数据就可以从缓存中清除。

TCP 报文里有个 Windows 字段,也就是窗口大小,16 位,最大窗口位 65536。由于网络的快速发展, 65535 大小的窗口已经够现在的网络使用了, 所以在握手的时候,握手报文 options 选项中有一个字段,kind=3,表示窗口扩大系数,里面有个字段 shift count,取值范围是 0-14,,表示 windows 要扩大做少倍。 比如说 shift count 为 8, 即 2^8 * 65535。 总而言之,就是把 16 位的 Windows 字段变成 16(16+0) ~ 30(16+14)位。

tcp 滑动窗口分为接收窗口和发送窗口:

会话的双方都可以同时接收和发送数据。
会话时,双方各自维护一个发送窗口和接收窗口,各自的窗口大小取决于应用、系统、硬件的限制(tcp 的传输速率不能大于应用处理数据的速率)。各自的发送窗口则要取决于对方的接收窗口,要求相同

发送窗口

下图是操作系统开辟的缓存空间,根据处理的情况分为 4 个部分 tcpsliderWindows.png

  • #1 已发送并且接收到 ACK 确认的数据,1~31 字节
  • #2 已发送,但还未接到 ACK 确认的数据, 32 ~45 字节
  • #3 未发送,但数据在接收处理范围内的数据, 46-51 字节
  • #4 未发送,但总大小超过处理范围的数据, 52 字节以后

我们所谓发送窗口的大小(windows size)就是 #2 的数据 + #3 的数据。而#3 紫色部分的大小,就是当前可用的窗口大小。

如果把#3 的数据也都发送出去,则当前窗口可用大小为 0,不能继续传输数据了。

tcpEnableUse.jpg 如果 3236 字节的 ACK 确认应答后,如果发送窗口的大小没变化,则滑动窗口向有移动 5 个字节,因为有五个字节被应答确认,接下来 5256 字节又变成可用窗口,后续就可以发送 52~56 字节的数据了 tcpEnableUse2.jpg 如何表示具体的可用窗口大小呢? 滑动窗口方案用了三个指针来对数据进行分割。 tcpWindowPointer.jpg

  • SND:send,表示发送方
  • SND.WND: Send windows,窗口的大小,包括可用窗口和已发送未确认的数据,由接收方指定,每次通信时,接收方会告诉 SND.WND 大小。
  • SND.UNA: Send UnAcknowledgment,指向已发送但未确认的第一个字节的序列号,就是#2 的字一个字节序列号。
  • SND.NXT:Send Next, 指向未发送但是可以发送数据的第一个字节,也就是#3 的第一个字节。

可使用的窗口大小 = SND.WND - (SND.NTX - SND.UNA);

接收窗口
接收窗口比发送窗口相对简单,根据处理情况分为三个部分。

TcpRecWindow.jpg

  • RCV: Receive,接收方
  • RCV.WND: Receive Windows,表示接收窗口的大小。他会发送给发送方。
  • RCV.NXT: Receive Next, 表示未收到数据但可以接收数据的第一个字节序列号。也就是#3 的第一个字节序列号。

接收窗口大小和发送窗口大小是约等于的

滑动窗口大小不是固定的。 当接收应用程序读取数据非常快的话,这样接收窗口会很快空闲出来,那么新的接收窗口大小需要通过 TCP 报文 Windows 字段来告诉发送方,传输的过程是有延时的。所以接收窗口大小和发送窗口大小是约等于的关系。

ISN(inital sequence number, 初始化序列号) 随机的原因
一个 TCP 接收方收到的 TCP Segment(数据段),会校验 segment 的 sequence number(序列号),以及 acknowledge number(确认号)是否合法。其实就是判断 seq 的值 是否处于当前的接收窗口内,如果序列号不在接收窗口内,就会被丢弃,只有 seq 在当前窗口内,semgent 才会被接收解析。

假如说是 初始化 seq 是一个固定的数字 1,攻击者伪造报文并且随意的取 65535 之内的 seq,然后发送一个 RST(断开连接)报文,当前 seq 非常容易命中。 接收方验证当前报文,seq 合法,然后就断开了链接,非常容易受到攻击。
而如果 seq 是一个随机的 1~2^32 之间的数, 攻击者即使想攻击,也要尝试着 2^32/65536 = 65536 次,大大增加了攻击成本。 这就是 ISN 随机的原因

流量控制

流量控制就是让发送方的传输数据的速率不要太快,让接收方可以及时接收,避免丢包。

主要是利用 tcp 的滑动窗口,来实现流量控制。 看下图。 tcpstreamcon.jpg 建立连接后 接收窗口 RCV.WND 大小为 200,RCV.NXT 为 241,发送窗口 SND.WND = RCV.WND = 200, SND.NXT=SND.UNA=241,此时可用窗口为 SND.WND-(SND.NXT-SND.UNA) = 200
客户端的发送窗口和服务端的接收窗口都未标识,在此过程都未用到, Usable 表示可用窗口

  1. 客户端请求数据,lenth=140,seq num = 1;
  2. 服务端收到请求后,发送确认和 80 字节的数据。 发送窗口减小,Usable = 200 -80 = 120,SND.NXT = 241 + 80 = 321,SND.UNA =241;
  3. 客户端收到确认报文,处理完数据,并发送确认报文。此时接收窗口(数据处理完毕了), RCV.NXT = 241+80 = 321;
  4. 服务端又发送了 120 字节的数据, 此时 Usable = 120 -120 = 0, SND.NXT = 321+120 = 441, SND.UNA = 241;
  5. 客户端收到确认报文,并处理数据 RCV.NXT = 321 + 120 = 441;
  6. 服务端收到了 ③ 的确认报文 Ack=321,窗口向右移动 321(ACK 的 seq) - 241(服务端的 SND.UNA) = 80, SND.UNK = 321, SND.NXT = 441,Usable = 200-(441-321) = 80
  7. 服务端收到了确认报文 ⑤ 的确认报文 Ack=441,窗口向右移动 441-321=120,SND.UNK=441,SND.NXT=441; Usable = 200-(SND.NXT-SND.UNK) = 200
  8. 服务端发送 160 个字节,SND.UNK = 441, SND.NXT = 441+160 = 601 Usable = 40;
  9. 客户端接收到数据后,发送确认报文。
  10. 服务端收到确认报文 ACK=601, 窗口向右移动 601 - SND.UNK = 160; 然后 SND.UNK = 601, SND.NXT = 601; Usable = 200;

以上就是流量控制的过程.每次发送的数据不能超过窗口的大小。

如果在上述过程中,当发送方窗口为 0 时, 而两次确认 ACK 在网络中丢失了,服务端未收到,导致客户端一直在等待服务端发送数据,而服务端却一直在等待客户端的 ACK,碰到这种情况该怎么办? 这就是 TCP 死锁

为了解决这个问题, TCP 为每个链接设计一个定时器, 只要 TCP 链接一方收到对方窗口为 0 的通知,就启动持续定时

如果持续定时器超时,就会发送窗口探测报文,而对方在确认的这个探测报文,给出自己现在的窗口大小。

  • 如果接收窗口收到的窗口大小仍然为 0,那么收到报文一方会继续重新启动持续定时器。
  • 如果接受窗口不是 0, 那么死锁的局面就可以被打破了。

窗口探测次数一般为 3 此,每次大约 30-60 秒。三次过后如果窗口还是 0 的话, 有的 TCP 连接会发 RST 报文中断连接。

拥堵控制

拥塞控制和流量控制区别: 流量控制是控制接收端和发送端对数据处理速率的控制, 而拥堵控制是网络中传输的数据太多,导致网络堵塞

拥塞控制就是避免发送方的数据填满网络。

为了调节发送方的数据量,定义了拥塞窗口(congestion window, cwnd) 的概念

cwnd(拥塞窗口)是发送方维护的一个变量,它会根据网络的拥塞程度动态变化的

我们在前面提到过发送窗口(send window,swnd)和接收窗口(receive window,rwnd)是约等于的关系,那么由于拥塞窗口的加入,发送窗口 swnd = min(rwnd, cwnd),也就是拥塞窗口和接收窗口的最小值。

拥塞窗口的变化:

  • 只要网络中没出现拥堵,cwnd 就会变大,
  • 网络中出现了拥塞,cwnd 就开始减小。

怎么知道当前网络是否出现了拥塞呢
如果发送方未在规定时间内收到确认 ACK,就认为是发生了超时重传,就会认为是网络出现了拥塞

拥塞控制主要是四个算法:

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复
慢启动

tcp 在建立连接完成后,后弦是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送的数据量,如果一上来就发大量的数据,这不是给网络添堵吗?
慢启动的算法记住一个规则就行: 当发送方每收到一个 ACK,拥塞窗口 cwnd 就增大 1(单位是 MSS, 最大传输报文段,正常就是 1460); tcpSlowStart.jpg

  • 初始化时 cwnd=1,表示可以穿 1 个 MSS 大小的数据。
  • 当收到 ACK 确认顶大后,cwnd 加 1,于是一次可以发送两个数据包。
  • 当收到 2 个 ACK 应答后,cwnd 加 2, 于是一次可以比之前多发两个,总共 4 个数据包。

慢启动算法,发包的个数是指数型增长。

慢启动算法涨到什么时候呢?

有一个叫慢启动门限, sshtresh(slow start threshold)状态变量,正常默认值为 65535 字节(64k)。

  • 当 cwd < sshthresh 时,指数式增长,
  • 当 cwd >= sshthresh 时,会使用拥塞避免算法。
拥塞避免算法。

当拥塞窗口 cwnd 超过慢启动门限 ssthresh 就会将进入拥塞避免算法。

那么进入拥塞避免算法后,它的规则是: 每当收到一个 ACK 后,cwnd 增加 1/cwnd

拥塞避免算法就将原本慢启动算法的指数增长变为了线性增长,还是增长阶段,只是增长速度慢了一些。

如果一直增长后,网络就会慢慢进入拥塞的状况了,就会出现丢包的现象,这时就会对包进行重传。

当触发了重传机制,也就进入了拥塞发生算法

拥塞发生算法

当网络出现拥堵后,也就是发生数据包重传,重传机制主要有两种:

  • 超时重传
  • 快速重传

两种重传方式使用的拥塞发生算法是不同的。

当发生超时重传
这时候 ssthresh 和 cwnd 会发生变化,

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1

接着重新开始慢启动,慢启动会突然减少数据刘。 一旦发生超时重传,马上回到解放前。方式太激进,反应很强烈,造成网络卡顿。

当发生快速重传

当接收方发现丢了其中一个中间包的时候,发送三次前一个的 ACK 包,于是发送端就会快速重传,不必等待超时在重传。

TCP 认为这种情况不严重,因为大部分包没丢失,只丢失了一小部分。则 sshthresh 和 cwnd 变化如下。

  • cwnd = cwnd /2, 设置为原来的一半。
  • ssthresh = cwnd,
  • 进入快速恢复算法。
快速恢复算法。

快速重传和快速恢复算法一般同时使用,快速恢复算法认为:还能收到三个重复 ACK,证明网络也不那么糟糕,所以没必要像超时重传那么强烈。

发生快速重传后, 会将 cwnd 减半,并且 ssthresh = cwnd,然后会进入快速恢复算法:

  • 拥塞窗口 cwnd = sshthresh+3(3 的意思是确认有 3 个数据包被收到了,3 个重复的 ACK 包)
  • 重传丢失的数据包
  • 如果在收到重复的 ACK,那么 cwnd 增加 1
  • 如果收到新的数据的 ACK 后,把 cwnd 设置为第一步的 sshthresh,原因是该 ACK 确认了新的数据,说明发送的新数据都已经收到, 可以恢复到之前的状态,也即在此进入拥塞避免状态。不会像超时重发那那样,一夜回到解放前。

应用层

应用层位于 TCP/IP 模型的最顶层。主要是为用户提供传输服务,把各种各样的数据,如汉字、字符、图片等转成二进制,传递给传输层。

应用层常见的协议有 http,FTP 文件传输协议,SMTP(发送邮件) 和 POP3(接收邮件)等。

附录

不同层中对数据的称谓:

数据帧(Frame):在数据链路层中数据称为数据帧。
数据包(Packet): 在网络层中的数据称为数据包。
段(Segment): 在传输层的数据称为数据段。
消息(message):在应用层的数据称为消息。

参考文献

30 张图解: TCP 重传、滑动窗口、流量控制、拥塞控制发愁