作为一个前端开发工程师,性能优化是绕不过去的难题,大的性能问题大体归类为首屏加载缓慢、白屏、卡顿、网络延迟等,小的问题有HTML布局抖动,css优化、图片资源加载优化等。解决网络延迟,资源加载等涉及网络的一类问题就需要对网络协议有一定的了解。
例如当浏览器输入网页地址,开始搜索网页后,浏览器会作如何处理呢?大概的过程如下:
从上面过程可以看出,整个过程包含了TCP/IP和DNS、缓存等网络相关知识。
DNS域名解析
DNS 的全称是 Domain Name System,又称域名系统。客户端与服务端建立TCP连接,需要知道服务端IP地址,但IP地址由一串数字组成,导致不方便记忆,所以便采用域名系统来管理名字和IP的映射关系,如把www.baidu.com解析为163.177.151.110,从而找到百度的服务器。
DNS 解析过程会进行递归查询,分别依次尝试从以下途径,按顺序地获取该域名对应的 IP 地址。
DNS 解析过程会根据上述步骤进行递归查询,如果当前步骤没查到,则自动跳转到到下一步骤通过下一个 DNS 服务器进行查找。如果最终依然没找到,浏览器便会将页面响应为打开失败。
DNS 解析完成后,浏览器获得了服务端的 IP 地址,接下来便可以向服务端发起 HTTP 请求。目前大多数 HTTP 请求都建立在 TCP 连接上,因此客户端和服务端会先建立起 TCP 连接。
在计算机网络与信息通信领域里,人们经常提及“协议”一词。互联网中常用的具有代表性的 协议有IP,TCP,HTTP等。而LAN(局域网)中常用的协议有IPX/SPX等。
“计算机网络体系结构”将这些网络协议进行了系统的归纳。TCP/TP就是IP、TPC、HTTP等协议的集合。因此前端工程师需要对TCP/IP的基础知识有所掌握,当然如果把OSI参考模型中的7个分层都了解清楚是最好的。
OSI参考模型
TCP 连接的建立过程比较偏通信底层,在前端日常开发过程中不容易接触到。但有时候我们需要优化应用的加载耗时、请求耗时或是定位一些偏底层的问题(请求异常、HTTP 连接无法建立等),都会或多或少依赖这些偏底层的知识。
另外,从面试的角度看,我们需要掌握 TCP/UDP 的区别、TCP 的三次握手和四次挥手内容。
TCP 协议提供可靠传输服务,UDP 协议则可以更快地进行通信;
三次握手:指 TCP 连接的建立过程,该过程中客户端和服务端总共需要发送三个包,从而确认连接存在。
四次挥手:指 TCP 连接的断开过程,该过程中需要客户端和服务端总共发送四个包以,从而确认连接关闭。
当客户端和服务端建立起 TCP 连接之后,HTTP 服务器会监听客户端发起的请求,此时客户端会发起 HTTP 请求。
HTTP协议是OSI模型的应用层协议。前端经常需要用ajax技术将http请求发送给服务器,这是前端开发唯一能接触到的协议内容。
当用户在浏览器的地址栏里输入访问的web页URL以后,HTTP处理便会开始。它的工作机制,首先是客户端向服务器的默认端口80建立一个TCP链接,然后在这个TCP连接之上进行请求和应答以及数据报文的发送。所有的应用网络通信都是OSI参考模型7个分层自上而下的结果,而http请求是建立在TCP连接之上的。
目前大多数 HTTP 请求都是基于 TCP 协议。TCP 协议的目的是提供可靠的数据传输,它用来确保可靠传输的途径主要包括两个:
乱序重建:通过对数据包编号来对其排序,从而使得另一端接收数据时,可以重新根据编号还原顺序。
丢包重试:可通过发送方是否得到响应,来检测出丢失的数据并重传这些数据。
通过以上方式,TCP 在传输过程中不会丢失或破坏任何数据,这也是即使出现网络故障也不会损坏文件下载的原因。
当服务端建立起与客户端的 TCP 连接之后,服务端会持续监听客户端发起的请求。接下来,客户端将发起 HTTP 请求,请求内容通常包括请求方法、请求的资源等,服务端收到请求后会进行回复,回复内容通常包括 HTTP 状态、响应消息等。
可以看到,网络请求的过程包括两个步骤:客户端发送请求,服务器返回响应。这就是HTTP 协议的主要特点:遵循经典的“客户端-服务端”模型。
除此之外,HTTP 协议还被设计得简单易读。在 HTTP/2 之前,HTTP 协议是语义可读的,我们可以直观地获取其中的内容。比如:
HTTP 请求方法:代表着客户端的动作行为(GET-获取资源/POST-提交资源/PUT-修改资源/DELETE-删除资源)。
HTTP 状态码:代表着当前请求的状态(1XX-提示信息/2XX-成功/3XX-重定向/4XX-客户端错误/5XX-服务端错误)。
HTTP 消息头:客户端和服务端通过request和response传递附加信息。
通过 HTTP 协议,我们可以看到该请求的状态码,是否成功还是错误,原因是哪些、请求是否使用了缓存、请求和响应数据是否符合预期等。
HTTP1.0 工作机制
HTTP 协议从被创造以来,一直在不断演变着:从 HTTP/1.0、HTTP/1.1,到 HTTP/2、HTTP/3,HTTP 协议在保持协议简单性的同时,拓展了灵活性,提供越来越快、更加可靠的传输服务。
HTTP/1.0 到 HTTP/1.1,主要实现了对 TCP 连接的复用。 最初在 HTTP/1.0 中,每一对 HTTP 请求和响应都需要打开一个单独的 TCP 连接。这样的方式对资源消耗很大,因此 HTTP/1.1 中引入了持久连接的概念,通过设置Connection头部为keep-alive的方式,可以让 TCP 连接不会关闭。该功能避免了 TCP 连接的重新建立,客户端可在已建立的 TCP 连接上,长时间地对同一个服务端的发起请求,这种方式也叫保持连接。
HTTP/1.1 到 HTTP/2,主要实现了多个请求的复用。 HTTP/2 通过将 HTTP 消息拆分为独立的帧,进行交错发送,实现在同一个连接上并行多个请求,来减少网络请求的延迟。为了实现多路复用,HTTP/2 协议对 HTTP 头部进行了二进制编码,因此不再语义可读。除此之外,HTTP2 还实现了 Header 压缩、服务端主动推动、流优先级等能力。
HTTP/2 到 HTTP/3,主要实现了基于 UDP 协议、更快的传输。 HTTP/3 使用了基于 UDP 的 QUIC 协议,实现了又快又可靠的传输。由于 UDP 协议中没有错误检查内容,因此可以更快地实现通信。同时,QUIC 协议负责合并纠错、重建丢失的数据,解决了 UDP 协议传输丢包的问题。
HTTP 协议的演变过程主要围绕着传输效率和速度上的优化,可以通过升级 HTTP 协议来优化前端应用。除此之外,我们在日常的工作中,同样可以借鉴 HTTP 协议的优化手段。比如,可以使用资源压缩、资源复用等技术手段,来优化前端性能。
缓存常常被用作性能优化的技术方案之一,通过缓存我们可以有效地减少资源获取的耗时,减少用户的等待时长,从而提升用户的体验。
其中,我们可以通过 HTTP 协议,设置浏览器对 HTTP 响应资源进行缓存。使用浏览器缓存后,当我们再发起 HTTP 请求时,如果浏览器缓存发现请求的资源已经被存储,它会拦截请求并返回该资源的副本,不需要再去请求服务端获取资源,因此减少了 HTTP 请求的耗时,同时也能有效地缓解服务端压力。
一般来说,HTTP 缓存只能存储 GET 请求的响应内容,对于这些响应内容可能会存在两种情况:
1.不缓存内容,每次请求的时候都会从服务端获取最新的内容;
2.设置了缓存内容,则在有效期内会从缓存获取,如果用户刷新或内容过期则去服务端获取最新的内容。
那么,要如何给 GET 请求设置缓存呢?在浏览器中,便是依靠请求和响应中的头信息来控制缓存的。根据缓存的行为,我们可以将它们分为强制缓存和协商缓存两种。
1.强制缓存, 在规定有效期内,直接使用缓存。可以通过以下的方式使用强制缓存:
服务端通过设置Expires和Cache-Control,和客户端约定缓存内容的有效时间;
若符合缓存条件,浏览器响应HTTP 200(from cache)。
2.协商缓存, 与服务端协商是否使用缓存。可以通过以下的方式使用协商缓存:
服务端通过设置If-Modified-Since和If-None-Match,和客户端约定标识协商缓存的值;
有效期过后,浏览器将缓存信息中的 Etag 和 Last-Modified 信息,分别使用 If-None-Match 和 If-Modified-Since 请求头设置,提交给服务端。
若符合缓存条件,服务端则响应HTTP 304,浏览器将从缓存读数据。
若以上缓存条件均不符合,服务端响应HTTP 200,返回更新后的数据,同时通过响应头更新 HTTP 缓存设置。整个过程可以用下面的流程图来表示:
客户端和服务端的通信方式有很多种,大多数场景下都是由客户端主动发送数据给服务端,但在特定的场景下(如多人协作、在线游戏)客户端还需要和服务端保持实时通信,此时需要使用双向通信。
常见的双向通信方式包括 HTTP 短轮询(polling)、HTTP 长轮询(long-polling)、XHR Streaming、Server-Sent Events、Websocket 等。
客户端每隔特定的时间(比如 1s)便向服务端发起请求,获取最新的资源信息。该方式会造成较多的资源浪费,尤其当服务端内容更新频率低于轮询间隔时,就会造成服务端资源、客户端资源的浪费。除此之外,过于频繁的请求也会给服务端造成额外的压力,当服务端负载较高的时候,甚至可能导致雪崩等情况发生。
解决了短轮询的一些问题,长轮询实现特点主要为当客户端向服务端发起请求后,服务端保持住连接,当数据更新响应之后才断开连接。然后客户端会重新建立连接,并继续等待新数据。此技术的主要问题在于,在重新连接过程中,页面上的数据可能会过时且不准确。
可以维护客户端和服务端之间的连接。但使用 XHR Streaming 过程中,XMLHttpRequest对象的数量将不断增长,因此在使用过程中需要定期关闭连接,来清除缓冲区。
主要基于浏览器中EventSourceAPI 的封装和协议。服务端使用分块传输编码(Chunked transfer encoding)的HTTP传输机制进行响应,并且服务器端不终止HTTP响应流,让HTTP始终处于持久连接状态,当有数据需要发送给客户端时再进行写入数据。
它实现了浏览器与服务端全双工通信。前面我们提到,HTTP 短轮询、长轮询都会带来额外的资源浪费,因此 Websocket 在实现实时通信的同时,能更好地节省服务端资源和带宽。
Websocket 建立在 TCP 协议之上,握手阶段采用 HTTP 协议,但这个 HTTP 协议的请求头中,有以下的标识性内容。
Connection: Upgrade、Upgrade: websocket:表示这个连接将要被转换为 WebSocket 连接。
Sec-WebSocket-Key:向服务端提供所需的信息,以确认客户端有权请求升级到 WebSocket。
Sec-WebSocket-Protocol:指定一个或多个的 WebSocket 协议。
Sec-WebSocket-Version:指定 WebSocket 的协议版本。
如果服务端同意启动 WebSocket 连接,会在握手过程中的 HTTP 协议中返回包含Sec-WebSocket-Accept的响应消息,接下来客户端和服务端便建立 WebSocket 连接,并通过 WebSocket 协议传输数据。
由于不再需要通过 HTTP 协议通信,省去请求头等内容设置,Websocket 数据格式会更加轻量,通信更加高效,性能开销也相应地降低。除此之外,不同于 HTTP 协议,Websocket 协议没有同源限制,因此客户端可以与任意服务端通信。
各通信详细原理可参考https://segmentfault.com/a/1190000019697463
网络通信就好比2人沟通的过程,通过双方为沟通建立一种高效的机制,从而做到最省时(传输速度快),最省力(资源消耗低)且保证双方都能满足需要(高可靠)