多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
https://juejin.cn/post/7020964728386093093#heading-8 https://juejin.cn/post/6844904007593361415 https://juejin.cn/post/6844903544978407431#heading-10 ## WebSocket由来 通信只能由客户端发起,不具备服务器推送能力。 应用场景:即时聊天通信、多玩家游戏、在线协同编辑/编辑、实时数据流的拉取与推送、体育/游戏实况、实时地图位置; 实时更新或连续数据流,则可以使用WebSocket;获取旧数据或只获取一次数据,则用HTTP协议; ## WebSocket 与 HTTP 的区别 相同点: 都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。 联系: WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。 不同点:1、WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息,而HTTP是单向的, HTTP1.1 中有一个 keep-alive,在一个 HTTP 连接中,可以发送多个 Request接收多个 Response,但是 一个 Request 只能有一个 Response 2、WebSocket是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。 注意:虽然HTTP/2也具备服务器推送功能,但HTTP/2 只能推送静态资源,无法推送指定的信息。 ## Websocket的优缺点 优点:WebSocket协议一旦建议后,互相沟通所消耗的请求头是很小的;服务器可以向客户端推送消息了 缺点:少部分浏览器不支持,浏览器支持的程度与方式有区别(IE10) ## WebSocket连接的过程 首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等; 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据; 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。 - 客户端发起协议升级请求 ```js HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat ``` WebSocket复用了HTTP的握手通道。--- 客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。 > `Connection: Upgrade`:表示要升级协议 > > `Upgrade: websocket`:表示要升级到websocket协议。 > > `Sec-WebSocket-Version: 13`:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Version header,里面包含服务端支持的版本号。 > > `Sec-WebSocket-Key`:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。 - 服务端:响应协议升级 状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。 ```js HTTP/1.1 101 Switching Protocols Connection:Upgrade Upgrade: websocket Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU= ``` ## websocket 断线重连 - 判断在线离线? 当客户端第一次发送请求至服务端时会携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果不存在就存入db或者缓存中, 第二次客户端定时再次发送请求依旧携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果存在就把上次的时间戳拿取出来,使用当前时间戳减去上次的时间,得出的毫秒秒数判断是否大于指定的时间,若小于的话就是在线,否则就是离线; - 怎么断线? 1. 主动断开连接,`ws.close();` 2. - 断线原因 1. websocket超时没有消息自动断开连接;【 超时 】 解决: 在小于服务端设置的超时时间内发送心跳包,有2中方案: 客户端主动发送上行心跳包,或者服务端主动发送下行心跳包。 2. websocket异常:服务端出现中断,交互切屏等客户端异常中断;【 中断 】 解决:客户端通过onclose 关闭连接,服务端再次上线时则需要清除之间存的数据,若不清除则会造成只要请求到服务端的都会被视为离线。 措施: 异常中断 ---> 重连:引入reconnecting-websocket.min.js,ws建立链接方法使用js库api方法 断网监测 ----> 使用js库:offline.min.js ## 心跳包机制 下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。 1. 客户端每隔一个时间间隔发生一个探测包给服务器 2. 客户端发包时启动一个超时定时器 3. 服务器端接收到检测包,应该回应一个包 4. 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器 5. 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了 ## 客户端的 API - 新建WebSocket实例 WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。 ```js var ws = new WebSocket('ws://localhost:8080'); ``` - webSocket.readyState 当前状态 ```js switch (ws.readyState) { case WebSocket.CONNECTING: // CONNECTING:值为0,表示正在连接 case WebSocket.OPEN: // 值为1,表示连接成功,可以通信了 case WebSocket.CLOSING: // 值为2,表示连接正在关闭 case WebSocket.CLOSED: // 值为3,表示连接已经关闭,或者打开连接失败 } ``` - webSocket.onopen --- 连接成功后的回调函数 - webSocket.onclose --- 连接关闭后的回调函数 - webSocket.onmessage --- 收到服务器数据后的回调函数 - webSocket.send() --- 向服务器发送数据 - webSocket.bufferedAmount --- 表示还有多少字节的二进制数据没有发送。用来判断发送是否结束 - webSocket.onerror --- 报错时的回调函数 ## 服务端实现 Node 实现有三种: µWebSockets、Socket.IO、WebSocket-Node ```shell websocketd --port=8080 bash ./counter.sh ``` 启动一个 WebSocket 服务器,端口是8080。每当客户端连接这个服务器,就会执行counter.sh脚本,并将它的输出推送给客户端. ```js // 客户端的 JavaScript 代码 var ws = new WebSocket('ws://localhost:8080/'); ws.onmessage = function(event) { console.log(event.data); }; ``` ## 在vue项目中使用websocket ```js <template> <div class="box"> websocket </div> </template> <script> const heartCheck = { timeout: 60 * 1000, timer: null, serverTimer: null, reset() { this.timer && clearTimeout(this.timer) this.serverTimer && clearTimeout(this.serverTimer) }, start(ws) { this.reset() this.timer = setTimeout(() => { // onmessage拿到返回的心跳就说明连接正常 ws.send(JSON.stringify({ heart: 1 })) // 如果超过一定时间还没响应(响应后触发重置),说明后端断开了 this.serverTimer = setTimeout(() => { ws.close() }, this.timeout) }, this.timeout) } } export default { name: 'Websocket', data() { return { wsuri: 'ws://123.207.167.163:9010/ajaxchattest', // ws wss lockReconnect: false, // 连接失败不进行重连 maxReconnect: 5, // 最大重连次数,若连接失败 socket: null } }, mounted() { this.initWebSocket() }, methods: { reconnect() { console.log('尝试重连') if (this.lockReconnect || this.maxReconnect <= 0) { return } setTimeout(() => { // this.maxReconnect-- // 不做限制 连不上一直重连 this.initWebSocket() }, 60 * 1000) }, initWebSocket() { try { if ('WebSocket' in window) { this.socket = new WebSocket(this.wsuri) } else { console.log('您的浏览器不支持websocket') } this.socket.onopen = this.websocketonopen this.socket.onerror = this.websocketonerror this.socket.onmessage = this.websocketonmessage this.socket.onclose = this.websocketclose } catch (e) { this.reconnect() } }, websocketonopen() { console.log('WebSocket连接成功', this.socket.readyState) heartCheck.start(this.socket) // this.socket.send('发送数据') this.websocketsend() }, websocketonerror(e) { console.log('WebSocket连接发生错误', e) this.reconnect() }, websocketonmessage(e) { let data = JSON.parse(e.data) console.log('得到响应', data, '可以渲染网页数据...') // 消息获取成功,重置心跳 heartCheck.start(this.socket) }, websocketclose(e) { console.log('connection closed (' + e.code + ')') this.reconnect() }, websocketsend() { let data = { id: 'a1b2c3' } this.socket.send(JSON.stringify(data)) } }, destroyed() { this.socket.close() } } </script> ```