在浏览器上输入一个URL
后发生了什么? 这也是面试中老生常谈的话题,包括网上也有大量关于这块的内容:
从百度的搜索结果来看,能够搜到七千多万条记录,因此本篇不会再以那种前篇一律的方式赘述,而是以目前较新的网络内容,结合系统中的大部分服务,将自己类比成一个请求,切身感受到每个技术栈的具体细节,彻底从“根儿上”理解客户端请求-服务端响应的全过程。
本篇以
https://www.juejin.cn/
为例进行分析,当然,这里假设掘金后端是Java做的(实际上掘金好像是基于Node
做的后端)。
当我们在浏览器的地址栏中,输入xxx
内容后,浏览器的进程首先会判断输入的内容:
xxx
生成URL
。URL
。当然,在地址栏中输入某个内容后,也会进行一些额外操作,例如:安全检查、访问限制等,但总归而言,浏览器做的第一件工作则是生成URL
,当按下回车后,浏览器进程会将生成的完整URL
发送到网络进程:
当网络进程收到传过来的URL
后,首先并不会直接发出网络请求,而是会先查询本地缓存:
观察上述流程,当网络进程收到传来的URL
后,会首先通过URL
作为Key
,在本地缓存中进行查询:
If-Modified-Since、If-None-Match
等标识向服务器发起请求,先判断服务器中的资源是否更新过:
304
状态码,并继续读取之前的缓存内容使用。如果在本地缓存中,无法命中缓存,或者本地缓存已过期并服务器资源更新过,那么此刻网络进程才会真正向目标网站发起网络请求。
当客户端的网络进程,在查询缓存无果后,会真正开始发送网络请求,但要牢记:客户端的网络进程并非直接向目标网站发起请求的,前期还需经过一些细节处理。
当然,为了能够更直观的感受整个过程,在这里我们将自己“化身”为一个请求,站在请求的角度切身体验一段奇特的“网络旅途”。
在网络进程发起请求之前,会首先对浏览器进程传过来的URL
进行解析,一般来说完整的URL
结构如下:
但上述结构使用较少,通常情况下,浏览器会使用的URL
的常用结构如下:
URL
中每个字段的释义如下:
scheme
:表示使用的协议类型,例如http、https、ftp、chrome
等。://
:协议类型与后续描述符之间的分隔符。domainName
:网站域名,经DNS
解析后会得到具体服务器IP
。/path
:请求路径,代表客户端请求的资源所在位置,不同层级目录之间用/
区分。?query1=value
:请求参数,?
后面表示请求的参数,采用K-V
键值对形式。&query2=value
:多个请求参数,不同的参数之间用&
分割。#fragment
:表示所定位资源的一个锚点,浏览器可根据这个锚点跳转对应的资源位置。网络进程会根据URL
的结构对目标URL
进行解析,其中有两个关键信息:
http、https
,这关乎到后续默认使用的端口号。假设浏览器传输过来的URL
为https://juejin.cn/user/862486453028888/posts
,那么在这个阶段会确定后续请求的服务器端口号为443
,请求的目标域名为www.juejin.cn
。其实在这里主要是根据浏览器的输入信息,去解析出一些“诞生我(请求)”的前置要素。
在上个阶段已经大概知道“我”该去往何处啦!但我具体地址该到那里呢?“我”好像不大清楚,要不找个人问问吧^_^
。我记得好像有个叫做DNS
的“大家族”是专门负责这个的!我要去找它们问问看~
不过在问DNS
之前,我先来看看本地有没有域名与IP
的映射缓存,好像没有~,那我只能去找DNS
了(-_-)
,我首先找到了「本地DNS
大叔」,把我要查找的域名交给了它,它让我稍等片刻,它给我找一下,让我们一起来看看「本地DNS
大叔」是怎么查找的:
DNS
大叔」找了它的「根DNS
族长」,族长告诉它应该去找「顶级DNS
长老」。DNS
大叔」根据族长的示意去找了「顶级DNS
长老」,然而长老又告诉它应该去找「授权DNS
执事」。DNS
大叔」又根据长老的示意找到了「授权DNS
执事」,最终在「授权DNS
执事」那里查到了我手里域名对应着的具体IP
地址。DNS
大叔」拿着从「授权DNS
执事」那里查到的IP
,最终把它交给了我,为了下次不麻烦大叔,所以我获取了IP
后,将其缓存在了本地。呼~,我终于知道我该去哪儿啦!准备出发咯!
更为详细且专业性的查询过程请参考:《HTTP/HTTPS-DNS域名解析系统》。
问过DNS
大叔后,获得了目的地址的我,此时已经知道该去往何处啦!但在正式出发前,由于前路坎坷,途中会存在各类危机(网络阻塞、网络延迟、第三方劫持等),因此为了我的安全出行,首先还需为我建立一条安全的通道,所以我还需要等一会儿才能出发,俺们一起来瞅瞅建立安全通道的过程是什么样的:
看着好复杂啊~,但似乎大体就分为了两个过程:
首先是
TCP
的三次握手过程,听说这个阶段是为了确保目的地能够正常接收我、也是为了给我建立出一条可靠的出行通道、并且为我计算一下出行失败之后多久重新出发的时间等目的(也就是为了测试双方是否能正常通信、建立可靠连接以及预测超时时间等)。
其实按照之前的“交通规则”,在建立好TCP
连接之后,我就可以继续走下一步啦,但现在有很多坏人,在我们出行的道路上劫持我们,然后窃取、篡改俺们携带的数据,所以如今出行变得很不安全,因此还需要还需要建立一条安全的出行通道,就是TLS
大叔的安全连接~(HTTP+TLS=HTTPS
):
TLS
握手阶段,在这个阶段中,TLS
大叔为了俺的安全出行,会通过很多手段:非对称加密、对称加密、第三方授权等,先和俺的目的地交换一个密钥,然后再通过这个密钥对我加密一下,确保我被坏人抓到了也无法得到俺护送的数据^_^
!
详细且专业性的过程请参考之前的:《计网基础TCP/IP综述-TCP三次握手》、《全解HTTP/HTTPS-SLL、TLS详解》。
经历上述过程后,安全的出行道路已经建立好啦!但此刻的我还不算完整,所以需要先构建一个“身体”,也就是HTTP
请求报文:
“我的身体”主要由请求行、请求头、空行以及请求主体四部分组成,里面包含了“我本次出远门的需要护送的数据和一些其他信息”。同时,为了我能够在“出行的道路上(传输介质)”安全且正常传输,我还需要经过层层封装:
首先为了确保俺护送的数据安全,TLS
大叔会先对我的数据进行一次加密,把我原本携带的明文数据转变为看都看不懂的密文,类似下面这个样子:
经过加密后的我会紧接着来到传输层,传输层会在我的脑袋上再贴上一个传输头,如果是TCP
大哥的话,它会给我贴上一个TCP
头,但如果传输层的UDP
大哥在的话,它给我贴的就是UDP
头。但不管是谁贴的,在这个传输头内,为了防止我迷路和走丢,TCP、UDP
两位大哥哥都会细心的在里面写清楚“我来自哪里,该去往何处”,也就是源地址和目的地址:
偷偷吐槽一句:
TCP
大哥贴的传输头里面,放了好多好多东西,让我感觉脑袋沉沉的。
过了传输层这一站之后,我又来到了网络层,果不其然,网络层里面最常见的还是IP
大叔,IP
大叔看到我之后,又在我的脑袋上贴上了一个网络头,也就是给我又加了一个IP
头。
哒哒哒~,我出了网络层这关之后,又来到了数据链路层,这关则是由大名鼎鼎的“以太网家族”驻守,在这里我和之前两关不同,除开在我脑袋上贴了一个链路头之外,还给我在尾巴上多加了一个链路尾。
不过刚刚出链路层的时候,好像有个人跟我说:你这个样子是无法在介质上行走的,你要记得改变一下啊!
我还没听的太清楚,就来到了物理层这关,这层和之前我“家里”以及之前的关卡环境都不一样,物理层的小伙伴们好像都有实际的形态,但之前接触所有内容都是虚拟的概念形态哎~。
在我对比物理层大哥们的异样差距时,一不愣神发现我的身体好像发生了“翻天覆地”的变化,整个我似乎都变为了0、1
构成了,正当纳闷时,物理层的某个大哥哥告诉我说:“只有变成这样子,你才可以在出行的道路上行走哈,所以我们给你转换了一下形态,你现在已经可以出发了”。
原来是这样呀,好像链路层的时候有人跟我说过哎~
同样对于更为专业、详细的过程可参考之前的:《HTTP/HTTPS-HTTP报文组成》、《计网基础之TCP/IP-网络分层模型》等内容。
GO~GO~GO~
,终于出发啦!我终于踏上了网络之旅!呼呼呼~
咔!我来到了第一个中转站,听别人说,好像它的名字叫做路由器,首先路由器大哥把我的身体按照之前封装的步骤层层解封了,但解封到传输层的时候,看到了我脑袋上的传输头,似乎路由器大哥发现了TCP
哥哥写的目的地址,发现我的目的地还在更远的位置,然后路由器大哥又按照原本的步骤把我的身体封装回去了,然后还亲切的给我指出了接下来该往那条路走,我又该继续前行啦....
我一边走着,一边在思考:好像路由器大哥就是负责给俺们指路的,防止俺们走丢~
具体可参考:《TCP/IP-IP寻址与路由控制》
啊!路途好遥远呀,我一路走了很久很久,也遇到了很多很多的中转站,每次当我不知道怎么走时,路由器大哥都会温馨的给我指出接下来该走的路途。期间我也走过很多很多路,曾踩着双绞铜线、同轴电缆、光纤前行,当然,可不要小看俺,就算没有物理连接的情况下,我也可以通过无线电技术,通过空气前行呢!
再次声明,文中所谓的道路,就是指数据传输的介质。
走着走着,突然前方遇到一个叫做CDN
的老爷爷,它问我说要去哪里,我说要去xx
地方办事,和蔼的CDN
老爷爷跟我说,我来看看我这里有没有你要的东西,如果有的话,就不用麻烦你这个小家伙一直跑下去了。可是很遗憾,CDN
老爷爷说它哪儿没有我要的东西,因此我只能继续前行下去。
记不清过了多久,一路跌跌撞撞,在迷迷糊糊中我来到了一个地方,但当我还在分辨时,刷的一下,很快啊,我就被丢到了其他地方,当我回头看的时候,发现刚刚哪个地方,大写着LVS。
LVS
一般会作为大型网站的网关接入层,负责提供更高的并发性能,具体可参考《亿级流量架构设计-LVS篇》。
再直视前方,前方有一个东西很眼熟,难道这就是当初听说过的服务器吗?带着一脸疑惑的我慢慢走了进去,我发现内部空间很大,上面漂浮着一块大陆,名为Linux
大陆,上面有好多好多的“城市(进程)”林立着,那我该去哪一座呢?让我想想!
对了,记起来了好像!!当时出门的时候有人跟我说过:如果你到了目的地之后,不知道该找谁,那么可以根据默认的编号(端口号)去找!
让我回想一下,HTTP
的默认端口是80
,HTTPS
的默认端口是443
,我目前属于HTTPS
派别的请求,那么我应该去找编号为443
的城市!出发出发~
顺着我的推理,我来到了编号443
城市的城门口,当我迈进城门后,嗖的一下,我被一个叫做Nginx
的大叔抓了过去....
IP:443
的地方办事!443
编号的守门将。话音刚落,Nginx
三下五除二的就把我的身体拆开了,然后得到了HTTP
报文,然后从HTTP
报文的请求行中,发现了我本次旅途的具体目标:/user/862486453028888/posts
,然后Nginx
大叔又把我组装了回去,然后根据它内部配置的规则,然后道:
xxx.xxx.xxx.xxx:xx
,快去吧。IP:443/user/....
,根据目前的规则以及我代理的地址,你就应该去这里!这里的规则是什么呢?其实就是
Nginx
的location
路由匹配规则、upstream
代理集群列表以及负载均衡算法,具体可参考:《Nginx篇:反向代理与负载均衡》、《负载均衡算法原理篇》。
顺着Nginx
大叔给的地址,我又来到了另外一台服务器,上面同样有一块Linux
大陆,然后根据地址在上面找到了一个名为Gateway
的东东,听它自己介绍,好像属于系统网关。但当我找它办事时,它却跟我说:“我不负责具体的业务处理,根据你的目标/user/....
,你应该去找Nacos
注册局,问它们要一下USER-SERVICE
的具体地址,所以,小家伙你还得继续奔波哦”!
好的好的,感谢
Gateway
叔叔指路,那我现在就去啦!
哒哒哒~,迈着愉快的步伐我来到了Nacos
注册局,然后将Gateway
叔叔给我的名字:USER-SERVICE
交给了它们的工作人员,它们的工作人员经过一番查询之后告诉我,这个“品牌”多有个分部,你可以去其中任意一处分部处理你的任务,你可以去:xxx.xxx.xxx.xxx:8080
这个地址噢!
这里的“品牌”是指后端的具体服务,分部是指服务集群中的每个节点。
好的好的,那我就去你说的这个xxx.xxx.xxx.xxx:8080
地址啦!
我一边在路上走着,一边想了一下刚刚过程发生的事情,然后把这个经历画成了一副逻辑图,如下:
回去的时候我一定要跟小伙伴们分享一下这个有趣的经历,耶!
根据Nacos
给我的地址,我又来到了一台新的服务器面前,我记得Nacos
给了我一个端口号,要我来到这里之后找编号为8080
的位置,我顺着这个编号慢慢找着,突然在我的前方,出现了一只大老虎,哦不,应该是一只大猫咪,它长这个样子:
它的长相似乎有些报看,但在它的脑门上正好写着我要找到8080
地址,那我要找的应该就是它了吧!终于到了!我慢慢靠近了这只大猫咪,然后跟它说要找它办事,Tomcat
说要看看我的数据,然后又把我的身体按照之前封装的方式逆向拆开了,从而还原了我最初的身体-HTTP
请求报文,最后Tomcat
说:“我确实是你本次要找的最终目标,不过要办你这件事情得到我肚子里面去噢”!
说罢,
Tomcat
张开了它的血盆大口,一口将我吞了下去.....,正当我以为我完蛋的时候,我却发现Tomcat
内部别有乾坤,上面似乎也有一块小陆地漂浮着,当我凑近的时候才看清楚,原来上面写的是JVM
呀!
我二话不说,一脚踏上了这块陆地,正当我看着上面密密麻麻的“屋子(Java
方法)”迷茫时,此时我正前方就走来了一个人,然后对我做了一个自我介绍:
来自远方的尊敬客人,您好呀,欢迎光临
JVM
神州,我叫Thread-xxx
,是线程家族的一员,您接下来的整个旅途,我终将陪伴在您左右,您需要办的所有事情,都会由我代劳,客官这边请(45
度鞠身)~
然后我一边走着,一边跟Thread-xxx
聊着:
/user/....
,原来您是要去这里呀,这边请~。DispatcherServlet
办事处,才能继续前行。PS:接下来是讲述
Java-SpringMVC
框架的执行过程,非Java开发可忽略细节。
随着Thread-xxx
的步伐,我们找到了线程口中所说的DispatcherServlet
办事处,该办事处的工作人员首先看了一下我本次的具体目的地(资源地址),然后说:您需要先去问一下HandlerMapping
管理局,让它给你找一下具体负责这块业务的工作室。
紧接着线程
Thread-xxx
又带我来到了HandlerMapping
管理局找到了其中的管理人员,该管理人员让我先把要找的资源位置给它,然后只见它拿着我的目标地址作为条件,然后输入进了查询器,一瞬间便查出来了我本次的最终目的地:UserController
工作室!
线程Thread-xxx
道:这就是负责您本次任务的最终工作室啦!我这就带您过去。
这其实本质上就是
SpringMVC
中,请求定位具体Java
方法的逻辑,但由于之前没出过《SpringMVC
的原理篇》,因此接下来从专业性的角度简单叙述一下SpringMVC
的核心原理。
先上一张SpringMVC
的原理图:
观察如上流程图,其实看起来难免有些生涩,那此刻咱们换成简单一点的方式叙述,不再通过这种源码性的流程去理解。
不知诸位是否还记得,最开始学习SpringMVC
时的配置过程,接下来我们简单回忆一下:
①配置
springmvc-servlet.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 通过context:component-scan元素扫描指定包下的控制器-->
<!-- 扫描com.xxx.xxx及子子孙孙包下的控制器(扫描范围过大,耗时)-->
<context:component-scan base-package="com.xxx.xxx"/>
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- viewClass需要在pom中引入两个包:standard.jar and jstl.jar -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
在这第一步中,最重要的就是配置一下扫描包的位置,以及配置一下视图解析器。
②配置
web.xml
:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- Spring MVC servlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<!--配置一下DispatcherServlet的位置-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定springMVC的初始化文件位置,默认值为:/WEB-INF/springmvc-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!--web.xml 3.0的新特性,是否支持异步-->
<!--<async-supported>true</async-supported>-->
</servlet>
<!--关键!!!配置一条请求路径映射,"/"代表匹配所有路径的请求-->
<!--也就是当有请求到来时,都会被进入前面servlet-name=SpringMVC的servlet中-->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在第二步中,主要会配置一条请求路径的映射位置,将进入WEB
程序的所有请求全部转入DispatcherServlet
的doGet、doPost
方法中。
同时由于web.xml
中配置了一个servlet:DispatcherServlet
,所以在程序启动时,首先会加载DispatcherServlet
,加载时会执行初始化操作,会调用initStrategies()
方法:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
重点看其中的第四个初始化操作:调用initHandlerMappings()
方法,由于之前在web.xml
中指定了初始化文件的位置:/WEB-INF/springmvc-servlet.xml
,那么紧接着SpringMVC
会去读取该配置文件中的base-package
扫描包路径,然后会开始扫描这个包路径下的所有类:
@Controller
注解的类。@RequestMapping
注解的方法。Key
,然后通过反射机制,将方法变为一个Method
对象,封装成InvocationHandler
实例作为Value
,一起加入到一个大的Map
容器中。上述流程下来大致诸位有些晕乎乎的,那么简单举个例子:
@Controller("/user")
public class UserController {
@RequestMapping("/get")
public String get(User user) {
......
}
@RequestMapping("/add")
public String add(User user) {
......
}
}
上述这个案例中,最终在初始化之后,会被以下面这种形式加入Map
容器:
// 这里是伪代码,主要是为了阐述逻辑
Map<String,InvocationHandler> map = new HashMap<>();
// 后面的UserController#get()就是以反射获取到的Method方法实例
map.put("/user/get",InvocationHandler(UserController#get()));
map.put("/user/add",InvocationHandler(UserController#add()));
最终当请求到来时,由于之前web.xml
中配置了一条/
匹配规则,所有的请求都会被转入到DispatcherServlet
的doGet、doPost
中,在该方法内首先会以HTTP
请求报文-请求行中的资源路径作为Key
,然后在这个Map
容器里面进行匹配,从而定位到具体的Java
方法并执行。
OK,最后在简单的把完整流程叙述一遍:
JavaWeb
程序打成war
包丢入Tomcat
并启动时,Tomcat
就会先去加载web.xml
文件。web.xml
配置文件时,会碰到DispacherServlet
需要被加载。DispacherServlet
时,其实就是把SpringMVC
的组件初始化,以及将所有Controller
中的URL
资源全部映射到容器中存储。Tomcat
经过DispacherServlet
时,DispacherServlet
就去容器中找到这个请求的URL
资源。Java
方法后,会调用组件通过反射机制去执行具体的Controller
方法。DispacherServlet
,此时DispacherServlet
又会去调用相关组件处理执行后的结果。OK~,话接前文,前面经过
HandlerMapping
管理局的管理人员查询后,我们已经找到了本次任务处理的具体工作室了...
随着线程的工作开始,我们一路走过了service
层、dao/mapper
层,在service
层办事时,我们遇到了强大的Redis
哥哥,Redis
哥哥看到我们之后,问清楚了我们本次到来的目的,然后它说:“来自远方的贵客,请稍等,让我先看看我这里有没有您需要的东西!”
这个场景似曾相识哎,我记得来的路上也有个
CDN
老爷爷跟我说过同样的话~
MyBatis
哪小子,它也许能够帮到您。根据Redis
的指示,线程Thread-xxx
领着我最终见到了MyBatis
,它长这个样子:
原来
Redis
哥哥口中的MyBatis
竟然是个鸟叔叔[吐舌~]
MyBatis
简单看了一下我本次的任务:
SQL
代码,是你您次任务的必须之物。Thread-xxx
去带您找一下JDBC
哪个老家伙。慢慢的,线程又带我找到了“鸟叔”口中所说的JDBC
老爷爷,JDBC
老爷爷见到我的到来,眼神中并没有丝毫的意外之情,似乎早已经习以为然,只见JDBC
老爷爷抬起消瘦的右手,指着一个地址:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/db_xxxxx
然后道:“小家伙,你又需要再跑一段远路咯,而且只能你去,Thread-xxx
只能在这里等你”。
我:好吧好吧,那我去啦!
又是孤身一人的旅途,难免有些孤独感袭来,但还好我早已习惯啦!随着一路奔波,我来到了JDBC
老爷爷给出的地址,这里同样是位于另外一台服务器的Linux
大陆上,我通过3306
这个编号找到了一座叫做MySQL
的城池,当我踏入之后发现,与之前踏上JVM
神州相同,在我刚踏入MySQL
这座大城的时候,有一个自称为DB
连接家族的弟子接待了我。
JVM
神州上那位JDBC
前辈介绍过来办事的,对吗?SQL
给我噢。这里不再展开叙述
SQL
的执行细节,因为MySQL
也是一门较庞大的内容,在开设的《全解MySQL数据库》专栏中,之后会出一篇:《一条SQL具体是如何执行的?》文章去详细阐述。
正当我吃完一块西瓜、喝完一瓶冰阔乐时,DB
连接家族的哪位弟子便回来了,同时怀里抱着一大堆东西(数据),然后丢给了我,道:“这便是您本次需要的数据啦,您本次的任务我都按照清单(SQL
)上的记录,给您一一处理了噢”。
我:好的,万分感谢,那我走啦!
顺着来时的原路,我飞速的赶回了JVM
神州所在的位置,然后映入眼帘的第一眼就是:Thread-xxx
哪个家伙在原地站着,老老实实的等候着我的回归,我悄悄的绕到了Thread-xxx
身后,然后从背后拍了一巴掌:
一路跟随着Thread-xxx
的脚步,兜兜转转的我们最终又回到了DispatcherServlet
办事处,经过它们内部人员的一顿操作之后,我就打算返航啦!一路走走停停,我走到了JVM
神州的边缘。
Java
线程家族的规则,正常情况下我是不能踏出JVM
神州的。Thread-xxx
~,我会记得你的。我告别了Thread-xxx
,也从此离开了JVM
神州,最终我从Tomcat
这只大猫咪的口中飞了出来,正式踏上了归途。
诸多经历过后,现在的我携带着本次任务的结果踏上了回家之路,首先我又路过了Gateway
叔叔那里,然后我又回到了Nginx
大叔所在的城池,不过Nginx
大叔把我的身体改为了应答报文结构,并且往其中还写入了一些东西,听说是让我回去交给浏览器老大的。
然而在我返航之前,似乎这边也有加密层、传输层、网络层、链路层、物理层这些关卡,和我当时出发的过程一样,我身上被一层一层的贴了很多东西,并且最终也被改为了0、1
组成的身体结构,这个过程是多么的熟悉呐!
我又踏上了哪不知有多遥远的路途,与来时的路一样,其中也遇到了很多中转站,也走过各种各样的道路,当然,为了防止我迷路,在
Nginx
大叔那里,也在我的脑袋上贴了一个TCP
头,里面写清楚了我来自那里,该去向何方.....
在迷迷糊糊中不断前行,终于看到了我的出生地,看到了网络进程和浏览器老大~,哦豁!我回来啦!
在进入家门之前,我又会经历物理层、链路层、网络层、传输层、TLS
层依次解封的过程,主要是为了将我从后端带回来的数据解析出来。网络进程在解析到数据后,我的使命就此完成啦!紧接着网络进程会将数据交给浏览器老大,然后老大会派遣一个小弟(渲染进程)对数据进行处理,我瞅了几眼,大体过程是这样的:
HTML、CSS
数据生成DOM
结构树和CSS
规则树。最后,因为我至此已经正常返航了,所以为了节省资源开销,会将我出发前构建的安全通道(TCP、TLS
连接)关闭,这个过程会由TCP
大哥去经过四次挥手完成,如下:
具体过程可参考:《计网基础与TCP/IP-TCP四次挥手》
综上所述,用户在浏览器地址栏输入内容后,我们站在一个“网络请求”的角度,切身感受了一场奇妙的网络之旅,从客户端发送请求到服务端返回响应,整个流程咱们都“亲身”体验了一回,最后写个流程总结:
URL
并传给网络进程。URL
并向DNS
发送请求,得到IP
。TCP、TLS
多次握手,建立TCP、TLS
安全连接。0、1
格式。CDN
查询是否有缓存的内容,如果没有则继续向下请求。LVS
后被转发到Nginx
,再由Nginx
转发到Gateway
网关。Gateway
网关根据配置好的API
分发规则,将请求分发到具体服务。Nacos
注册中心内,查询出该服务的具体服务实例IP
。WEB
服务进程Tomcat
。Tomcat
基于SpringMVC
的工作流程为请求定位到具体的Java
后端方法。Java
方法时,先去Redis
中查询是否有数据,没有则查询MySQL
。DB
前先通过MyBatis
生成SQL
语句,然后再通过DB
连接执行SQL
。MySQL
并执行SQL
语句,从而获得数据。TCP
四次挥手,断开连接。至此整个流程结束,当然,这个过程中并未涉及到太多的技术栈,也包括对于整个前/后端系统内部的执行细节并未阐述,这是由于整个系统的全细节执行流程较为庞大,展开叙述之后难以收尾,因而在本篇中则抓住核心点去叙说。
最后,对于请求执行的完整经历,也画成了一副流程图,但由于文件过大会失真,因而可点击链接在线访问:《浏览器输入URL后究竟发生了什么?》