浏览器输入url后发生了什么

从url到ip地址

 

dns解析

  1. 浏览器检域名是否在缓存当中
  2. 如果缓存中没有,就去调用 gethostbyname 库函数进行查询。
  3.  gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里
  4. 没有缓存,也没有在 hosts 里找到,则将会向 DNS 服务器发送一条 DNS 查询请求(UDP,53端口)
  5. 查询本地 DNS 服务器
  6. 如果 DNS 服务器和我们的主机在同一个子网内,系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询
  7. 如果 DNS 服务器和我们的主机在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询

 

ARP

  1. 首先查询 ARP 缓存,如果缓存命中,我们返回结果:目标 IP = MAC,如果缓存没有命中:
  2. 向本网段的所有主机发送ARP数据包
  3. 当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
  4. 源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
    广播发送ARP请求,单播发送ARP响应

 

递归查询迭代查询

之后从DNS服务器中获得域名对应的ip地址

 


 

 从tcp数据报到比特流

当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数socket ,请求一个 TCP流套接字,对应的参数是  AF_INET/AF_INET6  和  SOCK_STREAM  。

操作系统的任务:

  • 这个请求首先被交给传输层,在传输层请求被封装成 TCP segment。目标端口会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range)
  • TCP segment 被送往网络层,网络层会在其中再加入 IP 头部(可能会切片),里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成IP packet

集成网卡的任务(实现以太网协议,负责组装成帧、串行/并行转换、缓存数据:由于网络上的数据率和计算机总线上的数据率并不相同,因此在网卡中必须装有对数据进行缓存的存储芯片):

  • 这个 IP packet 接下来会进入链路层,链路层会在封包中加入 frame 头部,也就是封成以太网帧。里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的 MAC 地址。像前面说的一样,如果内核不知道网关的 MAC 地址,它必须进行 ARP 广播来查询其地址。
  • 集成网卡将以太网帧编码成适合在线路上进行传输的物理信号(比特流),并将比特流从网络接口中发出。

再通过调制解调器把数字信号转换成模拟信号从网线发出

 


 

从路由器到路由器

如上图,比特流在路由器中剖成ip数据报之后再提取出目标ip地址,并根据分组转发协议进行查找:

 

分组转发协议

  1. 从数据报的首部提取ip地址D,得出目的网络地址为N
  2. 若N就是直接相连的某个主机,直接交付
  3. 若路由表中有目的地址为D的特定主机路由,则把数据报传送给路由表中的下一跳路由器
  4. 若路由器中有到达目的网络N的路由,则把数据报传送给路由表中所指明的下一跳路由器
  5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的下一跳路由器
  6. 使用ICMP差错报告报文报错

 

通过分组转发协议,得到相应的路由器或主机ip后,不是填入ip数据报,而是进行ARP将该ip地址转化为物理地址。之后将物理地址包入以太网帧,转成比特流之后继续发送。

在路由器之间移动的过程中可能会经过一些AS,顺便一提AS的路由选择协议有RIP(UDP)和OSPF(IP),AS间是BGP(TCP)

 


 

从网线到Socket

  1. 调制解调器把模拟信号转换回数字信号
  2. 经过网卡拆解成IP packet存入网卡的缓冲区队列
  3. 之后发出中断,CPU保存运行现场后响应中断,运行网卡中断程序(这里以epoll为例)(这里已经变成TCP segment了,之后epoll流程处理的是TCP segment,具体怎么变成TCP segment的我目前还不清楚,有知道的请告诉我一声):
  4. 添加socket,并加入到eventpoll的等待队列中(第一次发起才有添加socket的操作),将网卡的数据写入到对应 socket 的接收缓冲区里面;
  5. 修改 rdlist,并唤醒 eventpoll 等待队列中的进程对socket进行处理

(epoll的具体流程可以看这里epoll的实现原理

 

这一部分将会在后面不停进行以传输TCP segment 

 


 

从socket到http或https

 这里你的http服务器(可以是nginx也可以是tomcat)就会开始接受socket连接(也就是tcp连接,socket是对tcp和udp的封装),这里如果是tomcat,源码中会有 .accept 和 .register 的调用,在经过三次握手之后

 如果你是采用https,则会创建ssl连接:

  1. 客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
  2. 服务器可进行 SSL通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
  3. 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。(这里的证书是怎么来的,如何验证的我们一会再说,我们只要知道它包含服务器的公钥就够了)
  4. 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL握手协商部分结束
  5. SSL第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该随机密码串已用步骤 3 中的公开密钥进行加密。
  6. 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
  7. 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
  8. 服务器用自己的私钥解开步骤5中的报文,得到随机密码串。服务器同样发送 Change Cipher Spec 报文。
  9. 服务器同样发送 Finished 报文。

连接完成之后继续接收从socket拿到的数据,如果是https,后续的数据都要进行简单的解密(加密方式是使用前面获得的随机密码串作为密码参数进行对称加密

之后就是http服务器对传来的数据进行封装,封装成http请求类等。

 

总结一下,这里这些操作包括ssl连接主要是http服务器对socket的调用(如Java写的Tomcat调用的accept、register、select等,为NIO部分的知识),并封装http或https对象,感兴趣可以看一下我的这篇源码解析:jdk下httpserver源码解析,https部分详情请见:Https原理

 


 

从http到servlet

之后就是容器的各种封装了,下图是Tomcat的架构图,这里会送到最右边的Container封装成servlet。这里本来可以写不少东西,不过我没研究过,就不多说了。

前面的Connector部分的解析的话可以看这里:Tomcat中对NIO的应用

 

 


 

从servlet到springMVC框架

之后就是SpringMVC对Servlet的封装了,具体就不细说了,之后就是常见的SpringMVC的流程了:

 

至于返回到浏览器的流程就大同小异了

本文是我当前水平对这个问题所能做到的最详细的解答了,后续如果有更加深入(例如我一直没看的linux内核)的理解的话再更新吧。

 

因为我是后端的,就不提浏览器解析部分了,感兴趣的可以看这里:What-happens-when,里面还有从键盘按键中断开始聊起的,还蛮有意思的。

 

最后惯例附一图:太棒了,我逐渐理解一切.jpg(顺便佩服找不到实习还花了几天水博客的自己)

 

Tags: