OSI

第一层物理层:节点之间传输的是0和1,根据传输的介质不同,对应的传输数据的方法也不同。在以太网中是以电脉冲;在WiFi中是以无线电波;
第二层数据链路层:将数据0和1传输到物理层,例如会将WiFi的电脉冲转换为0和1传输到物理层,第二层使用MAC地址在端和端之间传送数据包
第三层网络层:网络层复制端和端之间数据包的传送,路由器位于该层,由它来促进两个网络之间的沟通,第三层使用IP地址在点对点之间传送数据包
第四层传输层:传输层的职责是负责区分网络流。有两种方法可以区分网络流,一种是TCP(Transmission Control Protocol)另一种是UDP(User Datagram Protocol),它们的端口使用范围是 0-65535,第四层是服务和服务之间(host to host)的数据交换
第五、六、七层分别是会话层,表示层,应用层,相对来说这三层不是特别的显著,因为由另一个TCP/IP模型,将这三层进行了封装。

TCP

TCP Transmission Control Protocol 传输控制协议。
文档:https://datatracker.ietf.org/doc/html/rfc793?fileGuid=xxQTRXtVcqtHK6j8#page

TCP头格式:
image.png
TCP,Transmission Control Protocol,传输控制协议,是面向连接的协议。也就是用它传输数据之前双方是要建立连接的。

  • SYN(Synchronization)同步,一个 host 向另一个 host 主动发起连接
  • FIN(Finish)完成,一个 host 主动断开请求
  • PSH(Push)推送,一个 host 给另一个 host 发送请求
  • ACK(Acknowledgement)确认,接收方接收到数据后,都需要给发送方发一个 ACK

知道上述 SYN、ACK、PSH 这些标志位的用处,三次握手也就好理解了:

  1. 客户端发送一个 SYN 的标志位给服务端,这是第一次握手;
  2. 服务端收到 SYN 标志位后,需要返回一个 ACK 标志位给客户端来表示自己收到了,又会发起一次请求将 SYN 和 ACK 合并一起返回,这是第二次握手;
  3. 客户端收到服务端 SYN 标志位后,还需发送一个 ACK 标志位告诉服务端收到了

那挥手为什么要四次才可以?因为断开连接时一个复杂的过程,可能还有 ACK 没有收到,所以不能将两个标志位 FIN 和 ACK 合并返回,所以要四次,如下:

  1. 客户端发送了一个 FIN 标志给服务端,断开连接;
  2. 服务端收到后,返回一个 ACK 标志给客户端;
  3. 服务端也发送一个 FIN 标志给客户端;
  4. 客户端收到后,返回一个 ACK 标志给服务端;

image.png

TCP 的拆包和粘包

拆包,就是将一个数据包,拆分成若干份进行发送。所以为什么要拆成多份,而不是一份直接发送更好吗?这是因为一个数据包太大的时候,更容易出错,一旦出错就要重新发送,而拆分成小份,最后再一起合并,这种即使有几个失败了,重新发送的成本是很低的。当一个数据包比缓冲区还要大,那么就需要进行拆包。一个 TCP 段最小长度为 20 字节。

拆分成若干小份后,如何正确的组装?

粘包:拆包将数据拆分成一小部分进行传输,我们称这一部分为 TCP 段(TCP Segment),为了保证数据在组装的时候能还原,每段都一个序号值 Seq,这样就可以知道发送顺序了。但是当发送方和接收方互相发送数据时,接收方就不知道这条数据对应自己哪一条数据了,所以我们要用 ACK 来确定发送方发送了多少数据。

摘抄一段别人的介绍:

在TCP传输中,每一个字节都是有序号的,从0开始。通过序号的方式保存数据的顺序,接收端接受到之后进行重新排列成为需要的数据。

因此,我对于SEQ和ACK的了解就是: SEQ 代表:发送的这个包中第一个字节(如果有payload的话)的序号 ACK 代表:已成功接受序列号到 ack-1 的数据,期望接收的下一个字节的序号为 ack

举例说明: 我已经发送了前100字节的数据,那么我下一个发送的包(如果发送窗口还有空间)的SEQ就是101,比如要发送10字节的数据,那么下一个包中的数据的字节编号就是 101 - 110. 之后如果继续发送的话,序号就是从111开始。

如果接收端接到了这个10字节的包的话,便会返回一个 ACK 为 111 的包,表示前面110个字节已经成功接收。

滑动窗口

TCP 中每个发送的请求都需要响应。如果一个请求没有收到响应,发送方就会认为这次发送出现了故障,会触发重发。

滑动窗口的大小单位是字节。

TCP请求/相应模型类似左边这样,但是这种方式吞吐量是很低的,一个请求要等响应后才可以继续下一个请求。这样没有充分的利用带宽。优化下如右边这个,同时发送多个请求,这样吞吐量就能上来了。
image.png
因为 TCP 是一个有求必应的协议,所以每次请求都需要响应,所以在 TCP 发送数据包的模型类似这种,队列中有三种类型的数据包,分别是未发送,发送中待响应,已接收的数据。但是这样就变的很复杂了,用3个队列来维护数据包,我们可以换一种数据结构滑动窗口来实现
image.png
在滑动窗口中,我们吧数据定义成 4 种类型:

  1. 深绿色代表已经收到 ACK 的段
  2. 浅绿色代表发送了,但是没有收到 ACK 的段
  3. 白色代表没有发送的段
  4. 紫色代表暂时不能发送的段

image.png
传送成功的话,滑动窗口会向右滑动,并把已响应的标记为深绿色。
image.png
如果其中一个迟迟没有响应的话,如下面的4,那么就只能移动一个窗口了。如果4超过了响应时间还没确认,那么5、6、7都会被接收方丢弃,重新发送。
image.png
image.png

UDP

UDP User Datagram Protocol 用户数据报协议。
文档:https://datatracker.ietf.org/doc/html/rfc768?fileGuid=xxQTRXtVcqtHK6j8(只有3页,简单)

UDP 格式:
image.png
UDP 提供了发送数据包的能力,不需要连接,因为设计的简单性,所以发送的速度快,但没有复杂的协议来保证可靠性。

IP协议

Internet Protocol 网络协议,处于网络层。IP 协议有两种架构,一种是 IPv4,一种是 IPv6。 IPv4 是由 4 个 8 位组成,地址可用空间为 2^32-1个,例如:103.16.3.1
image.png
IPv6 是由 8 个 16 位组成,地址可用空间为 2^128-1个,例如:0123:4567:89ab:cdef:0123:4567:89ab:cdef,每个之间用冒号分隔。它们最大的区别是地址空间不同。

工作原理

IP 协议接收 IP 协议上方的 Host-To-Host (传输层的TCP/UDP)协议传来的数据,然后进行拆分,这个能力叫作分片(Fragmentation)。然后 IP 协议为每个片段(Fragment)增加一个 IP 头(Header),组成一个IP 封包(Datagram)。之后,IP 协议调用底层的局域网(数据链路层)传送数据。最后 IP 协议通过寻址和路由能力最终把封包送达目的地

寻址过程

寻址的作用就是如何根据 IP 地址找到设备。因为 IPv4 的世界中,网络是一个树状模型。IP 协议的寻址过程需要逐级找到网络,最后定位设备。通常我们把 IPv4 的网络分成这样 4 层。
image.png

路由

路由的作用就是选择一条路径。在寻址过程中,数据总是存于某个局域网中。如果目的地在局域网中,就可以直接定位到设备了。如果目的地不在局域网中,这个时候,就需再去往其他网络。

由于网络和网络间是网关在连接,因此如果目的地 IP 不在局域网中,就需要为 IP 封包选择通往下一个网络的路径,其实就是选择其中一个网关。

IP 地址会先合掩码进行位运算,找到对应的 Destination,然后再找到对应的网卡,就可以进入到一个新的路由节点中,直至找到对应的设备。

NAT 协议

NAT 协议(Network Address Translation 协议),内网将数据发动公网中,可以通过路由的方式,将数据传送过去,但是要将公网的数据发送到内网中,需要用到 NAT 技术。然后在通过 ARP(地址解析协议) 解析,用 IP 地址找到对应的硬件 MAC 地址。

该图是公司常见的网络架构
image.png
交换机:
路由器:

Socket 编程

Socket 是一种编程的模型。客户端、服务端的 Socket 对象负责提供通信能力,并处理底层的 TCP/UDP 连接。

I/O 多路复用技术,其实就是服务端的一个 Socket 绑定一个端口,并且将这些 Socket 放入到一个集合中,并开启一个线程定期去扫描,有哪些客户端想要连接进来。这样就达到了一个线程响应多个客户端连接的需求。

响应式,当 Socket 数量较少时,遍历还是高效的,但如果有上万个 Socket,那么性能就不行了,这时就有了响应式编程。响应式编程中有一个观察者的角色,它知道某个 Socket 文件状态发生了变化,并且通知到对应的线程,只要一开始将要执行的线程,注册到对应的 Socket 上就行,这里维护 Socket 使用红黑树,这种查找(是哪个 Socket 文件状态发生变化)和插入(线程注册)的性能都很高。在 Linux 的 epoll 模型中用的数据结构就是红黑树,而这个观察者,一般是操作系统,它最清楚哪些文件发生了变化。

在 Linux 的设计中有三种典型的 I/O 多路复用模型 select、poll、epoll。
select 是一个主动模型,需要线程自己通过一个集合存放所有的 Socket,然后发生 I/O 变化的时候遍历。在 select 模型下,操作系统不知道哪个线程应该响应哪个事件,而是由线程自己去操作系统看有没有发生网络 I/O 事件,然后再遍历自己管理的所有 Socket,看看这些 Socket 有没有发生变化。

poll 提供了更优质的编程接口,但是本质和 select 模型相同。因此千级并发以下的 I/O,你可以考虑 select 和 poll,但是如果出现更大的并发量,就需要用 epoll 模型。

epoll 模型在操作系统内核中提供了一个中间数据结构,这个中间数据结构会提供事件监听注册,以及快速判断消息关联到哪个线程的能力(红黑树实现)。因此在高并发 I/O 下,可以考虑 epoll 模型,它的速度更快,开销更小。

BIO、NIO 和 AIO

BIO Blocking I/O 阻塞IO,NIO Non I/O 非阻塞IO,AIO Async IO 异步IO。这三者其实是 IO 的编程模型。

网络与安全

如何抵御 SYN 拒绝攻击?
SYN 攻击是 DDoS 攻击的一种形式。这种形式攻击者伪装成终端不停地向服务器发起 SYN 请求。通常攻击者的肉鸡,发送了 SYN 之后,不等给服务端 ACK,就下线了。 这样攻击者不断发送 SYN ,然后下线,而服务端会等待一段时间(通常会在 3s 以上),等待 ACK。这样就导致了大量的连接对象在服务端被积累。

针对这个特点可以实现一个 TCP 代理(防火墙),发现有发送 SYN 但是不给 ACK 的行为就对目标 IP 地址禁用一段时间。这个策略平时可以配置成开关,等到被攻击的时候打开。另一方面,可以适当提升连接数支持。