websocket通信步骤

当客户端要和服务端建立 WebSocket 连接时,在客户端和服务器的握手过程中,客户端首先会向服务端发送一个 HTTP 请求,包含一个 Upgrade 请求头来告知服务端客户端想要建立一个 WebSocket 连接。

在客户端建立一个 WebSocket 连接非常简单:

  1. let ws = new WebSocket('ws://localhost:9000');

类似于 HTTP 和 HTTPS,ws 相对应的也有 wss 用以建立安全连接,本地已 ws 为例。这时的请求头如下:

  1. Accept-Encoding: gzip, deflate, br
  2. Accept-Language: zh-CN,zh;q=0.9
  3. Cache-Control: no-cache
  4. Connection: Upgrade // 表示该连接要升级协议
  5. Cookie: _hjMinimizedPolls=358479; ts_uid=7852621249; CNZZDATA1259303436=1218855313-1548914234-%7C1564625892; csrfToken=DPb4RhmGQfPCZnYzUCCOOade; JSESSIONID=67376239124B4355F75F1FC87C059F8D; _hjid=3f7157b6-1aa0-4d5c-ab9a-45eab1e6941e; acw_tc=76b20ff415689655672128006e178b964c640d5a7952f7cb3c18ddf0064264
  6. Host: localhost:9000
  7. Origin: http://localhost:9000
  8. Pragma: no-cache
  9. Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  10. Sec-WebSocket-Key: 5fTJ1LTuh3RKjSJxydyifQ== // 与响应头 Sec-WebSocket-Accept 相对应
  11. Sec-WebSocket-Version: 13 // 表示 websocket 协议的版本
  12. Upgrade: websocket // 表示要升级到 websocket 协议
  13. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36

响应头如下:

  1. Connection: Upgrade
  2. Sec-WebSocket-Accept: ZUip34t+bCjhkvxxwhmdEOyx9hE=
  3. Upgrade: websocket

websocket实现聊天室 - 图1
此时响应行(General)中可以看到状态码 status code 是 101 Switching Protocols , 表示该连接已经从 HTTP 协议转换为 WebSocket 通信协议。 转换成功之后,该连接并没有中断,而是建立了一个全双工通信,后续发送和接收消息都会走这个连接通道。
注意,请求头中有个 Sec-WebSocket-Key 字段,和相应头中的 Sec-WebSocket-Accept 是配套对应的,它的作用是提供了基本的防护,比如恶意的连接或者无效的连接。Sec-WebSocket-Key 是客户端随机生成的一个 base64 编码,服务器会使用这个编码,并根据一个固定的算法:

  1. GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // 一个固定的字符串
  2. accept = base64(sha1(key + GUID)); // key 就是 Sec-WebSocket-Key 值,accept 就是 Sec-WebSocket-Accept 值

其中 GUID 字符串是 RFC6455 官方定义的一个固定字符串,不得修改。
客户端拿到服务端响应的 Sec-WebSocket-Accept 后,会拿自己之前生成的 Sec-WebSocket-Key 用相同算法算一次,如果匹配,则握手成功。然后判断 HTTP Response 状态码是否为 101(切换协议),如果是,则建立连接,大功告成。


实现简单单聊

下面来实现一个纯文字消息类型的一对一聊天(单聊)功能,废话不多说,直接上代码,注意看注释。
客户端:

  1. function connectWebsocket() {
  2. ws = new WebSocket('ws://localhost:9000');
  3. // 监听连接成功
  4. ws.onopen = () => {
  5. console.log('连接服务端WebSocket成功');
  6. ws.send(JSON.stringify(msgData)); // send 方法给服务端发送消息
  7. };
  8. // 监听服务端消息(接收消息)
  9. ws.onmessage = (msg) => {
  10. let message = JSON.parse(msg.data);
  11. console.log('收到的消息:', message)
  12. elUl.innerHTML += `<li class="b">小秋:${message.content}</li>`;
  13. };
  14. // 监听连接失败
  15. ws.onerror = () => {
  16. console.log('连接失败,正在重连...');
  17. connectWebsocket();
  18. };
  19. // 监听连接关闭
  20. ws.onclose = () => {
  21. console.log('连接关闭');
  22. };
  23. };
  24. connectWebsocket();

从上面可以看到 WebSocket 实例的 API 很容易理解,简单好用,通过 send() 方法可以发送消息,onmessage 事件用来接收消息,然后对消息进行处理显示在页面上。 当 onerror 事件(监听连接失败)触发时,最好进行执行重连,以保持连接不中断。
服务端 Node : (这里使用 ws 库)

  1. const path = require('path');
  2. const express = require('express');
  3. const app = express();
  4. const server = require('http').Server(app);
  5. const WebSocket = require('ws');
  6. const wss = new WebSocket.Server({ server: server });
  7. wss.on('connection', (ws) => {
  8. // 监听客户端消息
  9. ws.on('message', (message) => {
  10. let msgData = JSON.parse(message);
  11. if (msgData.type === 'open') {
  12. // 初始连接时标识会话
  13. ws.sessionId = `${msgData.fromUserId}-${msgData.toUserId}`;
  14. } else {
  15. // 区分每一个客户端发过来的消息
  16. let sessionId = `${msgData.toUserId}-${msgData.fromUserId}`;
  17. wss.clients.forEach(client => {
  18. if (client.sessionId === sessionId) {
  19. client.send(message);
  20. }
  21. })
  22. }
  23. })
  24. // 连接关闭
  25. ws.on('close', () => {
  26. console.log('连接关闭');
  27. });
  28. });
  29. app.get('/', function (req, res) {
  30. res.sendFile(path.join(__dirname, '../src/client1.html'));
  31. })
  32. app.get('/2', function (req, res) {
  33. res.sendFile(path.join(__dirname, '../src/client2.html'));
  34. })
  35. server.listen(9000, function () {
  36. console.log('http://localhost:9000');
  37. });

同理,服务端也有对应的发送和接收的方法。完整示例代码见 这里
这样浏览器和服务端就可以愉快的发送消息了,效果如下:
websocket实现聊天室 - 图2
其中绿色箭头表示发出的消息,红色箭头表示收到的消息。


总结

websocket实现聊天室 - 图3
通过上面的介绍,大家应该对 WebSocket 有了一定认识,其实并不神秘,这里对文章内容简单总结一下。当创建 WebSocket 实例的时候,会发一个 HTTP 请求,请求报文中有个特殊的字段 Upgrade ,然后这个连接会由 HTTP 协议转换为 WebSocket 协议,这样客户端和服务端建立了全双工通信,通过 WebSocket 的 send 方法和 onmessage 事件就可以通过这条通信连接交换信息。


备份代码🤓

https://github.com/withwz/WebSocket

作者:政采云前端团队
链接:https://juejin.im/post/5dd4b991e51d450818244c30
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。