计算机网络 | 应用层 | websocket使用指南

732 阅读3分钟

前言

关于计算机网络,其主要部分是通信,而根据通信方式,我们又将协议划分为三类:单功、半双工、双工;我们常见的 HTTP 协议就是通信协议中最常见的一种半双工协议。怎么区分呢?我们将通信两端区分为clientserve端,如果只能单向传输,比如C -> S那就是单功;比如一端接收请求后可以响应,但不能主动发送信息,那即是半双功;再就是双端均可主动推送,那就是双功,我们今天的主角websocket就是一个双功协议。

它解决了什么问题?

在出现websocket之前,对于【服务器有连续的状态变化,客户端要获知】的情况,一般有下面三种处理方案

  • 轮询(polling):客户端定时发起请求状态的任务压力大
    • 普通轮询
    • 长轮询(long-polling)
  • iframe流(streaming):存在client端一直处于请求未关闭状态
  • EventSource流:不能跨域

文末会扩展这三个方案的具体细节,但也可以看到,上面三个方案都是存在问题的,这也是websocket被广泛使用的原因

它是如何使用的?

serve.js
let express = require('express');

let app = express();
app.use(express.static(__dirname));


let WebSockerServer = require('ws').Server;
let server = new WebSockerServer({
    port: 8888
})
// socket 套接字
server.on('connection', function (socket){
    console.log('2. 服务端监听到了客户端的连接');
    socket.on('message',function (message){
        console.log('4. 客户端连接信息', message);
        socket.send('5. 服务器说:'+message)
    })
})

app.listen(3001)
client

Index.html中的script

let socket = new WebSocket('ws://localhost:8888');
socket.onopen = function () {
  console.log('1. 客户端连接上了服务端');
  socket.send('3. 你好')
}
socket.onmessage = function (event) {
  console.log('6. ' + event.data);
}
现象

image-20210329170245824

image-20210329170253505

特点

在发起升级协议时, 在请求上存在

  • 请求头
    • Connection : Upgrade
    • Upgrade : websocket
    • Sec-WebSocket-Version: [websocket版本号]
    • Sec-WebSocket-Key: [base64加密key]{与响应头中的Sec-WebSocket-Accept相对应,如果匹配成功才会升级为ws协议}
    • 在响应上存在
  • 状态码是101 switching Protocols 切换协议成功
  • 响应头
    • Connection : Upgrade(Http常见keep-alive)
    • Upgrade : websocket

websocket前的三种处理方案

轮询

普通轮询:其实就是启动一个定时器,然后不断的发起请求

let clock = document.querySelector('#clock');
        setInterval(function (){
            let xhr = new XMLHttpRequest;
            xhr.open('GET','/clock',true);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    clock.innerHTML = xhr.responseText;
                }
            }
            xhr.send()
        },1000)

长轮询:区别于普通轮询,就是将下一次的请求后置在上一次的响应之后

function send() {
            let xhr = new XMLHttpRequest;
            xhr.open('GET','/clock',true);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    clock.innerHTML = xhr.responseText;
                    send()
                }
            }
            xhr.send()
        }
        send()

问题也是显而易见的,客户端会发起很多无意义的请求

iframe

iframe方案有点像jsonp,这个实现的关键在于两点

  • iframe会将src返回的内容显示在iframe中(这样就可以返回一个script进行操作)
  • 服务端在接收到iframe请求后写出数据但不断开端口连接,这样在数据变化时就可以写出新数据了

Client

 <!-- 会默认请求src 并将返回结果回显在iframe中 -->
    <iframe src="/clock" frameborder="0"></iframe>

    <script>
        function setTime(ts) {
            document.querySelector('#clock').innerHTML = ts;
        }
    </script>

serve

let express = require('express');

let app = express();
app.use(express.static(__dirname));
app.get('/clock', function (req,res){
    res.header('Content-Type', 'text/html');
    setInterval(function (){
        res.write(`
            <script>
                parent.setTime("${new Date().toLocaleString()}")
            </script>
        `)
    })
})

app.listen(8000)

问题在于client端一直处于请求未关闭状态,所以会tab栏上会一直转圈圈

EventSource流

EventSource是标准库的一个API,W3C:EventSource

Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is standardized as part of HTML5[1] by the W3C.

大致翻译下就是:SSE是一种能让浏览器通过HTTP连接自动收到服务器端更新的技术,SSE EventSource 接口被W3C制定为HTML5的一部分。

client

let eventSource = new EventSource('/clock');
// 监听服务器发过来的消息
eventSource.onmessage = function (event) {

  console.log('onmessage',event);
  let message = event.data;
  clock.innerHTML = message;
}
eventSource.onerror = function (event) {

}

server

关键在于三点

  1. Content-Type设置为text/event-stream
  2. 按照event:message写出数据
  3. 写出数据以\r\n结尾
res.header('Content-Type', 'text/event-stream');
setInterval(function (){
  // res.write(`  
  //    event:message
  // `)
  res.write(`
event:message\ndata:${new Date().toLocaleString()}\r\n
`)
},1000)

缺点:

  1. 无法跨域
  2. 只能支持服务端向客户端的推送
WebSocket & EventSource 的区别
  1. WebSocket基于TCP协议,EventSource基于http协议。
  2. EventSource是单向通信,而websocket是双向通信。
  3. EventSource只能发送文本,而websocket支持发送二进制数据。
  4. 在实现上EventSource比websocket更简单。
  5. EventSource有自动重连接(不借助第三方)以及发送随机事件的能力。
  6. websocket的资源占用过大EventSource更轻量。
  7. websocket可以跨域,EventSource基于http跨域需要服务端设置请求头。

文末推荐

阮一峰老师websocket