《 Socket.IO》 解决 WebSocket 通信!

小菜良记

共 6768字,需浏览 14分钟

 · 2022-03-18

大家好呀,我是小菜~

本文主要介绍 Socket.IO

微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

在介绍 Socket.IO 之前, 我们先考虑一个问题, 如果这个时候有个需求, 类似实现人工客服的功能该如何实现?

在线客服,需求理解起来很简单,就相当于一个 web 的聊天页面,也就是客户端能够 即时拉取到服务端的响应

当然, 作为接口工程师, 这并不是一个很难解决的问题, 我们可以提供一个获取聊天记录的接口, 通过该接口我们可以获取到对方已经发送到消息. 那么问题又来了, 如何保证能够 即时 的获取到聊天记录呢? 想必这也不是问题, 前端可以通过定时器的方式, 将间隔时间缩短到  100  毫秒, 这样子就已经实现了近实时的获取消息

setInterval(function (
 // do something
},100)

当我们写完以上代码上线后, 却通过监控可以发现, 上线后的服务器指标明显比之前有所提升

服务器是十分珍贵的资源, 那么为什么会发生这种情况呢? 回过头一想, 会发生这种情况也无可厚非, 每 100 毫秒就请求一次后端, 如果有聊天记录产生, 那么这种请求就认为是有意义的, 但如果长时间未聊天, 每次请求返回都是空记录, 那么这种频繁请求就是无意义的. 频繁请求会使服务器压力增大, 并且浪费带宽流量.

那么有没有别的方式可以解决?

我们也许可以使用  SSE  方式,  SSE  并不是一个什么比较新颖的概念, 它出现的时间也很早

SSE 全称 Server-Sent Events,指的是网页自动获取来自服务器的更新,也就是自动化获取服务端推送至网页的数据,这是一个 H5 的属性,除了 IE,其他标准浏览器基本都兼容

这种方式不需要客户端定时去获取,而是服务端向客户端声明要发送流信息,然后连续不断地发送过来

尽管这种方式不需要定时轮询, 但是它只能单工通信,建立连接后,只能由服务端发往客户端,且需要占用一个连接,如果需要客户端向服务端通信,那么需要额外再打开一个连接!

如果连接数过多会导致什么问题?

TCP 的连接数是有限的, SYN DDOS 洪水攻击, 就是利用 TCP 半连接的问题来攻击服务器

因此这也不是一种优雅的实现方式

其实到这里, 我们解决的思路已经很明确了, 就是在不浪费带宽的情况下如何让服务端将最新的消息以最快的速度发送给客

户端. 但是明显 HTTP 协议不适用, 它是会在服务端收到请求后才会做出回应. 因此为了解决这个问题, 那么就需要就需要讲到一种通信协议, 那就是 WebSocket

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。建立一个 WebSocket 连接,客户端会发送一个 WebSocket 握手请求,服务器为此返回一个 WebSocket 握手响应,如下图所示。

相比于传统 HTTP 的每次 请求-应答 都要客户端与服务端建立连接的模式, websocket 是一种  长连接 的模式, 一旦建起 websocekt 连接, 除非 client 或者 server 中有一端主动断开连接, 否则每次数据传输之前都不需要 HTTP 那样请求数

客户端请求

Upgrade: websocket
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
  • Upgrade 是为了表明这是一个 websocekt 类型请求, 意在告诉 server 需要将通信协议切换到 websocekt
  • Sec-WebSocket-Key是 client 发送的一个 base64 编码的密文 ,要求服务器用 Sec-WebSocket-Accept 头部中的密钥散列作为响应。这是为了防止缓存代理重新发送以前的 WebSocket 对话,并且不提供任何身份验证、隐私或完整性。

服务端响应

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

握手从 HTTP 请求/响应开始,允许服务器在同一端口处理 HTTP 连接和 WebSocket 连接。一旦连接建立起来,通信就换到不符合 HTTP 协议的双向二进制协议。

图源网, 侵删

到这里其实方案已经出来了, 但是我们这篇文章的标题却是 Socket.IO, 既然都有了 Websocket, 为什么我们讲的是 Socket.IO ?

Socket.IO

在大家往下看之前先清楚这么一个观点:

Socket.IO 不是替代, 而是升级

Socket.IO 是一个库, 说到库其实我们都不陌生, 库是对已有的功能进行封装, 没错, 它是构建在 WebSocket 协议之上, 并提供额外的保证, 既然它是构建在 websocekt 之上, 说明它同样具有客户机与服务器之间延迟通信的功能.

Socket.IO可用于实现以下几种通信方式:

  • HTML 5中的WebSocket通信
  • 可在Flash中使用的WebSocket通信
  • XHR轮询
  • JSONP轮询
  • Forever Iframe

Socket.IO确保在实现这些通信方式时,客户端与服务器端可以使用相同的API。并具备以下特性:

  • HTTP 长轮询回退

如果不能建立 WebSocket 连接,连接将退回到 HTTP 长轮询。

  • 自动重新连接

在某些特定条件下,服务器和客户端之间的 WebSocket 连接可能会被中断,双方都不知道链接的断开状态。而 Socket.IO 包含一个 heartbeat 机制的原因,该机制定期检查连接的状态.当客户端最终断开连接时,它会自动重新连接,并且会出现指数级的回退延迟,以免压垮服务器

  • 数据包缓冲

当客户端断开连接时,数据包将自动缓冲,并在重新连接时发送

既然 Socket.IO 如此的美妙, 那么它该如何使用呢? 那么接下来就让我们创建一个自己的聊天室吧 !

本案例采用 NodeJS 环境搭建, 极其简单, 有条件的可以上手一试

聊天室

准备前提:

  • 确保安装了 Node.js 环境
  • 准备一个空文件夹

准备步骤很简单, 接下来我们就开始创建我们自己的聊天室

1. 创建 package.json 文件

我们在空目录下创建 package.json 文件, 内容如下:

{  
    "name""c-chat",  
    "version""0.0.1",  
    "description""my first chat app",  
    "dependencies": {}
}

在当前目录执行命令  npm install express  安装web应用开发框架

2. 创建 index.js & index.html

在空目录下创建 index.js 文件, 内容如下:

const app = require('express')();
const http = require('http').Server(app);

app.get('/', (req, res) => {
   res.sendFile(__dirname + '/index.html');
});

http.listen(port, () => {
   console.log(`${port} 端口监听成功`);
});

接着创建 index.html 文件, 内容如下

html>
<html>
 <head>
   <title>my chattitle>
   <style>
     body { margin0padding-bottom3remfont-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

     #form { backgroundrgba(0000.15); padding0.25remposition: fixed; bottom0left0right0display: flex; height3rembox-sizing: border-box; backdrop-filterblur(10px); }
     #input { border: none; padding0 1remflex-grow1border-radius2remmargin0.25rem; }
     #input:focus { outline: none; }
     #form > button { background#333border: none; padding0 1remmargin0.25remborder-radius3pxoutline: none; color#fff; }

     #messages { list-style-type: none; margin0padding0; }
     #messages > li { padding0.5rem 1rem; }
     #messages > li:nth-child(odd) { background#efefef; }
   
style>
 head>
 <body>
   <ul id="messages">ul>
   <form id="form" action="">
     <input id="input" /><button>Sendbutton>
   form>
 body>
html>

为了测试 http 服务与我们的页面是否有效, 我们可以利用  node index.js  启动项目来验证

到这里, 我们就已经能够成功访问到我们的页面, 接下来就开始通过 socket.io 来实现我们的聊天功能

3. 安装 socket.io 库

npm install socket.io 首先就需要执行以上命令来安装 socket.io

现在离目标已经实现一大半了

我们只需要修改部分内容便可以看到我们想要的效果

服务端
const { Server } = require("socket.io");
const io = new Server(server);

以上代码是为了引入 socket.io库, 并创建 websocket 服务, 然后便可以建立 socket 监听

io.on('connection', (socket) => {console.log('连接建立成功');});

在一个Socket.IO服务器创建之后,当客户端与服务器端建立连接时,触发Socket.IO服务器的connection事件,可以通过监听该事件并指定事件回调函数的方法指定当客户端与服务器端建立连接时所需执行的处理

客户端

在 index.html 页面, 我们添加以下代码来引入 socket.io.js, 并创建 socket 对象

"/socket.io/socket.io.js"></script>