# WebSocket 示例

# 1、服务器(nodejs)

var WebSocketServer = require('ws').Server;

var wssWeb = new WebSocketServer({ port: 3000 });
wssWeb.on('connection', function connection(ws) {
  
  // 获取客户端ip
  // console.log(req.headers['x-forwarded-for'])
  // console.log(ws._socket.remoteAddress)
  // console.log(req.socket.remoteAddress)

  ws.on('message', function message(data, isBinary) {
    if (!isBinary) {
      // 是否为二进制数据
      const str = data.toString();
      const json = JSON.parse(str);

      if (json.type === 'heartbeat') {
        ws.send(str); // 回复心跳
      }

      if (json.type === 'conn') {
        // 新连接
        ws.userName = json.userName; // 用户名
        ws.roomId = json.roomId; // 房间id
      }

      if (json.type === 'text') {
        sendBroadcast(ws, str, isBinary);
      }

      if (json.type === 'user') {
        sendUser(json.userName, str);
      }
    } else {
      // 发送二进制数据
      sendBroadcast(ws, data, isBinary);
    }
  });
});

// 向房间内广播(除自己)
function sendBroadcast(ws, data, binary) {
  wssWeb.clients.forEach(client => {
    if (
      client.roomId === ws.roomId &&
      client.userName !== ws.userName &&
      client.readState === 1
    ) {
      client.send(data, { binary });
    }
  });
}

// 发送给指定用户
function sendUser(userName, data) {
  wssWeb.clients.forEach(client => {
    if (client.userName === userName && client.readState === 1) {
      client.send(data);
    }
  });
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 2、客户端(浏览器页面)

const protocols = location.protocol === 'http:' ? 'ws://' : 'wss://';
const url = protocols + location.host;
const ws = new WebSocketHeart({ url });
ws.onopen = () => {
  ws.send(
    JSON.stringify({ type: 'conn', userName: 'xiaoming', roomId: '10001' }),
  ); // 发送连接信息
};
ws.onmessage = evt => {
  if (typeof evt.data == 'string') {
    const json = JSON.parse(evt.data);

    // 处理心跳
    if (json.type === 'heartbeat') {
      console.log(json);
    }

    // 接收 text 广播信息
    if (json.type === 'text') {
      console.log(json);
    }

    // 接收来自 json.userName 的私信
    if (json.type === 'user') {
      console.log(json);
    }
  } else {
    // 解析二进制数据
  }
};

// 广播信息
function sendText(msg) {
  const str = JSON.stringify({ type: 'text', data: msg });
  ws.send(str);
}

// 给指定的人发送信息
function sendUser(userName, msg) {
  const str = JSON.stringify({ type: 'user', data: msg, userName });
  ws.send(str);
}
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
39
40
41
42

# 2.1 封装 WebSocket 库

WebSocketHeart.js

export default class WebSocketHeart {
  constructor({ url, pingTimeout, pongTimeout, reconnectTimeout, pingMsg }) {
    this.url = url; // websocket服务端接口地址
    this.pingTimeout = pingTimeout || 30000; // 每隔 30 秒发送一次心跳,如果收到任何后端消息定时器将会重置
    this.pongTimeout = pongTimeout || 10000; // ping 消息发送之后,10 秒内没收到后端消息便会认为连接断开
    this.reconnectTimeout = reconnectTimeout || 30000; // 尝试重连的间隔时间
    this.pingMsg = pingMsg || '{"type":"heartbeat"}'; // 发送心跳消息
    this.ws = null; // websocket 实例

    // 回调钩子
    this.onclose = () => {};
    this.onerror = () => {};
    this.onopen = () => {};
    this.onmessage = () => {};
    this.onreconnect = () => {};

    this.createWebSocket();
  }

  // 创建 WebSocket 实例
  createWebSocket() {
    try {
      this.ws = new WebSocket(this.url);
      this.initEventHandle();
    } catch (e) {
      this.reconnect();
      throw e;
    }
  }

  // 初始化事件钩子
  initEventHandle() {
    this.ws.onclose = () => {
      this.onclose();
      this.reconnect();
    };
    this.ws.onerror = () => {
      this.onerror();
      this.reconnect();
    };
    this.ws.onopen = () => {
      this.onopen();
      //心跳检测重置
      this.heartCheck();
    };
    this.ws.onmessage = event => {
      this.onmessage(event);
      //如果获取到消息,心跳检测重置
      //拿到任何消息都说明当前连接是正常的
      this.heartCheck();
    };
  }

  // 重连
  reconnect() {
    if (this.lockReconnect || this.forbidReconnect) return;
    this.lockReconnect = true;
    this.onreconnect();
    //没连接上会一直重连,设置延迟避免请求过多
    setTimeout(() => {
      this.createWebSocket();
      this.lockReconnect = false;
    }, this.reconnectTimeout);
  }

  // 发送消息
  send(msg) {
    this.ws.send(msg);
  }

  // 心态检测
  heartCheck() {
    this.heartReset();
    this.heartStart();
  }

  // 心态重置
  heartReset() {
    clearTimeout(this.pingTimeoutId);
    clearTimeout(this.pongTimeoutId);
  }

  // 发送心跳
  heartStart() {
    if (this.forbidReconnect) return; //不再重连就不再执行心跳
    this.pingTimeoutId = setTimeout(() => {
      //这里发送一个心跳,后端收到后,返回一个心跳消息,
      // onmessage 拿到返回的心跳就说明连接正常
      this.ws.send(this.pingMsg);
      //如果超过一定时间还没重置,说明后端主动断开了
      this.pongTimeoutId = setTimeout(() => {
        //如果 onclose 会执行 reconnect,我们执行 ws.close() 就行了.如果直接执行 reconnect 会触发 onclose 导致重连两次
        this.ws.close();
      }, this.pongTimeout);
    }, this.pingTimeout);
  }

  // 手动关闭
  close() {
    //如果手动关闭连接,不再重连
    this.forbidReconnect = true;
    this.heartReset();
    this.ws.close();
  }
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
上次更新: 3/5/2023, 7:47:10 PM