TCP/IP 协议族
TCP/IP(Transmission Control Protocol/Internet Protocol)传输控制协议/因特网互联协议,而 TCP/IP 协议族指的是 TCP/IP 的四层模型下的相关协议:
TCP/IP 也详细说明了各层的数据转换,在定义标准时也将实现过程规范了:
上图中用户发送数据在应用层开始逐层传递,传输层和网络层是 OS 内核会帮我们处理数据的封装和解析,在每一层都会添加传输相关的头信息用于数据识别和校验,最终通过网卡数据链路往外推数据,经过路由转发到目标,再从数据链路往上层解析数据到上层。
在网络传输过程中每一层的数据都有对应的信息单位:
-
包:各层数据单位都可以说明为包
-
消息:应用层的信息单位
-
段:传输层的信息单位
-
片:网络层的信息单位
-
帧:专指数据链路层的信息单位
MAC、IP、端口及子网地址寻址问题
MAC
MAC 地址是网卡厂商用于标识网卡设备的编号,或者说是一个网络设备的相关标识。
它在数据链路层在数据帧包发送传输时会将 MAC 地址带到数据帧包。一台计算机一般会有多个网络设备,网卡、wifi 等都是网络设备,所以查看系统可能会有多个 MAC 地址出现:
网卡的主要功能有:
-
数据的封装与解封装
-
链路管理
-
数据编码与译码
每一个网卡(网络设备)都有一个被称为 MAC 地址的独一无二的 48 位串行号,所以一般情况下 MAC 地址是唯一的。
MAC 地址的长度为 48 位(6 个字节),通常表示为 12 个 16 进制数,如:00-16-EA-AE-3C-40 就是一个 MAC 地址,其中前 3 个字节 16 进制数 00-16-EA 代表网络硬件制造商的编号,它由 IEEE(电气与电子工程师协会)分配,而后 3 个字节 16 进制数 AE-3C-40 代表该制造商所制造的某个网络产品(如网卡)的序列号。
IP
IP 地址是一个 32 位的二进制数,通常被分割为 4 个 8 位二进制数(也就是 4 个字节),IP 是位于网络层,可以理解为 IP 是当前计算机使用的名称,主要用于路由寻址时使用。
IP 地址通常用 “点分十进制” 的形式(a.b.c.d),其中 a、b、c、d 都是 0-255 之间的十进制整数。例如 IP 地址 100.4.5.6,实际上是 32 位二进制数 01100100.00000100.00000101.00000110。
端口
端口分围虚拟端口和物理端口。
-
虚拟端口:指计算机内部或交换机路由器内的端口,不可见,是特指 TCP/IP 协议中的端口
-
物理端口:也称为接口,是可见端口,计算机背板的 RJ45 网口等
端口类型也分为周知端口(Well Known Ports)、注册端口(Registered Ports)和动态/私有端口(Dynamic Ports / Private Ports)。
其中周知端口范围从 0-1023,它们紧密绑定于一些特定的服务。例如 80 端口分配给 www 服务,21 端口分配给 FTP 服务,23 端口分配给 Telnet 服务等。
很多人把 pid 和端口混淆,pid 是内核对进程分配的一个编号,用于系统内核对进程的识别;端口是进程对外的名字,即外面的数据要推到进程,需要先找到对外的端口假设为 8888,然后再根据这个端口 8888 找到 pid。这才是端口的实际意义。
网络寻址过程
路由寻址过程:
-
路由器会存储当前子网下的所有 IP-MAC,路由器维护着一个映射表也称为路由表,计算机的网络设备连接上路由时就会注册到路由表
-
当计算机要发送数据包给另一台计算机时,数据包在链路当中会有 MAC 信息,路由在分发时可以让网络设备识别是否是自己的 MAC
-
路由的分发直接以广播形式,所有注册到路由表中的子网网络设备会接收到数据包,但是网卡会识别数据包是否是当前 MAC 地址所需的信息,如果不是则会丢弃数据包
后续找到对应 MAC 地址的网络设备后,数据再进行解析后要给到哪个进程,就会通过端口例如 8888 再找到 pid 然后将数据给到对应进程。
因为路由是以广播的方式分发给已经注册到路由表的网络设备,所以一些网络嗅探抓包工具的工作原理例如 WireShark、Charles 等其实就是在路由分发时如果不是对应的 MAC 地址,不会丢弃数据包而是收集。
小结
简单总结下 MAC 地址、IP、端口和网络寻址。
-
MAC 地址是分配给每个网络设备的唯一编号,一台计算机可能会有多个网络设备,例如网卡、无线 wifi、蓝牙、NFC 等都属于网络设备
-
IP 可以简单理解为是用于标识机器的名称,这会在路由寻址时结合 MAC 地址注册到路由表,因为路由只通过 MAC 地址是不能实现寻址
-
pid 和端口不要混淆,pid 是内核对进程分配的一个编号,用于系统内核对进程的识别;端口是进程对外的名字,即外面的数据要给数据到进程,需要先找到对外的端口假设为 8888,然后再根据这个 8888 找到 pid
-
网络寻址提到了路由寻址的过程,路由实际上没有寻址功能,而是将数据包通过广播的方式广播给每个注册到路由表的网络设备,数据包是对应 MAC 地址的网卡会接收数据,否则会丢弃数据
TCP 协议特性
TCP 协议是传输层协议,主要的作用就是做校验并保证数据完整性。TCP 协议是面向连接、可靠的、全双工的协议。
1、面向连接
面向连接需要实现端到端也就是一对一通信,相比 UDP 不需要面向连接的目的是为了一对多。
2、可靠性
TCP 可靠性是怎么保证的?
可以考虑一种场景:如果传递的数据过大比如有 1 GB,是否是一次性传输到位?
我们都清楚不是一次性就传输完,所以在处理过程中会对数据进行拆分处理,数据的拆分是在网络层进行分片操作,但是数据拆分后会有各种复杂网络状况出现,可能对方网络带宽拉满了,或者传输过程中丢包了。
所以 TCP 为了保证可靠性采用了两种方案:超时重传(RTO)和往返时延(RTT)。
-
超时重传:如果超过对应时间对数据进行重传,但是超时时间不能固定,超时时间不好界定
-
往返时延:对超时重传方案的一种补充,一个数据包从当前端发送到对端,需要收到对端应答,这中间所损耗的时间会进行记录,每收到一个应答包则更新时间
3、全双工
全双工的意思是发送方和接收方都能做好数据接收的准备工作。它是 TCP 建立连接的前提,达成全双工也是三次握手的核心目的。
TCP 三次握手
在说明 TCP 三次握手前需要简单罗列下讲解时需要了解的报文含义:
-
SYN:可以将它理解为是一个标志位,表示是否已经建立连接。SYN=0 表示没有连接,SYN=1 表示已经我这边已经准备好 fd 做好了数据接收的准备
-
FIN:表示没有数据要发送
-
seq:如果是客户端发给服务端,seq 是用来给服务端识别是哪个客户端发送的报文,反之就是给客户端识别服务端。一般是一个随机的数据,并且 在网络处理中 seq 是一个累加的过程,比如客户端发送数据给服务端,seq 此时就会变动,seq 也能作为是否是有效连接(请求连接没有过期或超时)判断的依据
-
ACK:应答报文,表示已经知道了对方刚才发的报文
-
ack:告诉对方我确实知道了是哪个客户端/服务端告知的我,所以 ack 是在对方的 seq 基础上累加数值
下图是 TCP 三次握手图解:
-
第一次握手:客户端主动准备好了 fd 后,发送 SYN=1 给服务端,表示客户端已经做好了数据接收的准备,并且报文 seq=2222 让服务端在后续和客户端连接时能识别是指定的客户端
-
第二次握手:服务端在接受到客户端发的 SYN=1,seq=2222 报文后,也开始准备 fd,然后发送 SYN=1 表示也做好了数据接收准备,报文 seq=6666 让客户端在后续和服务端连接时能识别指定服务端;ACK=1 表示服务端已经知道客户端刚才发的 SYN=1,seq=2222,知道客户端有 fd 数据接收能力了,同时为了验证应答,服务端把客户端的 seq=2222 提出来 +1 作为 ack=2223 一起返回给客户端做验证
-
第三次握手:客户端在收到服务端的应答后,还需要再发送一次 ACK=1,ack=6667 告知服务端已经知道你的应答并且把你的 seq=6666 提出来 +1 作为 ack=6667 一起返回让你做校验
三次握手的核心目的在于建立连接,而建立连接的前提是要求全双工,全双工的意思是发送方和接收方都能做好数据接收的准备工作。
在了解了 TCP 三次握手的流程后,就可以回答 TCP 常见的问题。
为什么要三次握手,而不是两次?
[1] 为了防止无效连接
三次握手时,在网络拥堵等情况下,第一次握手的 SYN 包迟迟没能发送到服务端,那么客户端会连续发送 SYN 建立连接的报文,就可能会出现旧的 SYN 报文比最新的 SYN 报文早到达服务端。
服务端收到 SYN 报文后会应答一个 SYN+ACK 报文给客户端;假设是两次握手,服务端就不能判断是否是旧的还是最新的 SYN 报文请求的建立连接,客户端有请求连接的报文都响应,但客户端并不需要,导致错误。如下图:
所以如果是三次握手,客户端收到服务端的响应报文后,就可以根据自身上下文判断这是一个历史连接(seq 序列号过期或超时),客户端就会发送 RST 报文给服务端,中止这一次连接。
[2] 避免资源浪费
当客户端的 SYN 请求连接在网络中阻塞,客户端没有服务端收到 ACK 报文,就会重新发送 SYN。如果只有两次握手,服务端不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以 每收到一个 SYN 就只能先主动建立一个连接创建 fd,就会出现建立多个冗余无效的连接,造成资源浪费。
TCP 四次挥手
-
第一次挥手:客户端先将 fd close,但此时并没有释放(因为此时服务端可能还在发数据给客户端),而是发送一个报文给服务端 FIN=1, seq=98765 告知已经没有数据发送给你了
-
第二次挥手:服务端接收到客户端的消息后,服务端会应答 ACK=1, ack=98766 给客户端,表示已经收到了你的 FIN 报文,把你的 seq 提出来 +1 也一同发给你做校验
-
第三次挥手:等到服务端把发给客户端的数据都发完了,服务端主动再发一次消息 FIN=1, seq=12345,表示我这边也没东西给你了,客户端此时处于 TIME_WAITING 状态
-
第四次挥手:等客户端响应发回 ACK=1,seq=12346,服务端就处于 CLOSED 状态,此时服务端释放 fd 资源,客户端也会在 2 MSL 时间后再释放 fd 资源
按正常来讲,客户端没有数据发给服务端,服务端也没有数据发给客户端,客户端的 fd 是可以销毁释放的,但实际上客户端的 fd 是做了延迟的释放,官方定义为 2 MSL。
MSL 报文网络最长存活时间,官方定义为 2 分钟,但是一般 OS 实现定义为 30s,TIME_WAITING 一般时间为 1-4 分钟
1、客户端为什么要有 TIME_WAITING 状态延迟释放 fd?
在第四次挥手客户端处在 TIME_WAITING 状态要响应服务端,服务端这条消息是有可能因为网络原因没有收到,服务端是有可能让你将这条消息重传。所以如果此时客户端在收到第三次挥手后不是处在 TIME_WAITING 状态延迟将 fd 释放,而是直接进入 CLOSED 状态释放了 fd,那么重传就不能实现,服务端也就不会释放 fd。
2、为什么要四次挥手?
实际上四次挥手并不是绝对的,在常规的抓包中存在一种现象:ACK=1,ack=98766 和 FIN=1,seq=12345 是在一条报文发过来的。四次挥手是 TCP 协议的标准,但实际业务可以不是四次,也可以是三次挥手。