浅谈 WebSocket

使用 WebSocket 的理由

传统的http协议有一个根本性的缺陷,那就是请求只能由客户端向服务器发起,服务器接收到请求后再进行响应,把数据返回给客户端。也就是说,服务器是没有办法主动向客户端传送消息的。

这样一来,如果服务器有状态是频繁变化的,那么,客户端想要实时获悉这些状态势必非常麻烦。如在线多人游戏,聊天室等。

一种可行的解决方案是使用轮询。轮询是指浏览器通过JavaScript启动一个定时器,然后以固定的间隔给服务器发请求,询问服务器有没有新消息。

这种机制不仅效率低下,实时性不够,而且频繁地发起请求也会给服务器带来极大的压力。

另外一种比较靠谱的技术是HTML5的EventSource。EventSource 接口用于接收服务器发送的事件。它通过HTTP连接到一个服务器,以text/event-stream 格式接收事件, 不关闭连接。

相对于WebSocket,这种技术要简单很多,但是其只是从服务器端往客户端单向传输数据,并不能实现真正意义上的全双工通信。因此,WebSocket是目前来说最佳的选择。

有兴趣了解EventSource的小伙伴可以点击这里

WebSocket 协议

WebSocket是HTML5新增的协议,其诞生于2008年。最大特点就是,服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送信息,是一种不受限的全双工通信。

《浅谈 WebSocket》

该协议有以下特征:

  1. 握手阶段利用了HTTP协议来建立连接,因此WebSocket连接必须由浏览器发起。
  2. 建立在 TCP 协议之上,服务器端的实现比较容易。
  3. 其可以接收和发送的数据有两种,一种是文本,一种是二进制数据(blob对象或Arraybuffer对象)。一般情况下,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。
  4. 数据格式比较轻量,性能开销小,通信高效。
  5. 请求是以ws://为开头的地址(如果加密,则为wss://)。
  6. WebSocket协议本身不要求同源策略,也就是某个地址为”http://a.com”的网页可以通过WebSocket连接到”ws://b.com”。但是,浏览器会发送Origin的HTTP头给服务器,服务器可以根据Origin拒绝这个WebSocket请求。

《浅谈 WebSocket》

《浅谈 WebSocket》

浏览器支持情况

  • Chrome
  • Firefox
  • IE >= 10
  • Sarafi >= 6
  • Android >= 4.4
  • iOS >= 8

服务端实现

不同的编程语言和框架,实现方式各有不同。这里主要讲一下用node如何实现。

node常用的实现有一下几种:

具体如何使用可以查看它们各自的api。下面,我要详细介绍的是另一个WebSocket模块ws。

通过npm install ws –save之后,我们就可以可以编写一个简单WebSocket服务器程序。

// 首先导入ws模块
let WebSocket = require('ws');

// 通过ws模块的Server类实例化一个websocket服务器
let webSocketServer = new WebSocket.Server({
  port: 8030
}, err => {
  console.log('The WebSocket Server already running on: 8030');
});

// 监听客户端请求接入的connection事件,连接建立后,回调函数中会传入这个WebSocket连接实例
webSocketServer.on('connection', ws => {
  console.log(`Server is connected`)
  // 对于每个WebSocket连接,可以绑定监听某些事件来进行不同的处理。这里,通过响应message事件,在收到客户端发来消息后再返回一个消息过去。
  ws.on('message', mes => {
    console.log(`Message sent by client: ${mes}`);
    ws.send(`data responded by Server: ${mes}`, err => {
      if (err) {
        console.log(`Server error: ${err}`);
      }
    })
  })
})

也可以对http服务器进行拓展,在其基础上建立WebSocket服务器。

const Koa = require('koa');
const WebSocket = require('ws');
const bodyParser = require('koa-bodyparser');
const controller = require('./controller.js');

const server = new Koa();

const webSocketServer = new WebSocket.Server({server}, () => console.log('The WebSocket Server already running on: 8030'));

// 为websocket服务器添加一个broadcast()方法
webSocketServer.broadcast = data => {
    // 通过遍历webSocketServer.clients,找到所有与该服务器成功建立websocket连接的客户端,发送同一条消息
    for (const client of webSocketServer.clients) {
        if (client.readyState === WebSocket.OPEN) {
            client.send(data, err => console.log(`Server error: ${err}`));
        }
    }
}

webSocketServer.on('connection', ws => {
    console.log(`Server is connected`);
    ws.on('message', mes => {
        console.log(`Message sent by client: ${mes}`);
        // 接受到其中一个客户端发来的消息后,广播给所有同时连接过来的客户端
        const data = {
            message: mes
        }
        webSocketServer.broadcast(JSON.stringify(data))
    })
})

server.use(bodyParser());

server.use(controller());

server.listen(8030);
console.log('server running on 8030...');

现在,websocket服务器与http服务器同时使用8030端口。当有一个请求发送过来,首先会判断其是否ws请求。若是,则交给WebSocketServer的回调函数处理,否则,还是走正常的http server回调的路子。

另外,我们注意到,这里还给WebSocketServer添加了一个broadcast()方法,用于将消息广播到所有与该服务器成功建立WebSocket连接的客户端上。

每当从其中一个客户端收到一条消息,就将该消息发送到所有WebSocket连接上,基于这种方式,我们就可以搭建一个聊天室应用的后台服务。

ws模块的完整使用方法请看这里

客户端实现

客户端要创建一个WebSocket连接就比较简单了。下面是一个简单的事例:

// 创建一个WebSocket连接:
var ws = new WebSocket('ws://localhost:8030/ws');

ws.onopen = function(event) { 
  console.log("Connection open"); 
  // 给服务器发送一个消息:
  ws.send("Hello WebSocket!");
};

// 响应onmessage事件:
ws.onmessage = function(event) { 
  console.log(event.data); 
};

// 也可以指定接收的二进制数据类型为blob对象
ws.binaryType = "blob";
ws.onmessage = function(event) {
  console.log(event.data.size);
};

// 或

// 指定接收的二进制数据类型为ArrayBuffer对象
ws.binaryType = "arraybuffer";
ws.onmessage = function(event) {
  console.log(event.data.byteLength);
};

详细的属性和方法可以看这里

另外,使用ws模块提供的WebSocket构造函数也可以充当客户端创建连接。

let ws = new WebSocket('ws://localhost:8030/ws');

// 打开WebSocket连接后立刻发送一条消息
ws.on('open', () => {
    console.log(`Client open`);
    ws.send('Hello WebSocket!');
});

// 响应收到的消息
ws.on('message', mes => {
    console.log(mes);
});

参考链接

    原文作者:骏如皓月
    原文地址: https://segmentfault.com/a/1190000017258139
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞