乐趣区

你不知道WebSocket吗

什么是 WebSocket?

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。这里我们发现了一个有趣的词:”全双工”, 那我们就来简单了解下通信方式有哪些!

单工

通信双方中,一方固定为发送端,一方则固定为接收端。信息只能沿一个方向传输。
例如计算机与打印机之间的通信是单工模式

说的简单些就是: 我打你你只能忍着!

半双工

允许数据在两个方向上传输,但是同一时间数据只能在一个方向上传输,其实际上是切换的单工。
例如 HTTP 协议: 客户端向服务器发送请求(单向的),然后服务器响应请求(单向的)

说的简单些就是: 我打你,你忍完后可以打我,我忍着…

全双工

允许数据在两个方向上同时传输。
例如手机通话,WebSocket 就是这个样子!

说的简单些就是: 两个人同时可以互相打对方

说了这么多其实目的就是让大家知道,WebSocket 是支持双向通信的!

双向通信的优点

为什么要支持双向通信?单向通信有什么问题? 还是从 HTTP 说起, 我们知道 HTTP 协议是半双工的,而且服务器不能主动推送消息给浏览器!这个就是他的缺陷。假若我希望实现一个股票交易系统,可能股价每秒钟都有变化,但是价格变化了如何通知我们的客户端?

咱们来看看以前是怎么实现的!

轮询

什么叫轮询?就是不停的轮番询问!说的直白些就是客户端定期发送请求给服务端。

短轮询

配段代码,Talk is cheap,show me your code.

const express = require("express");
const app = express();
// express 静态服务中间件用来返回静态文件
app.use(express.static(__dirname));
// 当前价格是 100 元
let currentPrice = 100;
// 获取最新价格接口
app.get("/getPrice", (req, res, next) => {res.send('¥'+currentPrice * Math.random());
});
app.listen(3000);

客户端不停的发送请求,去服务端获取最新价格。

<div> 当前交易价格: <span id="price"></span></div>
<script>
    setInterval(() => {fetch('/getPrice').
        then(res=>res.text()).
        then(data=>price.innerHTML = data)
    }, 1000);
</script>

很快我们就看出了这样编写代码的缺陷!如果数据变化的不快呢,那就会发送很多无意义的请求。每次发送请求都会有 HTTP 的 Header 会消耗大量流量,同时也会消耗 CPU 的利用率!

长轮询

长轮询是对短轮询的改进版,就是当第一个请求回来时再发送下一个请求!

(function poll(){fetch('/getPrice').
    then(res=>res.text()).
    then(data=>{price.innerHTML = data;poll()})
})()

问题依旧是显而易见的!如果服务端数据变化很快,那么请求数目会更多;如果变化很慢,可能 ajax 会出现超时的问题。

Iframe 方式

我们并不希望每次都创建一个新的请求,此时就可以使用 Iframe 来实现长连接

app.get("/getPrice", (req, res, next) => {setInterval(()=>{
        // 不能使用 end 否则会中断请求,我们要实现的是长连接
        res.write(`
            <script>
                parent.document.getElementById('price').innerHTML = ${currentPrice * Math.random()}
            </script>
        `);
    },1000);
});
<body>
    <div> 当前交易价格: <span id="price"></span></div>
    <iframe src="/getPrice" frameborder="0"></iframe>
</body>

现在确实可以利用 Iframe 实现了长连接通信,但是页面的状态一直是加载态!

EventSource 流

EventSource 接口用于接收服务器发送的事件。它通过 HTTP 连接到一个服务器,以 text/event-stream 格式接收事件, 不关闭连接。

<div> 当前交易价格: <span id="price"></span></div>
<script>
    const eventSource = new EventSource('/getPrice');
    eventSource.onmessage = function(e){ // 拿到接受到的数据
        price.innerHTML = e.data;
    }
</script>
app.get("/getPrice", (req, res, next) => {res.header('Content-Type','text/event-stream',);
    timer = setInterval(()=>{
        res.write( // 发送 message 事件 \n\n 表示当前的 event-stream 通信结束
            `event:message\nid:${id++}\ndata:${currentPrice*Math.random()}\n\n`
        );
    },1000);
    res.on('close',()=>{clearInterval(timer);
    });
});

当然这种方式依旧是单向的,主要是服务端向客户端推送数据。并且兼容性也不是很美丽~

WebSocket

终于等到你! 双向通信的 WebSocket 让你欲罢不能!

WebSocket 让客户端和服务器保有一个持久的连接,两边可以在任意时间开始发送数据!它是基于 TCP 协议的:

先来聊聊 WebSocket 的优势!

  • http 协议不支持双向通信 -> 我支持双向通信
  • http 协议数据包头部较大 -> 我的 header 很小!我最少只需两个字节
  • http 不支持跨域 -> 我支持跨域,哈哈!

ws 模块

ws: a Node.js WebSocket library,ok 就是在 node 中可以使用的 WebSocket 库!

安装 ws 模块

yarn add ws

服务端开启 WebSocket 服务

const WebSocketServer = require('ws').Server;
const ws = new WebSocketServer({port:8888});
ws.on('connection',(socket)=>{ // socket 链接我的那个人
    console.log('服务端: 有人链接我!');
    socket.on('message',(data)=>{console.log(data); // 收到客户端发来的消息
        socket.send('我是服务端'); // 给客户端发消息
    });
});

客户端链接 8888 端口的 ws 服务!

const socket = new WebSocket('ws://localhost:8888');
socket.onopen = function(){ // 链接成功后, 发送消息
    console.log('客户端: 链接成功');
    socket.send('我是客户端');
}
socket.onmessage = function(e){ // 监听客户端发来的信息
    console.log(e.data);
}

客户端和服务端可以开心的互相通信啦!

socket.io

socket.io 是一个 WebSocket 库,包括了客户端的 js 和服务器端的 nodejs, 刚才是不是高兴的太早了而忘记了兼容性问题?没错 socket.io 就是帮你解决自动根据浏览器从 WebSocket、AJAX 长轮询、Iframe 流等等各种方式中选择最佳的方式来实现网络实时应用!

安装 socket.io 模块

yarn add socket.io

通过 socket.io 建立链接

const express = require("express");
const app = express();
app.use(express.static(__dirname))
const server = require('http').createServer(app); // app 本身就是监听函数
// socket 需要借助 http 服务
const io = require('socket.io')(server);
// 划分路径 /
io.of('/').on('connection',function(socket){console.log('链接成功')
    socket.on('message',function(msg){console.log(msg);
        socket.send('我是服务端');
    });
});
// 监听 3000 端口
server.listen(3000);
// 默认会像浏览器中注入 socket.io.js 脚本
<script src="/socket.io/socket.io.js"></script>
<script>
    const socket = io.connect('/');
    socket.on('connect',()=>{console.log('链接成功');
        socket.send('我是客户端');
    });
    // 接收到消息后打印出来
    socket.on('message',(data)=>{console.log(data);
    });
</script>

我们有了 socket.io 实现双向通信是不是很简单!


觉得本文对你有帮助吗?请分享给更多人

关注「前端优选」加星标,提升前端技能

关注公众号,获得更多前端高级技能
加我微信:webyouxuan

退出移动版