客户端和服务器端交互及性能优化

3,351 阅读19分钟

前言

文章涉及的内容可能不全面,但量很多,需要慢慢看。我花了很长的时间整理,用心分享心得,希望对大家有所帮助。但是难免会有打字的错误或理解的错误点,希望发现的可以邮箱告诉我1163675970@qq.com,我会及时的进行修改,只希望对你有所帮助,谢谢。

客户端和服务端:

    客户端: 可以向服务器发请求,并接收返回的内容进行处理客户端请求
    
    服务器端: 能够接受客户端请求,并且把相关的资源信息返回给客户端的

客户端与服务端

面试题:当用户在地址栏中输入网址,到最后看到页面,中间都经历了什么?

1. URL 地址解析 然后浏览器向  -> 你要通过那个地址进行访问

2. DNS域名解析 -> DNS服务器发送请求  ==》request请求阶段《==

3. 和服务器建立 TCP 连接 -> DNS服务器域名解析后向服务器发送请求

4. 把客户端信息传递给服务器(发送HTTP请求)

5. 服务器得到并处理请求(HTTP响应内容) ==》response响应阶段《==

6. 客户端渲染服务器返回的内容  

7. 和服务器端断开 TCP 连接

步骤详细化--URL 地址解析

  1. URL 地址解析

概念

​ URN (Uniform Resource Name : 统一资源名称): 一般指国际上通用的标准的名字(例如:国际统一发版的编号) ​ URL(Uniform Resource Locator : 统一资源定位符): 根据地址能够找到对应的资源

​ URI (Uniform Resource Identifier : 统一资源标志符): URL和URN 是URI的子集-> 一般写URI 也是找的 URL

一个完整的URL所包含的内容 ↓↓↓

http://www.zhufengpeixun.cn:80/stu/index.html?from=wx&1x=1#teacher      拆解如下
协议 http://

传输协议就是能够把客户端和服务器端通信的信息,进行传输的工具(类似于《快递小哥》 ---> 没有别的意思 比喻)

  • http 超文本传输协议,除了传递文本,还可以传递媒体资源文件(或者流文件)及XML格式数据
  • https 更加安全的HTTP, 一般涉及支付的网站都采用HTTPS协议(S:SSL 加密传输过程)
  • ftp 文件传输协议 (一般应用于把本地的大资源文件传到服务器端)
    • ftp上传工具: FileZilla 通过这个工具,通过ftp传输协议,我们可以把本地的文件上传到服务器上
域名(www.zhufengpeixun.cn)

一个让用户方便记忆的名字,不通过域名,直接用服务器的外网IP也能访问到服务器,但是外网IP很难被记住 例如: 125.39.174.202

我们用qq 举例
-> 顶级域名 qq.com  -> 购买域名买顶级 剩下的都是自己分的
-> 一级域名 www.qq.com
-> 二级域名 sports.qq.com
-> 三级域名 kbs.sports.qq.com

-> 详情域名后缀
    -> .com 国际域名
    -> .cn 中文域名
    -> .com.cn
    -> .edu 教育
    -> .gov 国家政府
    -> .io 博客
    -> .org 官方组织
    -> .net 系统类

 给服务器通网后,会有两个IP地址
    内网IP :局域网内访问
    外网IP :外部用户可以基于外网IP访问到服务器
     例如: 125.39.174.202
端口号

端口号(:80): 端口号的取值范围 0-65535 ,用端口号来区分同一台服务器的不同项目

  • http默认端口号:80
  • https默认端口号:443
  • ftp默认端口号:21
  • 如果项目采用的就是默认端口号,我们在书写地址的时候,不用加端口号,浏览器在发送请求的时候会帮我们默认给加上

服务器接收到请求后 ​ 1) 根据端口号找到对应的项目 ​ 2) 根据请求资源的路径名称找到资源文件 ​ 3) 读取资源文件中的内容 ​ 4) 把内容返回

请求资源路径名称(/stu/index.html)
  • 默认的路径或者名称 (xxx.com/stu/ 不指定资源名,服务器会找默认的资源,一般默认资源名是default.html、index.html...当然这些可以在服务器端自己配置)
  • 注意伪URL地址的处理(URL重写技术是为了增加SEO搜索引擎优化的,动态的网址一般不能被搜索引擎收录,所以我们要把动态网址静态化,此时需要的是重写URL)
  • item.jd.hk/2688449.htm… => item.jd.hk/index.php?i…
问号传参信息(?from=wx&1x=1)
  • 客户端想把信息传递给服务器,有很多的方式
    • URL地址问号传参
    • 请求报文传输(请求头和请求主体)
  • 也可以不同页面之间的信息交互,例如:从列表到详情
HASH值(#teacher)
  • 也能充当信息传输的方式
  • 锚点定位
  • 基于HASH实现路由管控(不同的HASH值,展示不同的组件和模块)

-步骤详细化-DNS服务器域名解析

.DNS服务器

域名解析服务器, 在服务器上存储着域名<=>服务器外网IP 的相关记录而我们发生请求时候所谓的DNS解析,其实就是根据域名,在DNS服务器上查找到对应服务器的外网IP

网站性能优化-DNS优化
  • DNS缓存(一般浏览器会在第一次解析后,默认建立缓存,时间很短,只有一分钟左右)
  • 减少DNS解析次数(一个网站中我们需要发送请求的域名和服务器尽可能少即可)
  • DNS预获取(dns-prefetch):在页面加载开始的时候,就把当前页面中需要访问其他域名(服务器)的信息进行提前DNS解析,以后加载到具体内容部分可以不用解析了
<head>
    <meta charset="UTF-8">
    <!--DNS预获取:-->
    <meta http-equiv="x-dns-prefetch-control" content="on">
    <link rel="dns-prefetch" href="//static.360buyimg.com">
    <link rel="dns-prefetch" href="//misc.360buyimg.com">
    <link rel="dns-prefetch" href="//img10.360buyimg.com">
    <link rel="dns-prefetch" href="//img11.360buyimg.com">
    <link rel="dns-prefetch" href="//img12.360buyimg.com">
    <title>客户端和服务端</title>
</head>

和服务器建立 TCP 连接(三次握手)

发生在信息传输之前

  • 第一次握手:由浏览器发起,告诉服务器我要发送请求了

  • 第二次握手:由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧

  • 第三次握手:由浏览器发送,告诉服务器,我马上就发了,准备接受吧

TCP 三次握手

TCP 三次握手就好比两个人在街上隔着50米看见了对方,但是因为雾霾等原因不能100%确认,所以要通过招手的方式相互确定对方是否认识自己。

img

张三首先向李四招手(syn),李四看到张三向自己招手后,向对方点了点头挤出了一个微笑(ack)。张三看到李四微笑后确认了李四成功辨认出了自己(进入estalished状态)。

但是李四还有点狐疑,向四周看了一看,有没有可能张三是在看别人呢,他也需要确认一下。所以李四也向张三招了招手(syn),张三看到李四向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑(ack),李四看到对方的微笑后确认了张三就是在向自己打招呼(进入established状态)。

于是两人加快步伐,走到了一起,相互拥抱。

img

我们看到这个过程中一共是四个动作,张三招手--李四点头微笑--李四招手--张三点头微笑。其中李四连续进行了2个动作,先是点头微笑(回复对方),然后再次招手(寻求确认),实际上可以将这两个动作合一,招手的同时点头和微笑(syn+ack)。于是四个动作就简化成了三个动作,张三招手--李四点头微笑并招手--张三点头微笑。这就是三次握手的本质,中间的一次动作是两个动作的合并。

我们看到有两个中间状态,syn_sentsyn_rcvd,这两个状态叫着「半打开」状态,就是向对方招手了,但是还没来得及看到对方的点头微笑。syn_sent是主动打开方的「半打开」状态,syn_rcvd是被动打开方的「半打开」状态。客户端是主动打开方,服务器是被动打开方。

  • syn_sent: syn package has been sent
  • syn_rcvd: syn package has been received

TCP 数据传输

TCP 数据传输就是两个人隔空对话,差了一点距离,所以需要对方反复确认听见了自己的话。

img

张三喊了一句话(data),李四听见了之后要向张三回复自己听见了(ack)。

如果张三喊了一句,半天没听到李四回复,张三就认为自己的话被大风吹走了,李四没听见,所以需要重新喊话,这就是tcp重传。

也有可能是李四听到了张三的话,但是李四向张三的回复被大风吹走了,以至于张三没听见李四的回复。张三并不能判断究竟是自己的话被大风吹走了还是李四的回复被大风吹走了,张三也不用管,重传一下就是。

既然会重传,李四就有可能同一句话听见了两次,这就是「去重」。「重传」和「去重」工作操作系统的网络内核模块都已经帮我们处理好了,用户层是不用关心的。

img

张三可以向李四喊话,同样李四也可以向张三喊话,因为tcp链接是「双工的」,双方都可以主动发起数据传输。不过无论是哪方喊话,都需要收到对方的确认才能认为对方收到了自己的喊话。

张三可能是个高射炮,一说连说了八句话,这时候李四可以不用一句一句回复,而是连续听了这八句话之后,一起向对方回复说前面你说的八句话我都听见了,这就是批量ack。但是张三也不能一次性说了太多话,李四的脑子短时间可能无法消化太多,两人之间需要有协商好的合适的发送和接受速率,这个就是「TCP窗口大小」。

网络环境的数据交互同人类之间的对话还要复杂一些,它存在数据包乱序的现象。同一个来源发出来的不同数据包在「网际路由」上可能会走过不同的路径,最终达到同一个地方时,顺序就不一样了。操作系统的网络内核模块会负责对数据包进行排序,到用户层时顺序就已经完全一致了。

出处链接:juejin.cn/post/684490…

HTTP报文

  • 请求报文:所有经过传输协议,客户端传递给服务器的内容,都被成为请求报文

    • 起始行
    • 请求头(请求首部)
    • 请求主体
  • 响应报文:所有经过传输协议,服务器返回给客户端的内容,都被成为响应报文

    • HTTP状态码
    • 响应头
    • 响应主体
  • HTTP报文:请求报文+响应报文

  • 谷歌浏览器F12 =>Network(所有客户端和服务器端的交互信息在这里都可以看到) =>点击某一条信息,在右侧可以看到所有的HTTP报文信息

HTTP状态码

1~5开头,三位数字

  • 200 OK:成功
  • 201 CREATED:一般应用于告诉服务器创建一个新文件,最后服务器创建成功后返回的状态码
  • 204 NO CONTENT:对于某些请求(例如:PUT或者DELETE),服务器不想处理,可以返回空内容,并且用204状态码告知
  • 301 Moved Permanently:永久重定向(永久转移)
  • 302 Moved Temporarily:临时转移,很早以前基本上用302来做,但是现在主要用307来处理这个事情,307的意思就是临时重定向Temporary Redirect =>主要用于:服务器的负载均衡等
  • 304 Not Modified:设置HTTP的协商缓存
  • 400 Bad Request:传递给服务器的参数错误
  • 401 Unauthorized:无权限访问
  • 404 Not Found:请求地址错误
  • 500 Internal Server Error:未知服务器错误
  • 503 Service Unavailable:服务器超负荷

浏览器渲染机制

A:浏览器渲染页面的步骤

  • 解析HTML,生成DOM树,解析CSS,生成CSSOM树

  • 将DOM树和CSSOM树结合,生成渲染树(Render Tree)

  • Layout(回流): 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个阶段是回流

  • Painting(重绘): 根据渲染树以及回流得到的几何信息,得到节点的绝对像素

  • Display:将像素发送给GPU,展示在页面上

B:DOM的重绘和回流

  • 重绘:元素样式的改变(但宽高、大小、位置等不变)

  • 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染

  • 注意:回流一定会触发重绘,而重绘不一定会回流

C:前端性能优化之:避免DOM的回流

  • 放弃传统操作dom的时代,基于vue/react开始数据影响视图模式

  • 分离读写操作 (现代的浏览器都有渲染队列的机制)

  • 样式集中改变

  • 缓存布局信息

  • 元素批量修改

  • 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流)

  • CSS3硬件加速(GPU加速)

  • 牺牲平滑度换取速度

  • 避免table布局和使用css的javascript表达式

遇到 link/img/audio/video等是异步去加载资源信息(浏览器分配一个新的线程去加载,主线程继续向下渲染页面),如果遇到的是script或者@import,则让主线程去加载资源信息(同步),加载完成信息后,再去继续渲染页面

script 为同步加载任务

服务器端断开 TCP 连接

发生在发送和响应中

  • 第一次挥手:由浏览器发起,发送给服务器,我请求报文发送完了,你准备关闭吧;

  • 第二次挥手:由服务器发起,告诉浏览器,我接收完请求报文,我准备关闭,你也准备吧;

  • 第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完毕,你准备关闭吧;

  • 第四次挥手:由浏览器发起,告诉服务器,我响应报文接收完毕,我准备关闭,你也准备吧;

小栗子

TCP 四次挥手

TCP断开链接的过程和建立链接的过程比较类似,只不过中间的两部并不总是会合成一步走,所以它分成了4个动作,张三挥手(fin)——李四伤感地微笑(ack)——李四挥手(fin)——张三伤感地微笑(ack)。

img

之所以中间的两个动作没有合并,是因为tcp存在「半关闭」状态,也就是单向关闭。张三已经挥了手,可是人还没有走,只是不再说话,但是耳朵还是可以继续听,李四呢继续喊话。等待李四累了,也不再说话了,朝张三挥了挥手,张三伤感地微笑了一下,才彻底结束了。

img

上面有一个非常特殊的状态time_wait,它是主动关闭的一方在回复完对方的挥手后进入的一个长期状态,这个状态标准的持续时间是4分钟,4分钟后才会进入到closed状态,释放套接字资源。不过在具体实现上这个时间是可以调整的。

它就好比主动分手方要承担的责任,是你提出的要分手,你得付出代价。这个后果就是持续4分钟的time_wait状态,不能释放套接字资源(端口),就好比守寡期,这段时间内套接字资源(端口)不得回收利用。

它的作用是重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文。

同时在这段时间内,该链接在对话期间于网际路由上产生的残留报文(因为路径过于崎岖,数据报文走的时间太长,重传的报文都收到了,原始报文还在路上)传过来时,都会被立即丢弃掉。4分钟的时间足以使得这些残留报文彻底消逝。不然当新的端口被重复利用时,这些残留报文可能会干扰新的链接。

4分钟就是2个MSL,每个MSL是2分钟。MSL就是maximium segment lifetime——最长报文寿命。这个时间是由官方RFC协议规定的。至于为什么是2个MSL而不是1个MSL,我还没有看到一个非常满意的解释。

四次挥手也并不总是四次挥手,中间的两个动作有时候是可以合并一起进行的,这个时候就成了三次挥手,主动关闭方就会从fin_wait_1状态直接进入到time_wait状态,跳过了fin_wait_2状态。

出处链接:juejin.cn/post/684490…

--请求的地址中如果出现非有效UNICODE编码内容,现代版浏览器会默认的进行编码--
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
	<!-- DNS预获取 -->
	<!-- <meta http-equiv="x-dns-prefetch-control" content="on">
	<link rel="dns-prefetch" href="//static.360buyimg.com">
	<link rel="dns-prefetch" href="//misc.360buyimg.com">
	<link rel="dns-prefetch" href="//img10.360buyimg.com">
	<link rel="dns-prefetch" href="//img11.360buyimg.com">
	<link rel="dns-prefetch" href="//img12.360buyimg.com"> -->
	<title>AJAX</title>
	<script>
		// let link = document.getElementById('link');
		// console.log(link);
	</script>
</head>

<body>
	<button id="link">我是按钮</button>
	<script>
		/*
		 * 请求的地址中如果出现非有效UNICODE编码内容,现代版浏览器会默认的进行编码
		 *    1. 基于encodeURI编码,我们可以基于decodeURI解码,我们一般用encodeURI编码的是整个URL,这样整个URL中的特殊字符都会自动编译
		 *    2. encodeURIComponent/decodeURIComponent它相对于encodeURI来说,不用于给整个URL编码,而是给URL部分信息进行编码(一般都是问号传参的值编码)
		 *    客户端和服务器端进行信息传输的时候,如果需要把请求的地址和信息编码,我们则基于以上两种方式处理,服务器端也存在这些方法,这样就可以统一编码解码了
		 *    3.客户端还存在一种方式,针对于中文的编码方式 escape/unescape,这种方式一般只应用于客户端页面之间自己的处理,例如:从列表跳转到详情,我们可以把传递的中文信息基于这个编码,详情页获取编码后的信息再解码,再比如我们在客户端种的cookie信息,如果信息是中文,我们也基于这种办法编码...
		 */

		 //=>基于JS实现页面跳转
		link.onclick = function () {
			//=>获取当前页面的URL地址
			let url = window.location.href;
			//=>跳转页面
			window.location.href = "http://www.zhufengpeixun.cn/stu/?from=" + encodeURIComponent(url);
			// window.open("http://www.zhufengpeixun.cn/");
		} 
	</script>
</body>

</html>

TCP传输的详细过程是怎样的?

进行三次握手,建立TCP连接。
  • 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  • 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
  • 完成了三次握手,客户端和服务器端就可以开始传送数据。
  • ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0。
  • TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。
  • SYN(SYNchronization) : 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文。 FIN (finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。
  • 发送HTTP请求,服务器处理请求,返回响应结果 TCP连接建立后,浏览器就可以利用HTTP/HTTPS协议向服务器发送请求了。服务器接受到请求,就解析请求头,如果头部有缓存相关信息如if-none-match与if-modified-since,则验证缓存是否有效,若有效则返回状态码为304,若无效则重新返回资源,状态码为200. 关闭TCP连接
  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。