HTML5 - WebSocket

杨旭 bio photo By 杨旭

HTML5 WebSocket技术定义了客户端和服务器之间的全双工通信通道。避免了以往通过各种hacks方式的通信。

历史

在WebSocket技术出现之前,前端在处理一些实时更新的数据展现,例如:股票走势、位置信息、进度等业务场景时,往往通过:要求用户不断刷新通过定时器不停查询或者Comet的服务端推送

要求用户不断刷新

这是一个不可理喻的要求,特别对于广泛的大众用户或者非IT从业者来说。也许只有专业的IT从业者才能够理解并忍受如此糟糕的用户体验。

轮询

实现:前端通过JS脚本定时发送HTTP请求,返回数据后更新界面显示。

setInterval(() => {
    
    fetch('url').then((response) => {
        // update page
    }) 
    
}, 5000) // every five seconds

轮询带来的问题:

  • 客户端无法预知是否有新的数据
  • 大量的无效请求增加带宽消耗
  • 厚重的HTTP请求头,频繁的连接创建和断开

长轮询

服务器对每个请求保持一段时间打开状态,如果出现数据更新,会通过该连接发送信息,并关闭该连接。

虽然在一定程度上减少了连接次数,但是服务端保持连接会造成资源消耗,而且无法保证消息返回顺序,同样难于管理和维护

iframe流

在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间建立一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。 iframe流方式的优点是浏览器兼容好

WebSocket

WebSocket是Web应用程序的传输协议,类似于TCP,提供了双向、按序到达的数据流。

  • 阮一峰老师的WebSocket教程

  • 在创建websocket通信时,客户端于服务器进行握手,将HTTP协议升级为WebSocket协议
  • 建立连接时,使用ws://或wss://前缀作为url

  • 客户端使用时,基于socket对象的事件来进行管理和数据获取
  • 没有同源限制,客户端可以与任意服务器通信。
  • 使用WebSocket,避免了循环请求的性能开销,而且避免了HTTP请求头造成的带宽消耗

建立websocket服务器

基于nodejs-websocket

  • 安装依赖
$ yarn add nodejs-websocket
  • websocket-server.js
const ws = require("nodejs-websocket")

// Scream server example: "hi" -> "HI!!!"
const server = ws.createServer(function (conn) {
    console.log("New connection")
    conn.on("text", function (str) {
        console.log("Received "+str)
        conn.sendText(str.toUpperCase()+"!!!")
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
}).listen(8001)
  • 启动服务
    $ node websocket-server.js
    

客户端使用WebSocket获取数据

首先检查浏览器支持情况

window.onload = () => {
    if (window.WebSocket) {
        createConnect()
    }
}
  • 通过检查全局WebSocket是否存在,判断浏览器是否支持

创建连接

let socket = null

function createConnect() {
    socket = new WebSocket('ws://localhost:8001')
    bindEvents(socket)
}

绑定事件

function bindEvents(socket) {
    socket.onopen = () => {
        appendMsg('connected')
    }

    socket.onclose = () => {
        appendMsg('disconnected')
    }

    socket.onerror = () => {
        appendMsg('error')
    }

    socket.onmessage = (e) => { 
        appendMsg(`Received: ${e.data}`)
    }
}
  • onmessage - 收到服务器推送的数据时,更新界面显示

完整代码如下(为了便于演示,js代码嵌在html文件中)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Client</title>
</head>
<body>

    <script type="text/javascript">

        let socket = null

        function createConnect() {
            socket = new WebSocket('ws://localhost:8001')
            bindEvents(socket)
        }

        function bindEvents(socket) {
            socket.onopen = () => {
                appendMsg('connected')
            }

            socket.onclose = () => {
                appendMsg('disconnected')
            }

            socket.onerror = () => {
                appendMsg('error')
            }

            socket.onmessage = (e) => {
                appendMsg(`Received: ${e.data}`)
            }
        }

        function appendMsg(msg) {
            const li = document.createElement('li')
            li.innerHTML = msg

            document.getElementById('result').appendChild(li)
        }

        function sendMessage() {
            const msg = document.getElementById('inputText').value
            appendMsg(`Send: ${msg}`)
            socket.send(msg)
        }

        window.onload = () => {

            if (window.WebSocket) {
                createConnect()
            }
        }

    </script>

    <div>
        <input type="text" id="inputText" />
        <input type="button" onclick="sendMessage()" value="Send"/>
    </div>

    <ul id="result">
    </ul>

</body>
</html>

关注一下握手时的请求信息

  • 请求头信息包含websocket的协商

  • 与服务端建立连接后,返回101状态码

其他资料