服务器与客户端实时通信的三种方式

实时通信,让交互更简单

Posted by Shiyanping on January 2, 2019

我们在做聊天室的时候,会需要用到实时通信,当服务端向客户端提前声明,发送的是流信息的时候,HTTP 协议可以允许服务器向客户端发送消息,因为一次性发送不完,需要连续不断的发送,客户端不会关闭连接,会一直等着服务器发送新的数据流。

服务端与客户端的实时通信,主要有以下三种解决方案:

轮询

前端可以在自己的页面进行轮询(定时)调用后端的接口,然后触发后端给前端返回消息,虽然可以解决前后端实时推送的数据问题,但是这样做并不是一个最优解,使用 WebSocket 和 SSE 更好一点。

WebSocket

WebSocket 是一个全双工通道,客户端可以给服务端发送消息,并且可以接收服务端的消息,服务端也可以给客户端发送消息,接收客户端的消息。

WebSocket 有很多特点:

  1. 建立在 TCP 协议之上,服务器端的实现比较容易
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。
  6. 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。

像直播的聊天室都是 WebSocket 做的。我们在使用 WebSocket 的时候,可以使用 socket.io 这个库,会方便很多。在前端页面需要依靠 socket.io.js,node 要依靠socket.io的库。

用一个简单的 socket.io 的例子,实现一下精简版的互相通信。废话不多说,上代码:

server 端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Koa = require('koa');
const app = new Koa();
const server = require('http').Server(app.callback());
const io = require('socket.io')(server);
const port = 8081;

server.listen(process.env.PORT || port, () => {
  console.log(`app run at : http://127.0.0.1:${port}`);
});

io.on('connection', socket => {
  console.log('初始化成功!下面可以用socket绑定事件和触发事件了');
  socket.on('send', data => {
    console.log('客户端发送的内容:', data);
    socket.emit('getMsg', '我是返回的消息... ...');
  });

  setInterval(() => {
    socket.emit('getMsg', '我是初始化3s后的返回消息... ...');
  }, 3000);
});

client 端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>websocket示例</title>
  </head>
  <body>
    <button id="send">发送消息到服务器</button>
    <div>
      <h3>服务器响应的消息:</h3>
      <i id="msg"></i>
    </div>

    <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
    <script>
      var socket = io('ws://localhost:8081');
      var send = document.querySelector('#send');
      var msg = document.querySelector('#msg');

      socket.on('getMsg', data => {
        console.log('服务端消息:', data);
        msg.innerHTML += `${data} <br/>`;
      });

      socket.on('message', data => {
        console.log('服务端消息:', data);
        // msg.innerHTML += `${data} <br/>`;
      });

      send.onclick = () => {
        console.log('点击了发送消息!');
        socket.emit('send', 'hello');
      };
    </script>
  </body>
</html>
  • 前端通过 socket.on(‘事件名’, callback)监听到后端的触发
  • 前端通过 socket.emit(‘事件名’, ‘消息体’)向后端发送消息
  • 后端通过 socket.emit(‘事件名’, ‘消息体’)向前端发送消息
  • 后端通过 socket.on(‘事件名’, callback)接收前端发送来的消息
  • 后端通过 socket.on(‘disconnect’)监听退出事件
  • 后端可以通过 socket.broadcast.emit(‘message’, ‘消息内容’)进行广播

SSE(Server-Sent Events)

SSE 相对于 WebSocket 来说,没有 WebSocket 那么强大,因为 WebSocket 是全双工通道,但是 SSE 是单向通道,只能服务器向浏览器发送信息。

但是 SSE 相对 WebSocket 来说,也有自己的优点:

  1. 轻量级,使用简单,没有 WebSocket 的协议那么复杂
  2. 默认断线重连,WebSocket 需要自己去实现
  3. SSE 一般用来发送文本,如果发送二进制数据,需要编码后传输,WebSocket 默认支持传送二进制数据

具体的代码示例可以参考 http://javascript.ruanyifeng.com/htmlapi/eventsource.html