为何咱们要熟悉这些通讯协议? 【精读】

前端的最重要的基础知识点是什么?

  • 原生javaScriptHTML,CSS.
  • Dom操做
  • EventLoop和渲染机制
  • 各种工程化的工具原理以及使用,根据需求定制编写插件和包。(webpack的plugin和babel的预设包)
  • 数据结构和算法(特别是IM以及超大型高并发网站应用等,例如B站
  • 最后即是通讯协议

在使用某个技术的时候,必定要去追寻原理和底层的实现,久而久之坚持,只要自身底层的基础扎实,不管技术怎么变化,学习起来都不会太累,总的来讲就是拒绝5分钟技术css

从输入一个url地址,到显示页面发生了什么出发:

  • 1.浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  • 2.创建TCP链接(三次握手);
  • 3.浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文做为 TCP 三次握手的第三个报文的数据发送给服务器;
  • 4.服务器对浏览器请求做出响应,并把对应的 html 文本发送给浏览器;
  • 5.浏览器将该 html 文本并显示内容;
  • 6.释放 TCP链接(四次挥手);

目前常见的通讯协议都是创建在TCP连接之上

那么什么是TCP

TCP是因特网中的传输层协议,使用三次握手协议创建链接。当主动方发出SYN链接请求后,等待对方回答

TCP三次握手的过程以下:html

  • 客户端发送SYN报文给服务器端,进入SYN_SEND状态。
  • 服务器端收到SYN报文,回应一个SYN(SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
  • 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
  • 三次握手完成,TCP客户端和服务器端成功地创建链接,能够开始传输数据了。

如图所示:前端

TCP的四次挥手:java

  • 创建一个链接须要三次握手,而终止一个链接要通过四次握手,这是由TCP的半关闭(half-close)形成的。具体过程以下图所示。
  • 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP因而发送一个FIN分节,表示数据发送完毕。
  • 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。 注意:FIN的接收也做为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其余数据以后,由于,FIN的接收意味着接收端应用进程在相应链接上再无额外数据可接收。
  • 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这致使它的TCP也发送一个FIN。
  • 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。 [3] 既然每一个方向都须要一个FIN和一个ACK,所以一般须要4个分节。

特别提示: SYN报文用来通知,FIN报文是用来同步的react

以上就是面试官常问的三次握手,四次挥手,可是这不只仅面试题,上面仅仅答到了一点皮毛,学习这些是为了让咱们后续方便了解他的优缺点。webpack

TCP链接创建后,咱们能够有多种协议的方式通讯交换数据:

最古老的方式一:http 1.0

  • 早先1.0的HTTP版本,是一种无状态、无链接的应用层协议。git

  • HTTP1.0规定浏览器和服务器保持短暂的链接,浏览器的每次请求都须要与服务器创建一个TCP链接,服务器处理完成后当即断开TCP链接(无链接),服务器不跟踪每一个客户端也不记录过去的请求(无状态)。github

  • 这种无状态性能够借助cookie/session机制来作身份认证和状态记录。而下面两个问题就比较麻烦了。web

  • 首先,无链接的特性致使最大的性能缺陷就是没法复用链接。每次发送请求的时候,都须要进行一次TCP的链接,而TCP的链接释放过程又是比较费事的。这种无链接的特性会使得网络的利用率很是低。面试

  • 其次就是队头阻塞(headoflineblocking)。因为HTTP1.0规定下一个请求必须在前一个请求响应到达以前才能发送。假设前一个请求响应一直不到达,那么下一个请求就不发送,一样的后面的请求也给阻塞了。

Http 1.0的致命缺点,就是没法复用TCP链接和并行发送请求,这样每次一个请求都须要三次握手,并且其实创建链接和释放链接的这个过程是最耗时的,传输数据相反却不那么耗时。还有本地时间被修改致使响应头expires的缓存机制失效的问题~(后面会详细讲)

  • 常见的请求报文~

因而出现了Http 1.1,这也是技术的发展必然结果~

  • Http 1.1出现,继承了Http1.0的优势,也克服了它的缺点,出现了keep-alive这个头部字段,它表示会在创建TCP链接后,完成首次的请求,并不会马上断开TCP链接,而是保持这个链接状态~进而能够复用这个通道

  • Http 1.1而且支持请求管道化,“并行”发送请求,可是这个并行,也不是真正意义上的并行,而是可让咱们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列)

例如:客户端同时发了两个请求分别来获取html和css,假如说服务器的css资源先准备就绪,服务器也会先发送html再发送css。

  • B站首页,就有keep-alive,由于他们也有IM的成分在里面。须要大量复用TCP链接~

  • HTTP1.1好像仍是没法解决队头阻塞的问题

实际上,现阶段的浏览器厂商采起了另一种作法,它容许咱们打开多个TCP的会话。也就是说,上图咱们看到的并行,实际上是不一样的TCP链接上的HTTP请求和响应。这也就是咱们所熟悉的浏览器对同域下并行加载6~8个资源的限制。而这,才是真正的并行!

Http 1.1的致命缺点:

  • 1.明文传输
  • 2.其实仍是没有解决无状态链接的
  • 3.当有多个请求同时被挂起的时候 就会拥塞请求通道,致使后面请求没法发送
  • 4.臃肿的消息首部:HTTP/1.1能压缩请求内容,可是消息首部不能压缩;在现今请求中,消息首部占请求绝大部分(甚至是所有)也较为常见.

咱们也能够用dns-prefetch和 preconnect tcp来优化~

<link rel="preconnect" href="//example.com" crossorigin>
<link rel="dns=prefetch" href="//example.com">
复制代码
  • Tip: webpack能够作任何事情,这些均可以用插件实现

基于这些缺点,出现了Http 2.0

相较于HTTP1.1,HTTP2.0的主要优势有采用二进制帧封装,传输变成多路复用,流量控制算法优化,服务器端推送,首部压缩,优先级等特色。

HTTP1.x的解析是基于文本的,基于文本协议的格式解析存在自然缺陷,文本的表现形式有多样性,要作到健壮性考虑的场景必然不少。而HTTP/2会将全部传输的信息分割为更小的消息和帧,而后采用二进制的格式进行编码,HTTP1.x的头部信息会被封装到HEADER frame,而相应的RequestBody则封装到DATAframe里面。不改动HTTP的语义,使用二进制编码,实现方便且健壮。

多路复用

  • 全部的请求都是经过一个 TCP 链接并发完成。HTTP/1.x 虽然经过 pipeline 也能并发请求,可是多个请求之间的响应会被阻塞的,因此 pipeline 至今也没有被普及应用,而 HTTP/2 作到了真正的并发请求。同时,流还支持优先级和流量控制。当流并发时,就会涉及到流的优先级和依赖。即:HTTP2.0对于同一域名下全部请求都是基于流的,无论对于同一域名访问多少文件,也只创建一路链接。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计能够确保重要的东西能够被优先加载完

流量控制

  • TCP协议经过sliding window的算法来作流量控制。发送方有个sending window,接收方有receive window。http2.0的flow control是相似receive window的作法,数据的接收方经过告知对方本身的flow window大小代表本身还能接收多少数据。只有Data类型的frame才有flow control的功能。对于flow control,若是接收方在flow window为零的状况下依然更多的frame,则会返回block类型的frame,这张场景通常代表http2.0的部署出了问题。

服务器端推送

  • 服务器端的推送,就是服务器能够对一个客户端请求发送多个响应。除了对最初请求的响应外,服务器还能够额外向客户端推送资源,而无需客户端明确地请求。当浏览器请求一个html,服务器其实大概知道你是接下来要请求资源了,而不须要等待浏览器获得html后解析页面再发送资源请求。

首部压缩

  • HTTP 2.0 在客户端和服务器端使用“首部表”来跟踪和存储以前发送的键-值对,对于相同的数据,再也不经过每次请求和响应发送;通讯期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只 需发送一次。事实上,若是请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时全部首部都自动使用以前请求发送的首部。

  • 若是首部发生变化了,那么只须要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP 2.0 的链接存续期内始终存在,由客户端和服务器共同渐进地更新 。

  • 本质上,固然是为了减小请求啦,经过多个js或css合并成一个文件,多张小图片拼合成Sprite图,可让多个HTTP请求减小为一个,减小额外的协议开销,而提高性能。固然,一个HTTP的请求的body太大也是不合理的,有个度。文件的合并也会牺牲模块化和缓存粒度,能够把“稳定”的代码or 小图 合并为一个文件or一张Sprite,让其充分地缓存起来,从而区分开迭代快的文件。

Demo的性能对比:

Http的那些致命缺陷,并无彻底解决,因而有了https,也是目前应用最广的协议之一

HTTP+ 加密 + 认证 + 完整性保护 =HTTPS ?

能够这样认为~HTTP 加上加密处理和认证以及完整性保护后便是 HTTPS

  • 若是在 HTTP 协议通讯过程当中使用未经加密的明文,好比在 Web 页面中输入信用卡号,若是这条通讯线路遭到窃听,那么信用卡号就暴露了。
  • 另外,对于 HTTP 来讲,服务器也好,客户端也好,都是没有办法确认通讯方的。 由于颇有可能并非和本来预想的通讯方在实际通讯。而且还须要考虑到接收到的报文在通讯途中已经遭到篡改这一可能性。
  • 为了统一解决上述这些问题,须要在 HTTP 上再加入加密处理和认证等机制。咱们把添加了加密及认证机制的 HTTP 称为 HTTPS

不加密的重要内容被wireshark这类工具抓到包,后果很严重~

HTTPS 是身披 SSL 外壳的 HTTP

  • HTTPS 并不是是应用层的一种新协议。只是 HTTP 通讯接口部分用 SSL(SecureSocket Layer)和 TLS(Transport Layer Security)协议代替而已。 一般,HTTP 直接和 TCP 通讯。

  • 当使用 SSL 时,则演变成先和 SSL 通讯,再由 SSL和 TCP 通讯了。简言之,所谓 HTTPS,其实就是身披 SSL 协议这层外壳的HTTP。

  • 在采用 SSL 后,HTTP 就拥有了 HTTPS 的加密、证书和完整性保护这些功能。SSL 是独立于 HTTP 的协议,因此不光是 HTTP 协议,其余运行在应用层的 SMTP和 Telnet 等协议都可配合 SSL 协议使用。能够说 SSL 是当今世界上应用最为普遍的网络安全术。

相互交换密钥的公开密钥加密技术 -----对称加密

  • 在对 SSL 进行讲解以前,咱们先来了解一下加密方法。SSL 采用一种叫作公开密钥加密(Public-key cryptography)的加密处理方式。
  • 近代的加密方法中加密算法是公开的,而密钥倒是保密的。经过这种方式得以保持加密方法的安全性。

加密和解密都会用到密钥。没有密钥就没法对密码解密,反过来讲,任何人只要持有密钥就能解密了。若是密钥被攻击者得到,那加密也就失去了意义。

HTTPS 采用混合加密机制

  • HTTPS 采用共享密钥加密和公开密钥加密二者并用的混合加密机制。
  • 可是公开密钥加密与共享密钥加密相比,其处理速度要慢。因此应充分利用二者各自的优点,将多种方法组合起来用于通讯。在交换密钥环节使用公开密钥加密方式,以后的创建通讯交换报文阶段则使用共享密钥加密方式。

HTTPS虽好,非对称加密虽好,可是不要滥用

HTTPS 也存在一些问题,那就是当使用 SSL 时,它的处理速度会变慢。

SSL 的慢分两种。一种是指通讯慢。另外一种是指因为大量消耗 CPU 及内存等资源,致使处理速度变慢。

  • 和使用 HTTP 相比,网络负载可能会变慢 2 到 100 倍。除去和 TCP 链接、发送 HTTP 请求 ? 响应之外,还必须进行 SSL 通讯,所以总体上处理通讯量不可避免会增长。
  • 另外一点是 SSL 必须进行加密处理。在服务器和客户端都须要进行加密和解密的运算处理。所以从结果上讲,比起 HTTP 会更多地消耗服务器和客户端的硬件资源,致使负载加强。 针对速度变慢这一问题,并无根本性的解决方案,咱们会使用 SSL 加速器这种(专用服务器)硬件来改善该问题。该硬件为 SSL 通讯专用硬件,相对软件来说,可以提升数倍 SSL 的计算速度。仅在 SSL 处理时发挥 SSL加速器的功效,以分担负载。

为何不一直使用 HTTPS

  • 既然 HTTPS 那么安全可靠,那为什么全部的 Web 网站不一直使用 HTTPS? 其中一个缘由是,由于与纯文本通讯相比,加密通讯会消耗更多的 CPU 及内存资源。若是每次通讯都加密,会消耗至关多的资源,平摊到一台计算机上时,可以处理的请求数量一定也会随之减小。

  • 所以,若是是非敏感信息则使用 HTTP 通讯,只有在包含我的信息等敏感数据时,才利用 HTTPS 加密通讯。 特别是每当那些访问量较多的 Web 网站在进行加密处理时,它们所承担着的负载不容小觑。在进行加密处理时,并不是对全部内容都进行加密处理,而是仅在那些须要信息隐藏时才会加密,以节约资源。

  • 除此以外,想要节约购买证书的开销也是缘由之一。 要进行 HTTPS 通讯,证书是必不可少的。而使用的证书必须向认证机构(CA)购买。证书价格可能会根据不一样的认证机构略有不一样。一般,一年的受权须要数万日元(如今一万日元大约折合 600 人民币)。那些购买证书并不合算的服务以及一些我的网站,可能只会选择采用HTTP 的通讯方式。

复习完了基本的协议,介绍下报文格式:

  • 请求报文格式

  • 响应报文格式

所谓响应头,请求头,其实均可以本身添加字段,只要先后端给对应的处理机制便可

Node.js代码实现响应头的设置

if (config.cache.expires) {
                        res.setHeader("expries", new Date(Date.now() + (config.cache.maxAge * 1000)))
                    }
                    if (config.cache.lastModified) {
                        res.setHeader("last-modified", stat.mtime.toUTCString())
                    }
                    if (config.cache.etag) {
                        res.setHeader('Etag', etagFn(stat))
                    }
}
复制代码

响应头的详解:

本人的开源项目,手写的Node.js静态资源服务器,github.com/JinJieTan/u… star~

浏览器的缓存策略:

  • 首次请求:

  • 非首次请求:

  • 用户行为与缓存:

不能缓存的请求:

没法被浏览器缓存的请求以下:

  • HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或Cache-Control:max-age=0等告诉浏览器不用缓存的请求

  • 须要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的

  • 通过HTTPS安全加密的请求(有人也通过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public以后,可以对HTTPS的资源进行缓寸)

  • 通过HTTPS安全加密的请求(有人也通过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public以后,可以对HTTPS的资源进行缓存,参考《HTTPS的七个误解》)

  • POST请求没法被缓存

  • HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求没法被缓存

即时通信协议

从最初的没有websocket协议开始:

传统的协议没法服务端主动push数据,因而有了这些骚操做:

  • 轮询,在一个定时器中不停向服务端发送请求。
  • 长轮询,发送请求给服务端,直到服务端以为能够返回数据了再返回响应,不然这个请求一直挂起~
  • 以上两种都有瑕疵,并且比较明显,这里再也不描述。

为了解决实时通信,数据同步的问题,出现了webSocket.

  • webSockets的目标是在一个单独的持久链接上提供全双工、双向通讯。在Javascript建立了Web Socket以后,会有一个HTTP请求发送到浏览器以发起链接。在取得服务器响应后,创建的链接会将HTTP升级从HTTP协议交换为WebSocket协议。

  • webSocket原理: 在TCP链接第一次握手的时候,升级为ws协议。后面的数据交互都复用这个TCP通道。

  • 客户端代码实现:

const ws = new WebSocket('ws://localhost:8080');
        ws.onopen = function () {
            ws.send('123')
            console.log('open')
        }
        ws.onmessage = function () {
            console.log('onmessage')
        }
        ws.onerror = function () {
            console.log('onerror')
        }
        ws.onclose = function () {
            console.log('onclose')
        }
复制代码
  • 服务端使用 Node.js语言实现
const express = require('express')
const { Server } = require("ws");
const app = express()
const wsServer = new Server({ port: 8080 })
wsServer.on('connection', (ws) => {
    ws.onopen = function () {
        console.log('open')
    }
    ws.onmessage = function (data) {
        console.log(data)
        ws.send('234')
        console.log('onmessage' + data)
    }
    ws.onerror = function () {
        console.log('onerror')
    }
    ws.onclose = function () {
        console.log('onclose')
    }
});

app.listen(8000, (err) => {
    if (!err) { console.log('监听OK') } else {
        console.log('监听失败')
    }
})
复制代码

webSocket的报文格式有一些不同:

  • 客户端和服务端进行Websocket消息传递是这样的:

    • 客户端:将消息切割成多个帧,并发送给服务端。
    • 服务端:接收消息帧,并将关联的帧从新组装成完整的消息。

即时通信的心跳检测:

pingandpong

  • 服务端Go实现:
package main

import (
    "net/http"
    "time"

    "github.com/gorilla/websocket"
)

var (
    //完成握手操做
    upgrade = websocket.Upgrader{
       //容许跨域(通常来说,websocket都是独立部署的)
       CheckOrigin:func(r *http.Request) bool {
            return true
       },
    }
)

func wsHandler(w http.ResponseWriter, r *http.Request) {
   var (
         conn *websocket.Conn
         err error
         data []byte
   )
   //服务端对客户端的http请求(升级为websocket协议)进行应答,应答以后,协议升级为websocket,http创建链接时的tcp三次握手将保持。
   if conn, err = upgrade.Upgrade(w, r, nil); err != nil {
        return
   }

    //启动一个协程,每隔5s向客户端发送一次心跳消息
    go func() {
        var (
            err error
        )
        for {
            if err = conn.WriteMessage(websocket.TextMessage, []byte("heartbeat")); err != nil {
                return
            }
            time.Sleep(5 * time.Second)
        }
    }()

   //获得websocket的长连接以后,就能够对客户端传递的数据进行操做了
   for {
         //经过websocket长连接读到的数据能够是text文本数据,也能够是二进制Binary
        if _, data, err = conn.ReadMessage(); err != nil {
            goto ERR
     }
     if err = conn.WriteMessage(websocket.TextMessage, data); err != nil {
         goto ERR
     }
   }
ERR:
    //出错以后,关闭socket链接
    conn.Close()
}

func main() {
    http.HandleFunc("/ws", wsHandler)
    http.ListenAndServe("0.0.0.0:7777", nil)
}
复制代码

客户端的心跳检测(Node.js实现):

this.heartTimer = setInterval(() => {
      if (this.heartbeatLoss < MAXLOSSTIMES) {
        events.emit('network', 'sendHeart');
        this.heartbeatLoss += 1;
        this.phoneLoss += 1;
      } else {
        events.emit('network', 'offline');
        this.stop();
      }
      if (this.phoneLoss > MAXLOSSTIMES) {
        this.PhoneLive = false;
        events.emit('network', 'phoneDisconnect');
      }
    }, 5000);

复制代码

自定义即时通讯协议:

new Socket开始:

  • 目前即时通信大都使用现有大公司成熟的SDK接入,可是逼格高些仍是本身重写比较好。
  • 打个小广告,咱们公司就是本身定义的即时通信协议~招聘一位高级前端,地点深圳-深南大道,作跨平台IM桌面应用开发的~
  • 客户端代码实现(Node.js):
const {Socket} = require('net') 
const tcp = new Socket()
tcp.setKeepAlive(true);
tcp.setNoDelay(true);
//保持底层tcp连接不断,长链接
指定对应域名端口号连接
tcp.connect(80,166.166.0.0)
创建链接后
根据后端传送的数据类型 使用对应不一样的解析
readUInt8 readUInt16LE readUInt32LE readIntLE等处理后获得myBuf 
const myBuf = buffer.slice(start);//从对应的指针开始的位置截取buffer
const header = myBuf.slice(headstart,headend)//截取对应的头部buffer
const body = JSON.parse(myBuf.slice(headend-headstart,bodylength).tostring())
//精确截取数据体的buffer,而且转化成js对象
复制代码

即时通信强烈推荐使用Golang,GRPC,Prob传输数据。

上面的一些代码,都在个人开源项目中:

以为写得不错,能够点个赞支持下,文章也借鉴了一下其余大佬的文章,可是地址都贴上来了~ 欢迎gitHub点个star哦~

相关文章
相关标签/搜索