前言
一直好奇网络请求具现出来是什么样子的。从面试官的一个问题,在浏览器输入网址之后,到页面生成,中间会发生什么。为了这个问题,看了《计算机网络:自顶向下》,《图解HTTP》,学习了 node.js 中关于网络部分的源码,结果却还是自认为相差甚远,直到接触了路由表,才开始所有感觉,觉得补上缺失的一脚。
网络结构
OSI 7 层模型定义了一个规范概念,而 TCP/IP 的4 层结构则给出了实现。两者的比较,网上多有资源,这里不做对比了,结合发快递的过程来说说吧:
-
第七层,应用层,常见协议 Http、Https、DNS、FTP。作用是为应用程序接口提供网络,直白来讲,就是提供请求/响应数据服务。数据也叫做应用层数据 Application Data;在快递上,就是你自己把要发的快递准备好,包括目的地 url 等。
-
第四层,传输层,常见协议有 TCP、UDP,建立维护端到端的连接,直白来说就是将网络层上面的传输,扩展为端与端的进程之间的服务,数据为 TCP Segment 或 UDP Datagram;如同快递员上门揽件,你要填写物流信息,然后快递员将包裹发送至第一个集散地。
-
第三层,网络层,常见协议就是 IP、DHCP 协议。作用是提供路由和选址功能。从你这边发送快递到目的地,不可能是快递员亲自做高铁发送过去的,需要一个集散地,而始发集散地到目的集散地中间可能有许多的路径选择,比如从深圳到北京,可能中途是 深圳 - 武汉 - 石家庄 - 北京,也有可能是 深圳 - 上海 - 北京。网络层上流通数据叫做 IP Packet,也叫做 IP 包。
-
第二层,数据链路层,常见协议有帧中继。常用到的设备是交换机。主要作用是提供ARP 查询报文,在 MAC 表里面找到目标 MAC 地址,然后发送数据。这样的作用体现在从一个路由器到另外一个路由器的过程中,当然也包括初始发送数据的接入交换机过程。在快递里面可以理解为,在集散地武汉里面从飞机拿下快递,要发送到北京,发现离北京最近的是石家庄,石家庄在哪里呢,要查表,可能用汽车也可能用飞机发送,但是这个路径还是原来的路径。这里的数据也就是以太帧,Ethernet Frame,也就是深圳路由到武汉路由之间的数据包都是以太帧的形式,只是在路由器里面会形成 IP 包。
上面差不多就是网络结构的基本。可以发现平时用户接触到的都是应用层,而传输层则是电脑和交换机之间的事情,网络层则是路由器选择路由的故事,数据链路更多的是以太帧的传输。但是懂这些只能理解大致的网络流动,对理解日常网络请求却是远远不够的,没有太多实质的帮助。
路由表
为了更好的理解网络请求,应该从路由表开始处理。路由表是什么?一张存储路由信息的表,决定了分发的路径。在路由器里面就有这样一张表来决定下一跳的选择。这张表在电脑里面也有,其存储的是本地计算机可以到达的网络方位以及如何到达,在 window 系统上可以在终端 cmd 上面执行命令 route print
来查看路由表。如个人本地电脑
(这是随便截图的,但是表述更加清晰)
目标网络就是 Network Destination;网络掩码:Netmas;网关:Gateway;接口:Interface;跃点数:Metric;
目标网络指的是一个网段。上面列举出个人计算机可以访问到的网关地址,但是当有网络请求的时候,采用哪个网关地址呢?总是要选一条的,如何选择?答:通过目标网络和网络掩码来选择下一跳的网关。用目的 IP 和网络掩码 做与操作 后,得出的结果符合路由表上的某一条目标网络,就选择该条的网关作为下一次的目标网络。
网关,接口,是发送以太帧时候的服务器地址。
跃点数指的是到达目的 IP 的跳跃次数,次数越低,成本也就越低。当具有多条到达相同目的网络的路由项时,TCP/IP会选择具有更低跃点数的路由项。
例如,当你要访问 192.168.1.7 IP 的时候,该目的 IP 与路由表上面的网络掩码做与操作,发现只有第 1,3 条匹配,那采取哪一条?遵循最长原则,于是采用第 3 条的网关 192.168.1.6 作为发送地址。
可以发现网络掩码的与操作,对于我们来说其实就是取目标 IP 的前 n 位。例如第 3 条的网络掩码 255.255.255.0,就是取前 24 位,抛弃剩下的 8 位,来确定 192.168.1.0 这个网段。
确定下一跳的网关地址后,个人电脑准备发送的包,这个时候会将以太帧的头部 MAC 地址写为 192.168.1.6 的 MAC 地址。MAC 地址与 IP 地址的对应关系,在哪里找?存储在电脑的 ARP 缓存里面,通过 192.168.1.6 IP 找到对应的 MAC 地址,如果找不到则要通过 ARP 来广播查询 192.168.1.6 的 MAC 地址。至于广播如何查找该地址,还是通过上面的路由表来分发。
当以太帧转递到 192.168.1.6 的网关之后,会得到 IP 包,由 IP 层查询下一跳网关地址,依然是通过和网络掩码做与操作来匹配。然后写入下一跳的 MAC 地址,以以太帧的形式传递。通过这样的方式最终找到目的 IP 地址。
上面就是很清晰的数据链路层和网络层的切换了。网络层选择路由分发,数据链路层中写入 MAC 地址,并将过大的 IP 包分片,以以太帧的形式继续在介质中传递。
默认网关,也就是缺省网关,是路由表中的网络目标和网络掩码都为 0.0.0.0 的网关。当目标 IP 没有在路由表中找到对应的网络段,就会去到默认网关,因为任何 IP 和网络掩码 0.0.0.0 做与操作都是 0.0.0.0。这样也就不会出现有 IP 没有匹配上的情况。
如果网关是 127.0.0.1 呢?那就是本机地址,这个时候本机即是服务访问者,又是服务回答者,不同的只是端口差别。常见的如 web 开发里面,用 webpack 的 dev-server 来创建后台热更新服务时,跑路由表的时候,最后还是会由本机 dev-server 指定的某个端口应答。
内网 IP,公网 IP
经常遇到的 192.168 开头的地址,尤其是在 cmd 里面进行 ipconfig 里面查询电脑 ip 地址的时候。那这些是也是网络地址吗?不是的,这个是内网 IP。
上图是有特殊作用的 IPv4 网段,也就是在服务器地址里面,这些网段都不能公开使用的,有特殊用途,比如 192.168.0.0/16,就是前 16 位网段为 192.168 的 IP 地址都是私有地址,也就是内网 IP,外网是不能访问的。那这个内网 IP,怎么会存在呢?
服务器地址只有一个,但是连接的设备却有很多个,每个终端都需要一个 IP 地址,这个时候,就需要 网络地址转换(Network Address Translation)。地址不够就从上文提到的特殊作用的 IPv4 网段里面找,具体的是从私有 IP 里面,比如从 192.168.0.0/16 的地址池里面取一个 IP 出来,这个网段有 2^16 个主机可以分配,是远远足够的了。这样的私有 IP 段,还有 10.0.0.0 - 10.255.255.255 以及 172.16.0.0 - 172.31.255.255。路由器分配 IP 地址的时候,还会采用 DHCP 技术,即动态主机配置协议。个人电脑的 IP 地址并不是凭空生成的,只有 MAC 地址是固定的。 IP 地址是由服务器统一下发确定的。这里面就是用到了 DHCP 技术。服务器从地址池里面取一个空闲 IP 地址,以及对应的网络掩码、缺省网关、域名服务器 IP 给到客户端。
如若要访问外部服务器的某台内部地址的服务器,是不能直接访问该内部地址的,只能通过访问该服务器所在的公网 IP 地址,通过路由表到达目的路由器,再由该路由器/交换机的映射来确定访问哪台内网服务器。
对于同一网段的 IP 之间的访问,则没有路由器之间的跳来跳去。比如内部私有 IP 之间的通信的时候,由于网关是同一网关,则可以以内网 IP 来通信。
路由表添加
下面说个案例,也是最近工作中遇到的。比如你要访问公司内网里面的某台服务器。已知: VPN 地址;120.24.1.1 服务器地址: 10.24.1.1
可以看出服务器地址是内网地址,不能直接用 Xshell 访问连接,这个时候就需要连接 VPN 了,通过用户和密码来连接到 VPN,这个时候由于已经连接上 VPN,本机的默认网关会添加上该服务器的默认网关,而且该默认网关的跳跃数是要远少于之前的默认网关的。所以最后的网络服务基本都会跑到该 VPN 上面去。这个时候通过新的默认网关就能够访问该公网 IP 下的内网服务器了。
这个时候存在另外一个问题,一般公司的 VPN,也就是连上后基本都是无法访问其他网络的,只能访问内部 IP 地址,这个时候往往为了访问外网,会将该 VPN 的 IPv4 设置 “在远程网络上使用默认网关” 给去掉。
这样路由表里面的默认网关还是有两个,只是 VPN 的默认网关的优先级也就是跃点数是要高于之前的默认网关的。使得外网访问都能走原先的默认网关而不是 该 VPN 的默认网关。
这个时候就会有另外一个问题,访问内网服务器的时候,由于走的是原先默认网关,由于原先默认网关不在 VPN 下,所以导致内网服务器连接不上。为了访问内网,可以向本地的路由表添加路由,即使将该内网服务器访问的网关指向 VPN 的默认网关如:
route add 10.0.0.0 mask 255.0.0.0 VPN的默认网关 metric 1
通过该指令可以将 10.0.0.0/8 的目标 IP 下一跳跳到 VPN的默认网关上,metric 设置为 1,只是提高优先级,作用一般不大,毕竟有最长匹配原则的存在。
客户端的网络请求
上面介绍了网络层和数据链路层的过程。而传输层 TCP,则是应用层将 HTTP 格式的包丢入 SOCKET 让其传递就好了。
让我们来看看一个客户端角度的网络请求:
这里主要涉及的就是传输层和应用层,包括了重定向,浏览器缓存,DNS查询,TCP 连接等等,另外还有一个简化的图:
这里就更能直观看到了,应用层的 HTTP 与 DNS;传输层的 TCP 三次握手,发送数据,返回确认,以及四次握手关闭 TCP 进程。至于 TCP 为何要三次握手,关闭为何又是 四次握手,这个就不介绍了,太基本了,
最后的数据包如上所示。可以看到请求中 源IP/源端口,目标IP/端口,都会在数据包里面。