Node.js精进(11)——Socket.IO
<p> <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">Socket.IO</span></span> 是一个建立在 WebSocket 协议之上的库,可以在客户端和服务器之间实现低延迟、双向和基于事件的通信。</p><p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609131531345-1405670624.png" width="600"></p>
<p> 并且提供额外的保证,例如回退到 HTTP 长轮询、自动重连、数据包缓冲、多路复用等。</p>
<p> <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">WebSocket</span></span> 是一种基于 TCP 协议在服务器和浏览器之间提供全双工和低延迟通道的通信协议。</p>
<p> 注意,Socket.IO 不是 WebSocket 的实现。尽管 Socket.IO 确实在可能的情况下使用 WebSocket 进行传输,但它为每个数据包添加了额外的元数据。</p>
<p> 这就是为什么 WebSocket 客户端将无法成功连接到 Socket.IO 服务器,而 Socket.IO 客户端也将无法连接到普通的 WebSocket 服务器。</p>
<p> 如果需要一个普通的 WebSocket 服务器,可以使用 <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">ws</span></span> 或 <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">µWebSockets.js</span></span>。</p>
<p> 在 Socket.IO 的底层依赖 Engine.IO 引擎,它是跨浏览器/跨设备双向通信层的实现,可处理各种传输、<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">升级机制</span></span>和断线检测等。</p>
<p> 刚刚所说的自动重连、数据包缓冲、多路复用等附加功能都是 Engine.IO 引擎提供的能力。</p>
<p> 本系列所有的示例源码都已上传至Github,<span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">点击此处</span></span>获取。</p>
<h1>一、广播</h1>
<p> 现在来建立一个提供表单和消息列表的简单 HTML 网页,用 Socket.IO 广播消息(如下图所示),并且可以在页面中呈现消息内容。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609131729601-2028365120.png" width="400"></p>
<p><span style="font-size: 16px"><strong>1) HTTP 服务器</strong></span></p>
<p> 首先安装 socket.io 包:npm install socket.io。</p>
<p> 然后创建一个 <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">HTTP 服务器</span></span>,用于接收 HTML 和 JavaScript 文件的请求,内部实现了个简单的路由。</p>
<p> 其中 <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">URL</span></span> 实例用于解析请求地址,最终响应的内容是通过 <span style="color: rgba(51, 102, 255, 1)"><span style="color: rgba(51, 102, 255, 1)">fs.readFileSync()</span></span> 同步读取到的。</p>
<p> index.html 文件的内容会在后文给出,socket.io.js 是从 node_modules/socket.io/client-dist/socket.io.js 目录中复制过来的。</p>
<div class="cnblogs_code">
<pre>const http = require('http'<span style="color: rgba(0, 0, 0, 1)">);
const fs </span>= require('fs'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> HTTP服务器</span>
const server = http.createServer((req, res) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实例化 URL 类</span>
const url = <span style="color: rgba(0, 0, 255, 1)">new</span> URL(req.url, 'http://localhost:1234'<span style="color: rgba(0, 0, 0, 1)">);
const { pathname } </span>=<span style="color: rgba(0, 0, 0, 1)"> url;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 路由</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(pathname === '/'<span style="color: rgba(0, 0, 0, 1)">) {
res.writeHead(</span>200, { 'Content-Type': 'text/html'<span style="color: rgba(0, 0, 0, 1)"> });
res.end(fs.readFileSync(</span>'./index.html'<span style="color: rgba(0, 0, 0, 1)">));
}</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span>(pathname === '/socket.io.js'<span style="color: rgba(0, 0, 0, 1)">) {
res.writeHead(</span>200, { 'Content-Type': 'application/javascript'<span style="color: rgba(0, 0, 0, 1)"> });
res.end(fs.readFileSync(</span>'../socket.io.js'<span style="color: rgba(0, 0, 0, 1)">));
}
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监控端口</span>
server.listen(1234);</pre>
</div>
<p><span style="font-size: 16px"><strong>2)Socket.IO 服务器</strong></span></p>
<p> 接着是创建 Socket.IO 服务器,其中 socket.id 是每个新连接都会被分配到的一个随机的 20 个字符的标识符,此标识符与客户端的值同步。</p>
<p> connection 是建立连接时的事件,disconnect 是断开连接时的事件,chat message 是注册的接收消息的自定义事件。</p>
<div class="cnblogs_code">
<pre>const { Server } = require("socket.io"<span style="color: rgba(0, 0, 0, 1)">);
const io </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Server(server);
io.on(</span>'connection', (socket) =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'id'<span style="color: rgba(0, 0, 0, 1)">, socket.id);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> socket.broadcast.emit('hi');// 广播给其他人,除了自己</span>
console.log('a user connected'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册断开连接事件</span>
socket.on('disconnect', () =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'user disconnected'<span style="color: rgba(0, 0, 0, 1)">);
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册接收消息事件</span>
socket.on('chat message', (msg) =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'message: ' +<span style="color: rgba(0, 0, 0, 1)"> msg);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 触发事件</span>
io.emit('chat message'<span style="color: rgba(0, 0, 0, 1)">, msg);
});
});</span></pre>
</div>
<p><span style="font-size: 16px"><strong>3)广播页面</strong></span></p>
<p> 在广播页面中,先给出 HTML 结构和 CSS 样式,在表单中有一个按钮和文本框,如下图所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)"><!</span><span style="color: rgba(255, 0, 255, 1)">DOCTYPE html</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">html</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">head</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">title</span><span style="color: rgba(0, 0, 255, 1)">></span>Socket.IO broadcast<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">title</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
body </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> margin</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding-bottom</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 3rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> font-family</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#form </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> background</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> rgba(0, 0, 0, 0.15)</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0.25rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> position</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> fixed</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> bottom</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> left</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> right</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> display</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> flex</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> height</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 3rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> box-sizing</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> border-box</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> backdrop-filter</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> blur(10px)</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#input </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> border</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> none</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0 1rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> flex-grow</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 1</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> border-radius</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 2rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> margin</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0.25rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#input:focus </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> outline</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> none</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#form > button </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> background</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> #333</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> border</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> none</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0 1rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> margin</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0.25rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> border-radius</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 3px</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> outline</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> none</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> color</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> #fff</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#messages </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> list-style-type</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> none</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> margin</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#messages > li </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> padding</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> 0.5rem 1rem</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(128, 0, 0, 1)">
#messages > li:nth-child(odd) </span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">{</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(255, 0, 0, 1)"> background</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">:</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 255, 1)"> #efefef</span><span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">;</span> <span style="background-color: rgba(245, 245, 245, 1); color: rgba(0, 0, 0, 1)">}</span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">head</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">body</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">ul </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="messages"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">ul</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">form </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="form"</span><span style="color: rgba(255, 0, 0, 1)"> action</span><span style="color: rgba(0, 0, 255, 1)">=""</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">input </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="input"</span><span style="color: rgba(255, 0, 0, 1)"> autocomplete</span><span style="color: rgba(0, 0, 255, 1)">="off"</span> <span style="color: rgba(0, 0, 255, 1)">/><</span><span style="color: rgba(128, 0, 0, 1)">button</span><span style="color: rgba(0, 0, 255, 1)">></span>Send<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">button</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">form</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">script </span><span style="color: rgba(255, 0, 0, 1)">src</span><span style="color: rgba(0, 0, 255, 1)">="../socket.io.js"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">script</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">body</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">html</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132042955-1572923966.png" width="600"></p>
<p> 在页面的内嵌脚本中,先初始化 socket,io() 中的协议既可以是 http 也可以是 ws。</p>
<p> 其中 WS 是 WebSocket 协议的缩写,WSS(Web Socket Secure)是 WebSocket 的加密版本。WS 一般默认是 80 端口,而 WSS 默认是 443 端口。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> socket = io("ws://localhost:1234");</pre>
</div>
<p> 然后是注册表单提交事件,在文本框中输入内容后,触发 chat message 事件发送消息到服务器中,服务器情况如下图所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> messages = document.getElementById("messages"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> form = document.getElementById("form"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> input = document.getElementById("input"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册表单提交事件</span>
form.addEventListener("submit", <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (e) {
e.preventDefault();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (input.value) {
socket.emit(</span>"chat message"<span style="color: rgba(0, 0, 0, 1)">, input.value);
input.value </span>= ""<span style="color: rgba(0, 0, 0, 1)">;
}
});</span></pre>
</div>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132158108-1519128664.png" width="500"></p>
<p> 最后注册 chat message 事件,和服务器中的事件同名,在接收到从服务器传回的消息时,就在页面中增加一栏消息(如下图所示),类似于聊天记录。</p>
<div class="cnblogs_code">
<pre>socket.on("chat message", <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (msg) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> item = document.createElement("li"<span style="color: rgba(0, 0, 0, 1)">);
item.textContent </span>=<span style="color: rgba(0, 0, 0, 1)"> msg;
messages.appendChild(item);
window.scrollTo(</span>0<span style="color: rgba(0, 0, 0, 1)">, document.body.scrollHeight);
});</span></pre>
</div>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132241844-143775010.png" width="500"></p>
<p> 客户端中的 socket 实例有 3 个保留事件,connect、connect_error 和 disconnect。</p>
<p> 其中 connect_error 会在底层连接失败或中间件拒绝连接时触发。</p>
<p> 下面是一张客户端 socket 连接的生命周期图,在建立连接时会分两种情况,在断开连接时还会自动重连。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132342743-2026030610.png" width="600"></p>
<p><span style="font-size: 16px"><strong>4)请求头和消息</strong></span></p>
<p> 下图是一个请求头,状态码是 101 表示可以使用新协议,Connection: Upgrade 指示这是一个升级请求,Upgrade 指定 websocket 协议。</p>
<p> 一旦这次升级完成后,连接就变成了双向管道。sid 参数表示一个会话 ID。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132535325-462969140.png" width="600"></p>
<p> 下图一系列的消息,每条消息的开头都是 1 到 2 个数字,它们都有各自的含义。</p>
<p> 第四条是发送的消息,第五条是接收的消息。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132601224-313774899.png" width="800"></p>
<p> 第一个数字是 Engine.IO 的通信类型。</p>
<table border="0">
<tbody>
<tr>
<td>key</td>
<td>value</td>
</tr>
<tr>
<td>0</td>
<td>open</td>
</tr>
<tr>
<td>1</td>
<td>close</td>
</tr>
<tr>
<td>2</td>
<td>ping</td>
</tr>
<tr>
<td>3</td>
<td>pong</td>
</tr>
<tr>
<td>4</td>
<td>message</td>
</tr>
<tr>
<td>5</td>
<td>upgrade</td>
</tr>
<tr>
<td>6</td>
<td>noop</td>
</tr>
</tbody>
</table>
<p> 第二个数字是 Socket.IO 的操作类型。</p>
<table border="0">
<tbody>
<tr>
<td>key</td>
<td>value</td>
</tr>
<tr>
<td>0</td>
<td>CONNECT</td>
</tr>
<tr>
<td>1</td>
<td>DISCONNECT</td>
</tr>
<tr>
<td>2</td>
<td>EVENT</td>
</tr>
<tr>
<td>3</td>
<td>ACK</td>
</tr>
<tr>
<td>4</td>
<td>ERROR</td>
</tr>
<tr>
<td>5</td>
<td>BINARY_EVENT</td>
</tr>
<tr>
<td>6</td>
<td>BINARY_ACK</td>
</tr>
</tbody>
</table>
<h1>二、附加功能</h1>
<p> 附加功能包括命名空间、专属通道和适配器。</p>
<p><span style="font-size: 16px"><strong>1)命名空间(namespace)</strong></span></p>
<p> 命名空间是一种通信通道,允许通过单个共享连接拆分应用程序的逻辑,即多路复用,适合一台服务器提供多条不同长连接业务的场景。</p>
<p> 如下图所示,分配了两个命名空间,通过一条管道连接了客户端和服务器。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609132914552-595151973.png" width="800"></p>
<p> 在服务端,注册 connection 事件之前需要先调用 of() 方法,参数要和客户端请求地址中的路径一致。</p>
<p> 注意,与之前不同的是,触发事件的对象是 socket 而不是 io,也就是调用 socket.emit() 才能发送消息。</p>
<div class="cnblogs_code">
<pre>const io = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Server(server);
io.of(</span>"/orders").on('connection', (socket) =><span style="color: rgba(0, 0, 0, 1)"> {
socket.on(</span>'chat message', (msg) =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'orders message: ' +<span style="color: rgba(0, 0, 0, 1)"> msg);
socket.emit(</span>'chat message'<span style="color: rgba(0, 0, 0, 1)">, msg);
});
});
io.of(</span>"/users").on('connection', (socket) =><span style="color: rgba(0, 0, 0, 1)"> {
socket.on(</span>'chat message', (msg) =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'users message: ' +<span style="color: rgba(0, 0, 0, 1)"> msg);
socket.emit(</span>'chat message'<span style="color: rgba(0, 0, 0, 1)">, msg);
});
});</span></pre>
</div>
<p><span style="font-size: 16px"><strong>2)专属通道(room)</strong></span></p>
<p> room 可以建立专属于几条 socket 的通道,用于向一部分客户端广播事件,如下图所示。</p>
<p> 类似于微信群的概念,发送的消息,只能群里的人收到。</p>
<p> 注意,room 是服务端的概念,客户端是不知道 room 的存在。</p>
<p> 客户端延续命名空间中的代码不需要改造,在服务端调用 join() 方法加入一个 room,leave() 方法可以离开一个 room。</p>
<p> 然后在接收消息时调用 to() 方法给指定 room 中的 socket 发送消息,但不包括自己,效果如下图所示。</p>
<div class="cnblogs_code">
<pre>io.of("/orders").on('connection', (socket) =><span style="color: rgba(0, 0, 0, 1)"> {
socket.join(</span>"one room"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册接收消息事件</span>
socket.on('chat message', (msg) =><span style="color: rgba(0, 0, 0, 1)"> {
socket.to(</span>"one room").emit('chat message'<span style="color: rgba(0, 0, 0, 1)">, msg);
});
});</span></pre>
</div>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609133033573-1466345450.png" width="400"></p>
<p> socket.to() 的效果其实就是这条消息不会让自己收到,与 io.to() 的区别是后者可以让自己也收到。</p>
<p> 不过在调试的时候,调用 io.to() 后,不知为何,客户端都收不到消息。</p>
<p> 在做即时通信的项目时,采用 socket.to() 更合适,自己发送的消息完全可以通过脚本添加到聊天界面中。</p>
<p><span style="font-size: 16px"><strong>3)适配器(adapter)</strong></span></p>
<p> 适配器是一个服务端组件,负责将事件广播到所有或部分客户端。</p>
<p> 当扩展到多个 Socket.IO 服务器时,需要集群部署时,就得将默认的内存适配器替换为另一种,例如 Redis、MongoDB 等。</p>
<p> 这样做的目的,就是为了将事件正确路由到所有客户端。</p>
<p> 在下图中,客户端触发事件后,经过适配器路由到集群的 Socket.IO 服务器中。</p>
<p> <img src="https://img2022.cnblogs.com/blog/211606/202206/211606-20220609133112648-1333392056.png" width="600"></p>
<p> 以 redis 为例,首先安装 @socket.io/redis-adapter 和 ioredis 库,前者在 v7 版本之前叫 socket.io-redis。</p>
<p> 然后是改造服务端,客户端不用做调整,引入两个库。本机已安装 redis 环境,若未安装不知道会不会报错。</p>
<div class="cnblogs_code">
<pre>const { Server } = require("socket.io"<span style="color: rgba(0, 0, 0, 1)">);
const { createAdapter } </span>= require("@socket.io/redis-adapter"<span style="color: rgba(0, 0, 0, 1)">);
const { Cluster } </span>= require("ioredis");</pre>
</div>
<p> 接着连接 redis 库,调用 adapter() 方法选择适配器。</p>
<div class="cnblogs_code">
<pre>const io = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Server(server);
const pubClient </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Cluster([
{
host: </span>"localhost"<span style="color: rgba(0, 0, 0, 1)">,
port: </span>6380<span style="color: rgba(0, 0, 0, 1)">,
}
]);
const subClient </span>=<span style="color: rgba(0, 0, 0, 1)"> pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));</span></pre>
</div>
<p> </p>
<p>参考资料:</p>
<p>Node.js + Socket.io 实现一对一即时聊天</p>
<p>socket.io官方文档中文版</p>
<p>基于socket.io构建即时通讯应用</p>
<p>socket.io namespaces and rooms (译) </p>
<p>Socket.io源码分析</p><br><br>
来源:https://www.cnblogs.com/strick/p/16358972.html
頁:
[1]