浏览器输入url后发生了什么
从url到ip地址
dns解析
- 浏览器检查域名是否在缓存当中
- 如果缓存中没有,就去调用 gethostbyname 库函数进行查询。
- gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里
- 没有缓存,也没有在
hosts
里找到,则将会向 DNS 服务器发送一条 DNS 查询请求(UDP,53端口) - 查询本地 DNS 服务器
- 如果 DNS 服务器和我们的主机在同一个子网内,系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询
- 如果 DNS 服务器和我们的主机在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询
ARP
- 首先查询 ARP 缓存,如果缓存命中,我们返回结果:目标 IP = MAC,如果缓存没有命中:
- 向本网段的所有主机发送ARP数据包
- 当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
- 源主机收到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地址,并根据分组转发协议进行查找:
分组转发协议
- 从数据报的首部提取ip地址D,得出目的网络地址为N
- 若N就是直接相连的某个主机,直接交付
- 若路由表中有目的地址为D的特定主机路由,则把数据报传送给路由表中的下一跳路由器
- 若路由器中有到达目的网络N的路由,则把数据报传送给路由表中所指明的下一跳路由器
- 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的下一跳路由器
- 使用ICMP差错报告报文报错
通过分组转发协议,得到相应的路由器或主机ip后,不是填入ip数据报,而是进行ARP将该ip地址转化为物理地址。之后将物理地址包入以太网帧,转成比特流之后继续发送。
在路由器之间移动的过程中可能会经过一些AS,顺便一提AS的路由选择协议有RIP(UDP)和OSPF(IP),AS间是BGP(TCP)
从网线到Socket
- 调制解调器把模拟信号转换回数字信号
- 经过网卡拆解成IP packet存入网卡的缓冲区队列
- 之后发出中断,CPU保存运行现场后响应中断,运行网卡中断程序(这里以epoll为例)(这里已经变成TCP segment了,之后epoll流程处理的是TCP segment,具体怎么变成TCP segment的我目前还不清楚,有知道的请告诉我一声):
- 添加socket,并加入到eventpoll的等待队列中(第一次发起才有添加socket的操作),将网卡的数据写入到对应 socket 的接收缓冲区里面;
- 修改 rdlist,并唤醒 eventpoll 等待队列中的进程对socket进行处理
(epoll的具体流程可以看这里epoll的实现原理)
这一部分将会在后面不停进行以传输TCP segment
从socket到http或https
这里你的http服务器(可以是nginx也可以是tomcat)就会开始接受socket连接(也就是tcp连接,socket是对tcp和udp的封装),这里如果是tomcat,源码中会有 .accept 和 .register 的调用,在经过三次握手之后
如果你是采用https,则会创建ssl连接:
- 客户端通过发送 Client Hello 报文开始 SSL通信。报文中包含客户端支持的 SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
- 服务器可进行 SSL通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
- 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。(这里的证书是怎么来的,如何验证的我们一会再说,我们只要知道它包含服务器的公钥就够了)
- 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL握手协商部分结束。
- SSL第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该随机密码串已用步骤 3 中的公开密钥进行加密。
- 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
- 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
- 服务器用自己的私钥解开步骤5中的报文,得到随机密码串。服务器同样发送 Change Cipher Spec 报文。
- 服务器同样发送 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(顺便佩服找不到实习还花了几天水博客的自己)