Skip to content
Published:

网络基础

Table of contents

Open Table of contents

OSI 和 TCP/IP 网络模型

OSI 的七层体系结构概念清楚,理论也很完整,但是它比较复杂而且不实用,而且有些功能在多个层中重复出现。

TCP/IP 四层模型是目前被广泛采用的一种模型,我们可以将 TCP/IP 模型看作是 OSI 七层模型的精简版本(并不能完全精准匹配),由以下 4 层组成: image

应用层

两个终端设备上的应用程序之间的信息交换,定义了信息交换的格式,消息交给下一层传输层

常见的应用层协议有:

传输层

负责向两台终端设备进程之间的通信提供通用的数据传输服务。是为应用层提供网络支持的。

网络层

应用层和传输层都是端到端的协议,而网络层及以下都是中间件的协议了

主要协议包括:

网络层最常使用的是 IP 协议 (Internet Protocol),IP 协议会将传输层的报文作为数据部分,再加上 IP 头组装成 IP 报文,如果 IP 报文大小超过 MTU (以太网中一般为 1500 字节)就会再次进行分片,得到个即将发送到网络的 IP 报文。

网络层最重要的五个字:转发与路由

网络链路层

生成了 IP 头部之后,接下来要交给网络链路层 (Link Layer),在 IP 头部的前面加上 MAC 头部,并封装成数据帧 (Data frame) 发送到网络上。

以太网就是一种在局域网把附近的设备连接起来,使它们之间可以进行通讯的技术。电脑上的以太网接口,Wi-Fi 接口,以太网交换机、路由器上的干兆,万兆以太网口还有网线,它们都是以太网的组成部分

MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息,我们可以通过 ARP 协议获取对方的 MAC 地址。

所以说,网络链路层主要为网络层提供链路级别传输的服务,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标识网络上的设备。

总结

img

img

为啥要分层?

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,计算机整个体系从上到下都是按照严格的层次结构设计的

HTTP

TCP

基本

TCP 是什么

TCP 是面向连接的(一定是一对一才能连接)、可靠的、基于字节流的传输层通信协议。

TCP 连接是什么

客户端与服务端达成以下三个信息的共识后建立一个 TCP 连接

  1. Socket: 由 IP 地址和端口号组成
  2. 序列号:用来解决乱序问题
  3. 窗口大小:流量控制

为什么需要 TCP 协议

IP 层是不可靠的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。

TCP 是一个工作在传输层可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。

如何唯一确定一个 TCP 连接

TCP 四元组可以唯一的确定一个连接,四元组包括:

源地址和目的地址的字段(32 位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。

源端口和目的端口的字段(16 位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。

TCP header

img

  1. 序列号:建立连接时由计算机随机生成,通过 SYN 包传给接收端,每发送一次数据就累加一次该值。用来解决网络包乱序的问题
  2. 确认应答号:指下一次期望收到的数据的序列号。用来解决丢包的问题
  3. 控制位:
    • ACK: 该位为 1 时,确认应答号为有效。TCP 规定除了建立连接时 SYN 包之外,该位必须设置为 1
    • RST: 该位为 1 时,表示 TCP 连接出现异常,必须强制中断连接
    • SYN: 该位为 1 时,表示希望建立连接,并在其序列号的字段进行序列号的初始值设定
    • FIN: 该位为 1 时,表示今后不再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间相互交换 FIN 为 1 的 TCP 段

UDP vs TCP

UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务。

UDP 协议真的非常简,头部只有 8 个字节(64 位)

img

区别如下:

  1. 连接

    • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
    • UDP 是不需要连接,即刻传输数据。
  2. 服务对象

    • TCP 是一对一的两点服务,即一条连接只有两个端点。
    • UDP 支持一对一、一对多、多对多的交互通信
  3. 可靠性数据

    • TCP 是可靠交付数据的,可以无差错、不丢失、不重复、按序到达,
    • UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议
  4. 拥塞控制、流量控制

    • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性:
    • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率
  5. 首部开销

    • TCP 首部长度较长,会有一定的开销,首部在没有使用选项字段时是 20 字节,如果使用了选项字段则会变长的。
    • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
  6. 传输方式

    • TCP 是流式传输,没有边界,但保证顺序和可靠。
    • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
  7. 分片不同

    • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
    • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后在 IP 层组装完数据,接着再传给传输层。
  8. 使用场景

    • TCP: https/http/ftp 文件传输
    • UDP: 包总量较少的通信:DNS,视频/音频等多媒体通信
为什么 UDP 头部没有首部长度字段,而 TCP 头部有首部长度字段呢? 原因是 TCP 有可变长选项字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。
为什么 UDP 头部有包长度字段,而 TCP 头部则没有包长度字段呢? TCP 数据长度 = IP 总长度 - IP 首部长度 - TCP 首部长度; 其中 IP 总长度 和 IP 首部长度,在 IP 首部格式是已知的。TCP 首部长度,则是在 TCP 首部格式已知的,所以就可以求得 TCP 数据的长度。
UDP 也是基于 IP 层的呀,那 UDP 的数据长度也可以通过这个公式计算呀? 为何还要有包长度呢? 我也不知道

TCP 和 UDP 可以使用一个端口吗

可以的

在数据链路层中,通过 MAC 地址来寻找局域网中的主机。

在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。

在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。

所以,传输层的端口号的作用,是为了区分同一个主机上不同应用程序的数据包。

传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。

当主机收到数据包后,可以在 IP 包头协议号字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据端口号确定送给哪个应用程序处理。

连接建立

TCP 三次握手过程

summary

tcp hello

tcp hello

tcp hello

为什么是三次

白话版:保证双方都具有接收和发送的能力

终极版:

TCP 连接主要是保证 Socked,序列号,窗口大小等信息的组合的可靠性和稳定性的传输。

那么三次握手是怎么初始化 Socked,序列号,窗口大小并建立连接呢?主要是如下三个原因:

原因一:避免历史连接(main)

试想一个场景:

客户端先发送了 SYN(seq = 90)报文,然后客户端宕机了且网络堵塞了,服务端还没有收到。

客户端重启后,重新向服务端发起连接,发送 SYN(seq = 100)报文(注意这里不是重传,重传的 Seq 是一样的)

SYN(seq = 90)报文较早到达服务端,服务端会返回一个 SYN + ACK 的报文给客户端,此报文中确认应答号为 91(90+1)

客户端收到后,发现与自己期望的 101(100+1) 不符,于是就会回 RST 报文

服务端收到 RST 报文后,就会释放连接

后续 SYN(seq = 100)到达服务端后,客户端与服务端即可进行正常的三次握手了

那么为什么两次握手做不到避免历史连接呢

主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阳止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。

在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据

但是客户端此时还没有进入 ESTABLISHED 状态

假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而服务端在第一次握手的时候就进入 ESTABLISHED 状态

所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。

原因二:同步双方初始序列号

TCP 协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素,它的作用:

当客户端发送携带初始序列号的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送初始序列给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了三次握手。 img

而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

原因三:避免资源浪费

如果只有两次握手,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢? 如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个兄余的无效链接,造成不必要的资源浪费。

总结

两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

第 2 次握手传回了 ACK,为什么还要传回 SYN?

初始序列号 ISN

为什么要求要不一样?

主要原因有两个方面

如何产生的

起始 ISN 是基于时钟的,每 4 微秒 +1,转一圈要 4.55 个小时。

RFC793 提到初始化序列号 ISN 随机生成算法:ISN=M+F(localhost, localport, remotehost, remoteport)

M 是一个计时器,这个计时器每隔 4 微秒加 1.

F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。

随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

既然 IP 会分片,为什么 TCP 还要控制 MSS 呢

如果在 TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,当 IP 层有一个超过 MTU 大小的数据 (TCP 头部 + TCP 数据) 要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,再交给上一层 TCP 传输层。

如果一个 IP 分片丢失,接收方的 IP 层就无法组装成一个完整的 TCP 报文(头部+数据),也就无法将数据报文送到 TCP 层,所以接收方不会响应 ACK 给发送方,因为发送方迟迟收不到 ACK 确认报文,所以会触发超时重传,就会重发整个 TCP 报文(头部+数据

因此,可以得知由 IP 层进行分片传输,是非常没有效率的。 所以,为了达到最佳的传输效能,TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU,自然也就不用 IP 分片了。

握手失败了会发生什么

第一次丢失

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。

在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发超时重传机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。

不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的。

当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几次呢? 在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries 内核参数控制,这个参数是可以自定义的,默认值一般是 5。

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。每次超时的时间是上一次的 2 倍。当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然 后断开 TCP 连接。

第二次丢失

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。

第二次握手的 sN-AcK 报文其实有两个目的:

所以,如果第二次握手丢了,就会发生比较有意思的事情,具体会怎么样呢? 因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。

然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。 在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries 内核参数决定,默认值是 5。

第三次丢失

客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手或者达到最大重传次数。

注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文

SYN 攻击(SYN Flood)

假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入 SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,默认情况都会丢弃报文。SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。

连接断开

TCP 四次挥手过程是怎样的?

tcp bye bye

为什么挥手需要四次?

关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。

服务端收到客户端的 FIN 报文时,先回一个 AcK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接

挥手丢失了,会发生什么?

第一次丢失

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2 状态。

如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2倍) 如果还是没能收到第一次挥手,那么直接进入到 close 状态。

第二次丢失

当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到CLOSE_WAIT 状态。

在前面我们也提了,ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

第三次丢失

当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于CLOSE WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。 服务端处于 CLOSE WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST ACK状态,等待客户端返回 ACK 来确认连接关闭。 如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries参数控制这与客户端重发 FIN 报文的重传次数控制方式是一样的。

第四次丢失

为什么 TIME_WAIT 等待的时间是 2MSL?

为什么需要 TIME_WAIT 状态?

TIME_WAIT 过多有什么危害?

如何优化 TIME_WAIT?

服务器出现大量 TIME_WAIT 状态的原因有哪些?

服务器出现大量 CLOSE_WAIT 状态的原因有哪些?

如果已经建立了连接,但是客户端突然出现故障了怎么办?

如果已经建立了连接,但是服务端的进程崩溃会发生什么?

IP