前端学习笔记(二十七)--计算机网络

379 阅读29分钟

要准备春招了,复习一下计算机网络。

1. 应用层

1.1 http

http 是超文本传输协议,属于应用层的协议。
http 使用的是 tcp 连接。属于 client-server 结构。
http 是无状态的,服务器不会记录客户端的访问。

1.1.1 url

全称 uniform resource locator,结构为
protocol://hostname[:portnum]/pathname

  1. protocol 确认是什么协议。
  2. hostname 可以是 dns 名 或者 ip 地址, 加上 portnum 可以确认地址,因为一个 host 上可以同时进行多个进程,每一个进程都有自己独特的端口号(http 的端口号为 80),hostname 加上 portnum 就可以准确定位一个请求了。
  3. pathname 则是请求的资源在服务器里的地址。

1.1.2 http 持久连接

http/1.0 默认是不持久连接,除非加上 Connection: Keep-Alive
http/1.1 默认持久连接。
非持久链接在 http 传输的时候,每一个 obejct 都要重新建立一次 tcp 连接。如在访问很多图片的网站时,会很慢。
持久连接则会在过期时间之前一直保持连接。

1.1.3 pipeline

同样是 http/1.1 引入的http 管线化。
指的是在建立 tcp 连接之后,一次性发送多个 http 请求,而不是等待前一个 http 发送后再发送后一个 http 请求。

1.1.4 web cache

  1. 数据库缓存。
  2. 缓存服务器:缓存代理服务器同时充当客户端和服务端两种角色。当客户端请求服务的时候,会向代理服务器请求而不是直接像源服务器请求,而代理服务器再向源服务器进行请求。代理服务器的运作和普通浏览器一样,验证缓存的有效性也和浏览器一样,也是会进行 http 缓存验证的,只是可以同时为多个用户进行处理。
  3. http 缓存:分为强缓存和协商缓存,区别在于是否向服务器发送请求。浏览器首先会判断是否命中强缓存,如果命中则不会向服务器发送请求;如果没有命中则会向服务器发送请求验证协商缓存;再不命中就只好重新下载文件了。
    • 协商缓存在 http/1.0 的内容为 last-modified/if-modified-since,缺点是只能精确到秒,如果文件1秒内修改多次就会失效,以及如果文件被修改了但是内容没变,etag 也不会变,但是 last-modified 会导致无法使用缓存。因此 http/1.1 推出了 Etag/if-none-match,每次修改后都会生成独有的 etag,因此 1 秒内多次修改也无妨。etag 和 last-modified 是可以同时使用的,优先验证 etag。
    • 强缓存为 expires 或者 cache-control,前者为 1.0,后者为 1.1 推出。expires 为一个具体的时间,到这个时间就过期。缺点是本地客户端修改时间会导致缓存混乱。而 cache-control 指定的则是过期的duration,有多个字段可控制。
  4. 浏览器缓存,或者说浏览器存储,就是 cookie,localStorage,indexedDB 这些。

1.2 DNS

domain name system,目的:为了让 hostname 和 ip 对应起来。因为人不想记 ip 地址。
一个 hostname 可以有多个 ip 地址。
DNS 主要是基于 udp 协议。

1.2.1 继承

DNS 的核心是继承,继承由根域名,顶级域名,权限域名构成。

  • 所有 hostname 都连接着根域名,根域名服务器连接着所有顶级域名服务器。
  • 顶级域名就是 .com .edu 这些。
  • 权限域名则是.com 前面的那些所有的,权限域名服务器真正储存 hostname 和 ip 的对应关系。

1.2.2 本地域名服务器

本地域名服务器不属于继承关系,本地域名服务器被安装在 ISP 上,用于处理解析 DNS 的请求。
当用户向本地域名服务器发送请求后,本地域名服务器会进行递归或者迭代查询。两种情况都可能发生。
本地域名服务器可更改为公共 DNS,比如常用的 114.114.114.114,8.8.8.8。
查询得到 ip 地址,同一个域名可以有多个 ip 地址,为了负载均衡。
所有域名服务器都会缓存得到的 hostname-ip 的匹配,缓存拥有过期时间,不只是服务器,包括计算机和浏览器也会缓存,大大提高了 dns 解析效率。

1.2.3 dns 的好处

  1. 可以在不改变 hostname 的情况下,更改 ip
  2. hostname 可以对应多个 ip,以负载均衡。
  3. ip 可以对应多个 hostname,用于区分不同行为(www,mail,ftp等),或者起别名。

1.3 CDN

将静态资源分布在 CDN 服务器上,用户想要获得资源时,直接请求到最近的 CDN 的 IP,而不用向源服务器请求。
查找的过程是:

  1. 权限 DNS 不是返回域名对应的源 IP,而是返回 CNAME 对应 hostname 的 IP 给本地DNS。
  2. 而这个 CNAME 则是一个 CDN 专用的 DNS 服务器,用于计算并返回离用户最近的 CDN 服务器的 IP。
  3. 也就是说,未使用 CDN 的时候,权限DNS就是最后一个请求的服务器了,但是使用了 CDN 后,权限服务器后头还有一个 CDN 专用的 DNS 服务器。
  4. 然后返回这个 IP 给本地 DNS,解析完毕,本地 DNS 返回给浏览器,然后浏览器向 CDN 服务器请求资源,如果有些资源 CDN 没有,则向源服务器请求。

1.4 http/2.0

最直观的特点就是速度大大加快了,尤其是多个图片的加载速度。

1.4.1 多路复用

2.0 可以在一次连接中多次请求。
而在1.x 中,同一时间内发送多个请求会被堵塞。

1.4.2 二进制分帧

http/1.x 是文本解析,这有着天然缺陷,因为计算机识别的还是二进制。因此 2.0 把 http 层用二进制帧分的更细,把二进制数据传输给 tcp 层。

1.4.3 持久连接

1.1 虽然也有持久连接,但是在一定时间后还是会消失。而 2.0 则实现了持久连接,减少了 tcp 连接的数量,也就减少了网络拥塞,由于 tcp 具有慢启动的限制,减少 tcp 数量也会提高 http 效率。

1.4.4 首部压缩

2.0 有专为首部压缩设计的 HPACK 算法

1.4.5 服务端推送 server push

服务端可以主动推送多个响应给客户端,因为服务端知道客户端可能需要什么,比如各种 css,js 文件。而不需要等客户端解析 html 后发现需要这些文件再去请求。
由于 push 只是猜测,自然会出现 push 过去的资源不需要的情况。
尤其是如果客户端已经有了某个资源的缓存的情况下。因此客户端请求的时候还会用 cache digest 告诉服务端这边已经缓存了哪些资源的信息。

1.5 https

参考资料为这个
https 就是 http+ssl/tls,加密的 http

1.5.1 http

http 请求容易被截获,因此需要加密手段

1.5.2 对称加密

客户端和服务器双方各自拥有一个密钥,但是需要交换密钥的手段,因为密钥交换过程也可能会被截取。

1.5.3 非对称加密

使用公钥和私钥,私钥是只有自己拥有,公钥则是给对方的。非对称加密算法是可以达到加密运输的,双方各自拥有一套公钥和私钥,客户端使用服务器的私钥加密,服务器收到后使用自己的私钥解密,再用客户端的公钥加密响应内容,客户端收到后使用私钥解密。不管是哪边发送的信息都需要私钥才能解密,因此黑客无法街区内容。
但是这样的缺点在于非对称加密的算法复杂度高,性能不好。

1.5.4 对称加密+非对称加密

因此结合两者的优点,形成这样的一个加密手段:
首先使用非对称加密算法交换对称加密的密钥,之后就一直使用对称密钥交换信息。这样解决了对称加密无法交换的缺陷。由于非对称加密算法只在交换密钥的时候使用,因此性能也不算差。

  1. 客户端使用服务端的公钥加密对称加密的密钥,发送给服务器。
  2. 服务器收到后使用自己的私钥解密,得到对称加密的密钥。使用该密钥返回信息告诉客户端收到了。
  3. 收到服务器的确认后,从此客户端就使用这个对称加密密钥来和服务器传输数据了。 这样依然有缺点:
  • 客户端怎么知道一开始拿到的是服务器的公钥,如果这个公钥是黑客的话,那从一开始就在和黑客交换信息。

1.5.5 SSL 证书

在一开始建立连接的时候,服务器就像客户端发送一个 SSL 证书,包含各种信息(也包含服务器的公钥)。
客户端收到后就对证书验证真伪。验证完毕后就可以用附带的公钥加密发送对称加密的密钥了。之后就是安全传输。

1.5.6 TLS

就是升级版更安全的 SSL,具体不清楚。

2.传输层

应用层之后是传输层。

2.1 套接字

用户可能有多个网络应用:http,https,邮箱,各种应用都要用到网络。
但是即使数据通过网络层(传输层的下一层)知晓了 IP 地址,从而准确从服务器流到客户端,但是客户端又怎么知道是哪个应用需要哪些数据呢?
IP 地址不足以知道这些信息。而这一步就要靠传输层了。传输层和应用层之间有一个 socket,每一个 socket 都有一个标识,这就是端口号。比如 http 是 80。

2.2 多路复用

套接字接受从应用层的数据后,对来自不同的端口号加以区分,打包进传输层header。或者接受网络层传过来的数据后,解析传输层header后,从而知道要发送到哪个端口。这就是多路复用。

2.3 UDP

UDP 是无连接的,无握手,header 数据小,无拥塞控制,因此传输速度快。容易犯错。

2.3.1 UDP 头

①source port, ②dest port, ③length,④checksum。(以上4个都是 16 bits/2 bytes)以及 ⑤payload。

  • length 是的大小(整个包,不只是头部)。
  • checksum 用于检验数据包。不是大小。

2.4 tcp

tcp 是属于 reliable data transfer。源ip,源端口号,目的ip,目的端口号,这四个可以确认一个tcp连接。

2.4.1 tcp 头部

  • 第一行
  1. 首先自然也有 source portnum 和 destination portnum。也都是 16 bits。
  • 第二行,第三行
  1. 然后是序列号和 ack 号,都是 32 bits。ack号为上次接受到的序列号+1,只有在 ack 为 1 的时候有效。
  • 第四行
  1. 接下来的16 bits分成很多小区块,有 4bits 的首部长度(因为 tcp 头部包含一个可变区域),表示有多少个 32bits,最大值为 15,也就是说tcp 头部最大值为 60 bytes。6bits的未使用长度,然后 6 个 1bits 的是否为 xxx 状态的布尔属性。是就为 1,其中如果 ACK 为 1,则上面说的 ack 号才有效。RST 为复位,当传输出现异常时候发送。
  2. 16bits 的窗口大小。在接收方发送给发送方时使用,表示接收端期望接受的数据大小。用于流量控制。
  • 第五行
  1. 16bits 的 checksum。
  2. 16bits 的紧急数据指针。当 URG 为 1 时有效。
  • 第六行(可选)
  1. 可选字段,如 MSS,SACK等。最大 40 bytes。 前五行的 20 个字节是 tcp头部一定有的,即 tcp 头部的最小长度。在此之后 tcp 还有可选选项

2.4.2 MSS(最大报文大小)

属于 tcp 头部的可选字段。
tcp 三次握手时双方都要发送,只有 SYN === 1 的时候才能用,也就是说前两次握手时候用。表明的是本端能接受的最大字段长度。

2.4.3 三次握手

传 统 艺 能。就不说了,不过提一嘴状态变化
状态变化(假设发送端为 A,接受端为 B):

  1. 刚开始什么都没发生时,由于没有连接,A,B 处于 CLOSED 状态,B 调用 listen 函数,变成 LISTEN 状态。
  2. A 发送第一次 SYN 时,立刻变成 SYNSENT 状态,非常的顾名思义
  3. B 接受到第一个 SYN 并返回 SYN-ACK 时,变成 SYNRCVD 状态,也非常的顾名思义
  4. A 接受到 SYN-ACK 并返回 ACK 时,变成 ESTAB 状态
  5. B 接收到 ACK 时,变成 ESTAB 状态
  6. 最终双方都是 ESTAB 状态,握手结束

2.4.4 四次挥手

tcp 支持半关闭,即可以只关闭发送,但还可以接收,vice versa。
关闭的流程如下(假设发起关闭的一端为 A,另一端为 B):

  1. 最开始什么都没发生时,A,B 都处于 ESTAB 状态。
  2. A 准备关闭了,A 向 B 发送 FIN=1,seq=a 的报文,此时变成 FIN_WAIT_1 状态,此时 A 关闭发送数据的通道,还可以接受数据,因为 B 可能还有正在发送中的数据没发送完。 A 此时处于半关闭状态。
  3. B 接受到了这个报文,如果此时 B 还有数据没有发送完,则只返回一个 ACK=1,ack=a+1 的报文,注意仅仅只是 ACK 哦。此时 B 关闭接受数据的通道,仍然还可以发送数据,处于半关闭状态。此时 B 变为 CLOSE_WAIT 状态,等待自己把数据发送完。
  4. A 接收到 ACK 后,变成 FIN_WAIT_2 状态,并继续接受 B 正在发送的数据。
  5. 等 B 发送完所有数据,B 会把发送数据的通道也关闭了。B 发送一个 FIN=1,seq=b 的报文。B 变成 LAST_ACK 状态
  6. A 接受到这个报文,发送一个 ACK=1,ack=b+1。进入 TIMED_WAIT 状态,等待两个 MSL(报文最大存活时间)。等待时间结束后变成 CLOSED 状态。
  7. B 接受到 ACK 后,变成 CLOSED 状态。
  8. A 的等待时间结束后,两边都变成 CLOSED 状态了,挥手结束。

2.4.4.1 另一种情况

如果 B 第一次接收 FIN 的时候,并没有数据要发送,则可以直接发送 FIN-ACK,CLOSE_WAIT 立刻变成 LAST_ACK 状态。A 接收到 FIN-ACK 也跳过 FIN_WAIT_2 状态,直接返回 ACK 并进入 TIMED_WAIT 状态开始计时。

2.4.4.2 为什么要 TIMED_WAIT

  1. 因为 A 最后一次发送的 ACK 可能会丢失,如果 B 没有接收到这个包,则会重新发送一个 FIN-ACK。如果不 TIMED_WAIT,那么已经关闭的 A 就不会发送 ACK,导致 B 一直开着。
  2. 如果使用了 TIMED_WAIT 等待两个 MSL,则有足够的时间接受包。因为 B 会花费一个 MSL 去等待 A 的ACK,如果丢失了,B 发送 FIN-ACK 又会花费一个 MSL,所以总共是两个 MSL。

2.4.5 流量控制

在 TCP 头有一个 16 bits 区块叫做 RWND(receive window),在接收方发给发送方时,告诉发送方自己还能缓存多少数据,用以控制发送方不要发太快了。因为应用层处理 tcp 是要花时间的,而 tcp socket buffer 是有限制的,不能堆积太多的 tcp,如果超过了就会被丢弃。因此需要进行流量控制。

2.4.5.1 发送端的滑动窗口

流量控制使用滑动窗口实现。发送端和接收端都有滑动窗口。
发送端的滑动窗口: 发送端通过接收端发送回来的 ACK 头部的 RWND 来调整滑动窗口大小(调整说的有点歧义,其实就是相等)。

  • #1 绿色:已发送,已确认ACK。
  • #2 蓝色:已发送,还没有收到ACK。
  • #3 黄色:还没有发送,但是接收方又余裕处理,可以发送。
  • #4 红色:还没有发送,接收方没有余裕处理,不能发送。 由于知道了#3黄色部分,发送端就会把黄色部分发送过去,发出去的时候滑动窗口变成下图这样: 此时在接收到下一个 ack 前自然是不能发送数据了,因为接收方还没处理完嘛。 等收到 ACK 后,一些蓝色部分变成了绿色,滑动窗口右移,一些红色部分变成了黄色,可以发送了:
  • 注意:这里收到 5 个 ACK,图里红色部分也是有 5 个字节变成黄色,但是这是滑动窗口大小不变的情况,如果发送端发过来的 ACK 里的滑动窗口大小改变了,黄色部分也会不一样。假设之前滑动窗口是 25,现在是 24,那么黄色部分也只会有 4 个字节。

2.4.5.2 接收端的滑动窗口

接收端只有 3 个部分。

  • #1+#2 绿色:已成功接收,并确认的数据(但不代表被应用处理了)。
  • #3 蓝色:未收到的数据,buff 里的空闲大小,发送 ACK 时会包含在头部告诉发送方进行流量控制。
  • #4 红色:超出 buff 的范围。

2.4.5.3 窗口关闭

如果接收方数据处理不过来的时候,窗口会变小,有时候可能会变成 0,这很正常,等到现有的数据处理到有空闲后,再给发送方非 0 的窗口大小的 ACK。但是如果这个 ACK 丢失了怎么办。由于发送方之前接受过 0 的窗口大小,不会再发送数据,之后没收到 ACK 的时候并不会认为这是数据丢失而是接收方没有处理完。接收方发送 ACK 后不会去等待回应,因此也不会发现这个错误。
解决方案是持续计时器,一旦发送方接收到窗口为 0 后,就会启动计时器,超时后就会发送一个窗口探测报文,请求接收方告诉现在的窗口大小。一共发送三次,如果三次都还是0,就发送 RST 重置 tcp 连接。

2.4.6 保活计时器

  • 注意这里开始已经不是流量控制的内容了。
  • 英文叫 keepalive,和 http 的 keep-alive 不一样,那个是应用层的协议,用于持久连接。 有时候 tcp 一端可能出现异常,无法继续工作,没有发送关闭报文,那么另一端怎么知道关闭连接呢?就有了保活计时器,这个计时器检测时间较长,当某一端长时间没发送报文过来,另一端就会发送报文检测,多次异常后关闭连接。

2.4.7 重传

tcp 重传有多个机制

2.4.7.1 超时重传

tcp 本身为了防止数据丢失/ack 丢失,有一个超时计时器,一旦发送的数据包没有收到 ack,就会重新传递数据。 缺点在于:

  1. 由于是被动等待,超时的等待重传的时间较长。
  2. 不知道重传多少个数据(具体看下面的快速重传)

2.4.7.2 快速重传

(首先注意快速重传和超时重传是同时存在的)
快速重传和超时重传区别在于快速重传和时间无关。
当接收到 3 次同样的 ack 时候就重传。

  • 优点是现在是主动重传,比被动等待timeout要快(尤其是超时重传第二次的等待时间还会翻倍)。
  • 缺点是和超时重传的第二个缺点一样,重传的时候并不知道重传一个还是多个。以上图为例,收到三次 ACK2,那么到底是重传 seq 2,还是要把 3,4,5 也一起重传了。因为接收方只返回一个 ACK2,发送方并不知道这个 ACK2 代表着只有 seq 2 没有收到,还是 2,3,4,5 都没有收到。

2.4.7.3 SACK(选择性 ACK)

为了解决上面的不知道重传一个还是多个数据的问题,SACK 诞生了。SACK 可以在 tcp 头部的可选选项中加上。
SACK 解决的方案就是直接把缓存的地图一起发过去,这样发送方就知道哪些被接受了,到底是发送 seq2 还是 2,3,4,5.

2.4.7.4 D-SACK

SACK 的更进一步,不仅告诉发送方那些数据被缓存了,还告诉哪些数据被重复发送了。 这样做的好处是:

  • 可以知道这次重传的原因是什么,是我发的数据包丢失了?还是接收方返回的ACK丢失了。甚至是其实没有丢失只是网络延时太长了。

2.4.8 拥塞控制

流量控制控制的是发送端和接收端。而拥塞控制考虑的是整个网络环境,如果网络环境太差,就会重传,但是重传又会加重整个网络环境的堵塞,恶性循环。因此需要拥塞控制。
拥塞控制使用了拥塞窗口cwnd的概念,由发送方维护,发送方的窗口大小不再是rwnd,而是两者之间较小的那个 min(cwnd, rwnd)。当整体网络堵塞的时候,cwnd 就会变小,取代 rwnd,减少整个网络环境的堵塞。

2.4.8.1 慢启动 slowstart

拥塞控制的第一步是慢启动,一开始的时候,cwnd为1,每当收到一个 ACK,cwnd 就会 + 1。 具体表现就是cwnd的增长呈现指数增长。因为第一次发送1个数据包后,收到一个ack,cwnd变成2,发送两个数据包,接受两个ack,cwnd变成4,以此类推。
慢启动有一个阈值叫做慢启动阈值 ssthresh。当cwnd到达这个阈值的时候,进行拥塞避免。

2.4.8.2 拥塞避免

当cwnd到达ssthresh的时候,使用拥塞避免算法。此时不再是收到一个ack就加1,那样太快了。而是收到一个ack就加1/cwnd,这样表现出来的就是cwnd呈线性增长。因为每次收到的ack数为cwnd,cwnd*(1/cwnd) = 1,每次cwnd都只增加一个。

2.4.8.3 拥塞发生

虽然进入拥塞避免算法后增长速度变慢了很多,但是依旧是在增长。总有一天会增长到很大,导致整个网络开始发生拥塞(当然这不是一定发生的,因为还有rwnd在限制着)。
那么如果发生了拥塞怎么办?
首先要知道如何判断拥塞,当重传的时候判断为拥塞。由于重传分两种主要情况(SACK和D-SACK是对重传要传哪些数据包进行优化,在SACK起作用前就已经判断了需要重传)。超时重传和快速重传:

  • 超时重传:如果这次重传是超时重传,则直接让 cwnd 变为1,sshthresh 变为降成 1 之前的 cwnd 的一半。
    ssthresh = cwnd/2;
    cwnd = 1;
    
    然后由于此时 cwnd < ssthresh, 开始慢启动状态。
  • 快速重传:如果这次重传是快速重传,则进行一种叫做快速回复的算法。 由于此时 cwnd > ssthresh ,直接进入拥塞避免状态,线性增长。
    快速恢复算法如下:
    1. 收到三个重复ACK,判断拥塞,进行以下改变(注意此时快速恢复还没有结束)。
      ssthresh = cwnd/2;
      cwnd = cwnd/2 + 3; // 为什么 + 3,因为既然还能收到重复ACK,说明网络环境还没那么差
      	            // 如果网络环境真那么差的话,那就是超时重传了
      
    2. 重传需要重传的数据。
    3. 如果还是收到重复的ACK,则每个重复的ACK都会让cwnd+1(注意不是1/cwnd)。
    4. 只有收到正常的数据,这个时候快速恢复阶段才会结束
    5. 快速恢复结束阶段时设置 cwnd = ssthresh;
    6. 此时进入拥塞避免状态。(有的实现的条件是 cwnd<=ssthresh 的时候慢启动,那么此时还会进行一次慢启动,然后立刻超过阈值,变成拥塞避免)

具体算法根据tcp不同的实现不一样。
以上都是以单个数据包为单位,如果以数据为单位,则都要乘以MSS。

3. 网络层

网络层主要是 IP 协议,也称 IP 层。
网络层是无连接,无可靠性的。每次传输的路线可以不一样,如果出现数据丢失也不会有什么反应。这些都是传输层的工作。

3.1 IP 头部

  • 第一行
  1. 4 bits 的协议版本,IPV4 为 4。
  2. 4 bits 的首部长度,和 tcp 一样表示有多少个 32bits,因此最大值也为 60 bytes。有这个的原因自然也是因为 IP 首部也有一个可选区域。如果没有可选区域,IP 头部大小也和 TCP 一样是 20 bytes。
  3. 8 bits 的服务类型。表示该 IP 应该被怎么传输。如最小延迟,最大吞吐量等等。
  4. 16 bits 的总体大小,表示整个数据包的大小,包括里面的数据本身。
  • 第二行
  1. 16 bits 的标识符,因为太大的 ip 包会在数据链路层被分成多个,到达目的地再重组,可以用标识符表示哪些是来自同一个 IP 。标识符相同的就为同一个包。
  2. 3 bits 的 flag,表示是否分包。
  3. 13 bits 的数据偏移,表示这个分包在原数据中的哪个位置。
  • 第三行
  1. 8 bits 的生存时间(TTL)。虽说是时间,但是表示的是经过路由的跳数,如果经过太多路由,报文就丢弃,向源ip返回超时信息。可以解决因为设计bug产生的数据环路的问题。
  2. 8 bits 的协议。表示上层协议。如 tcp,udp,icmp 等。
  3. 16 bits 的checksum。用于检验数据头,每个路由器都会检查。
  • 第四行
  1. 32 bits 的源 IP。
  • 第五行
  1. 32 bits 的目的 IP。
  • 第六行(可选)
  1. 可选字段选项,最大 40 bytes。 前五行总长也是 20 bytes。

3.2 IP 地址

IP 地址存在于接口,路由上可以有多个接口。host 上一般为1或2个接口(以太网和无线网)。每个接口都要有一个 IP 地址。

3.2.1 IP 地址划分

IP 地址是一个 32 位整数(这里指 IPV4 地址)。由网络号和主机号组成。网络号为高位bits,主机号为低位bits。
拥有同一个网络层的主机们被称为一个“网络”。在这个网络里的主机们可以直接互相交流,而无需经过中介路由。比如一个无线局域网里的主机可以直接在局域网里连接而无需连上路由。

3.2.1.1 分类方式划分

ip 地址有5种分类。每种分类代表不同的网络号和主机号分隔方式。

3.2.1.2 CIDR

一开始的 IP 分类实在是太容易溢出。A,B,C 类之间相差太大。有的组织买 A 太大了,买 B 太小了,或者买 C 都太大了。因此采用 CIDR 来解决。CIDR 的解决方案就是没有分类,网络号和主机号的划分可以在任意位置。

3.2.1.2.1 子网掩码

那么问题来了,当可以从任意地方切割分类之后,如何知道在哪里切割的呢?换句话说如何知道哪些部分是网络号,哪些部分是主机号呢?
这就要用到子网掩码的概念。
子网掩码是一串连续的 1,结尾为连续的 0 的 32 位整数。将子网掩码和 ip 地址进行按位与运算,得到的就是网络号。也就是说子网掩码前面有多少个 1,网络号位置就到哪里。
(下图表示掩码的工作原理)
不过上图不是很好的例子,上图的掩码是 255.255.255.0,其实就是 C 分类。由于 C 分类一个网络就有 256 个主机号,假设我们想把这些主机号分成 4 个网络,那么就需要掩码:255.255.255.192。也就是255.255.255.0 的最后 8bits 从 00000000 变成 11000000。因为 4 个网络需要两个bits,因此牺牲主机号的两个 bits 变成网络号。
这样就从 1 个有 256 个主机号的网络。分成了 4 个 分别都有 64 个主机号的子网。 如果按照上图的 ip 地址来写的话,应该写成:223.1.1.2/26。
其中,/26 为子网掩码的简写,因为子网掩码只能是连续的 1,/26 就代表有 26 个连续的 1。也就是 255.255.255.192。
以后可以通过简单的与运算得到这个 IP 地址的网络号,即 223.1.1.2 & 255.255.255.192。

3.2.1.2.2 特殊主机号

每个局域网中都有两个特殊主机号,一个是主机号全为 1,用于广播。还有一个是主机号全为 0,也就是网络号,代表这个局域网。

3.2.1.3 DHCP

IPV4 地址虽然有32位,但也只有 2**32 =43亿个地址。而且手动分配 ip 地址也不方便。
DHCP 可以动态的给连上的主机分配 ip 地址,只有连上的时候才会分配,断开的时候 ip 地址便可回收,用于其他主机。既增加了 ip 地址的复用性,又减少了手动分配 IP 的麻烦。
一般路由器基本上都配有 DHCP 功能。

  • DHCP 分配地址通过两个来回。第一个来回是主机广播寻找 DHCP 服务器,DHCP 服务器返回服务器的地址。第二次向 DHCP 服务器请求分配 IP 地址,返回分配的 IP 地址。
    DHCP 使用 UDP 协议。

3.2.1.4 NAT

也是为了防止 IPV4 地址枯竭而创造的技术。
NAT 将网络分为公网和私网。私网内所有主机共用同一个公网地址。私网是网络内部使用,不会占用 IPV4 地址,不同的私网 IP 的主机发送请求的时候,源 IP 和源端口都会被 NAT 网关翻译成同一个公网的不同端口。

  1. 私网主机发送请求(source 为私网 IP 以及端口号)。
  2. 请求到达 NAT 网关,网关将其翻译为自己的 IP 地址加上新的端口号。并记录该对应关系以便之后应答的时候用。
  3. 请求到达目的,返回应答的目的 IP 和端口也是公网 IP 和公网端口。
  4. NAT 网关收到应答,拿出之前记录的对应关系表,将该公网 IP+端口翻译成对应的私网 IP+端口。将应答发送给主机。
3.2.1.4.1 内网穿透

虽然上图看起来没什么问题,但这只是内网访问外网的情况,如上图第①步的目的IP是公网IP。但如果是外网访问内网呢?内网访问内网呢?
解决这个问题就要靠内网穿透,不过内网穿透有非常多的方式,这里就不细说了。要学的时候再说吧。

3.3 ICMP

之前提到 IP 头的协议类型中提到了数据里可以是 tcp,udp,还可以是 icmp。
icmp 虽然是被 ip 头包裹着的,但是仍然属于 ip 层,而不是传输层。因为 icmp 还是基于 ip 协议工作的。
ICMP 用于解决验证 ip 传输可靠性的。因为 ip 协议并不保证可靠传输,而 tcp 是属于传输层的无法验证 ip 层的可靠性。
像 ping 就是基于 icmp 协议的。

4. 数据链路层

4.1 MAC 地址

MAC 地址和网卡绑定,由生产商烧录好,具有唯一性。长度为 48 bits。

4.2 分组交换

之前讲 ip 的时候提到,大的 ip 数据包会在 datalink 层被分成多个小块。因为数据链路层存在 MTU 的限制。不同的数据链路有不同的 MTU。被分成的每一个小块叫做帧。帧也有首部,包含着源 mac 地址和目的 mac 地址。不同的数据链路有不同的首部。

4.3 错误检测

数据都是二进制的。使用 CRC 检测二进制的错误。

4.4 ARP

通过 DNS 得到 IP 地址后,由于数据链路层还需要 mac 地址。因此需要 ARP 协议。

  • 如果发送主机已存有该ip对应的mac地址,则直接封装帧数据发送即可。
  • 如果没有的话,分为相同局域网和不同局域网两种情况:
    1. 相同局域网下。发送一个 ARP 请求,其中包含着目的主机的 IP。ARP 请求为广播请求,该局域网上所有的主机都能收到这个请求。但只有目的主机会处理该请求(处理请求的时候也把发送主机的ip-MAC地址对应关系也储存,以便之后返回时使用),然后返回该主机的 MAC 地址,返回给发起请求的主机(因为储存了ip-mac 对应关系)。此时发送主机才开始发送一般的帧数据。
    2. 不同局域网下(假设从 A 经过路由 R,发送到 B)。
      1. 首先要查询路由的 MAC 地址(当然在此之前还要查询路由的 IP 地址)。
      2. 将数据发送到路由。IP src 为 A,IP dest 为 B,MAC src 为 A,MAC dest 为 R
      3. 路由接收到数据,检查 IP 发现是要传到 B 的数据,因此发送 ARP 查找 B 的 MAC 地址。然后发送帧数据,此时 IP src 和 dest 不变,还是 A 和 B。但是 MAC 的 src 变成 R,并且 dest 为 B。
      4. 也就是说,数据传输的时候,ip 地址源和目的不变,而 MAC 在每次主机和路由,路由和路由之间的传输时(或者说每一跳时)都会改变。IP 地址是一种整体的控制,MAC 地址是局部的控制。

5. 物理层

由于数据都是二进制构成的,因因此物理层可以通过各种方式如电压的高低,光的闪灭等方式来表示二进制。