Socket.IO 是一个建立在 WebSocket 协议之上的库,可以在客户端和服务器之间实现低延迟、双向和基于事件的通信。

  

  并且提供额外的保证,例如回退到 HTTP 长轮询、自动重连、数据包缓冲、多路复用等。

  WebSocket 是一种基于 TCP 协议在服务器和浏览器之间提供全双工和低延迟通道的通信协议。

  注意,Socket.IO 不是 WebSocket 的实现。尽管 Socket.IO 确实在可能的情况下使用 WebSocket 进行传输,但它为每个数据包添加了额外的元数据。

  这就是为什么 WebSocket 客户端将无法成功连接到 Socket.IO 服务器,而 Socket.IO 客户端也将无法连接到普通的 WebSocket 服务器。

  如果需要一个普通的 WebSocket 服务器,可以使用 wsµWebSockets.js

  在 Socket.IO 的底层依赖 Engine.IO 引擎,它是跨浏览器/跨设备双向通信层的实现,可处理各种传输、升级机制和断线检测等。

  刚刚所说的自动重连、数据包缓冲、多路复用等附加功能都是 Engine.IO 引擎提供的能力。

  本系列所有的示例源码都已上传至Github,点击此处获取。

一、广播

  现在来建立一个提供表单和消息列表的简单 HTML 网页,用 Socket.IO 广播消息(如下图所示),并且可以在页面中呈现消息内容。

  

1) HTTP 服务器

  首先安装 socket.io 包:npm install socket.io。

  然后创建一个 HTTP 服务器,用于接收 HTML 和 JavaScript 文件的请求,内部实现了个简单的路由。

  其中 URL 实例用于解析请求地址,最终响应的内容是通过 fs.readFileSync() 同步读取到的。

  index.html 文件的内容会在后文给出,socket.io.js 是从 node_modules/socket.io/client-dist/socket.io.js 目录中复制过来的。

const http = require('http');
const fs = require('fs'); // HTTP服务器
const server = http.createServer((req, res) => {
// 实例化 URL 类
const url = new URL(req.url, 'http://localhost:1234');
const { pathname } = url;
// 路由
if(pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(fs.readFileSync('./index.html'));
}else if(pathname === '/socket.io.js') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(fs.readFileSync('../socket.io.js'));
}
}); // 监控端口
server.listen(1234);

2)Socket.IO 服务器

  接着是创建 Socket.IO 服务器,其中 socket.id 是每个新连接都会被分配到的一个随机的 20 个字符的标识符,此标识符与客户端的值同步。

  connection 是建立连接时的事件,disconnect 是断开连接时的事件,chat message 是注册的接收消息的自定义事件。

const { Server } = require("socket.io");
const io = new Server(server);
io.on('connection', (socket) => {
console.log('id', socket.id);
// socket.broadcast.emit('hi'); // 广播给其他人,除了自己
console.log('a user connected');
// 注册断开连接事件
socket.on('disconnect', () => {
console.log('user disconnected');
});
// 注册接收消息事件
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
// 触发事件
io.emit('chat message', msg);
});
});

3)广播页面

  在广播页面中,先给出 HTML 结构和 CSS 样式,在表单中有一个按钮和文本框,如下图所示。

<!DOCTYPE html>
<html>
<head>
<title>Socket.IO broadcast</title>
<style>
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #efefef; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="../socket.io.js"></script>
</body>
</html>

  

  在页面的内嵌脚本中,先初始化 socket,io() 中的协议既可以是 http 也可以是 ws。

  其中 WS 是 WebSocket 协议的缩写,WSS(Web Socket Secure)是 WebSocket 的加密版本。WS 一般默认是 80 端口,而 WSS 默认是 443 端口。

var socket = io("ws://localhost:1234");

  然后是注册表单提交事件,在文本框中输入内容后,触发 chat message 事件发送消息到服务器中,服务器情况如下图所示。

var messages = document.getElementById("messages");
var form = document.getElementById("form");
var input = document.getElementById("input");
// 注册表单提交事件
form.addEventListener("submit", function (e) {
e.preventDefault();
if (input.value) {
socket.emit("chat message", input.value);
input.value = "";
}
});

  

  最后注册 chat message 事件,和服务器中的事件同名,在接收到从服务器传回的消息时,就在页面中增加一栏消息(如下图所示),类似于聊天记录。

socket.on("chat message", function (msg) {
var item = document.createElement("li");
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});

  

  客户端中的 socket 实例有 3 个保留事件,connect、connect_error 和 disconnect。

  其中 connect_error 会在底层连接失败或中间件拒绝连接时触发。

  下面是一张客户端 socket 连接的生命周期图,在建立连接时会分两种情况,在断开连接时还会自动重连。

  

4)请求头和消息

  下图是一个请求头,状态码是 101 表示可以使用新协议,Connection: Upgrade 指示这是一个升级请求,Upgrade 指定 websocket 协议。

  一旦这次升级完成后,连接就变成了双向管道。sid 参数表示一个会话 ID。

  

  下图一系列的消息,每条消息的开头都是 1 到 2 个数字,它们都有各自的含义。

  第四条是发送的消息,第五条是接收的消息。

  

  第一个数字是 Engine.IO 的通信类型。

key value
0 open
1 close
2 ping
3 pong
4 message
5 upgrade
6 noop

  第二个数字是 Socket.IO 的操作类型。

key value
0 CONNECT
1 DISCONNECT
2 EVENT
3 ACK
4 ERROR
5 BINARY_EVENT
6 BINARY_ACK

二、附加功能

  附加功能包括命名空间、专属通道和适配器。

1)命名空间(namespace)

  命名空间是一种通信通道,允许通过单个共享连接拆分应用程序的逻辑,即多路复用,适合一台服务器提供多条不同长连接业务的场景。

  如下图所示,分配了两个命名空间,通过一条管道连接了客户端和服务器。

  

  在服务端,注册 connection 事件之前需要先调用 of() 方法,参数要和客户端请求地址中的路径一致。

  注意,与之前不同的是,触发事件的对象是 socket 而不是 io,也就是调用 socket.emit() 才能发送消息。

const io = new Server(server);
io.of("/orders").on('connection', (socket) => {
socket.on('chat message', (msg) => {
console.log('orders message: ' + msg);
socket.emit('chat message', msg);
});
});
io.of("/users").on('connection', (socket) => {
socket.on('chat message', (msg) => {
console.log('users message: ' + msg);
socket.emit('chat message', msg);
});
});

2)专属通道(room)

  room 可以建立专属于几条 socket 的通道,用于向一部分客户端广播事件,如下图所示。

  类似于微信群的概念,发送的消息,只能群里的人收到。

  注意,room 是服务端的概念,客户端是不知道 room 的存在。

  客户端延续命名空间中的代码不需要改造,在服务端调用 join() 方法加入一个 room,leave() 方法可以离开一个 room。

  然后在接收消息时调用 to() 方法给指定 room 中的 socket 发送消息,但不包括自己,效果如下图所示。

io.of("/orders").on('connection', (socket) => {
socket.join("one room");
// 注册接收消息事件
socket.on('chat message', (msg) => {
socket.to("one room").emit('chat message', msg);
});
});

  

  socket.to() 的效果其实就是这条消息不会让自己收到,与 io.to() 的区别是后者可以让自己也收到。

  不过在调试的时候,调用 io.to() 后,不知为何,客户端都收不到消息。

  在做即时通信的项目时,采用 socket.to() 更合适,自己发送的消息完全可以通过脚本添加到聊天界面中。

3)适配器(adapter)

  适配器是一个服务端组件,负责将事件广播到所有或部分客户端。

  当扩展到多个 Socket.IO 服务器时,需要集群部署时,就得将默认的内存适配器替换为另一种,例如 Redis、MongoDB 等。

  这样做的目的,就是为了将事件正确路由到所有客户端。

  在下图中,客户端触发事件后,经过适配器路由到集群的 Socket.IO 服务器中。

  

  以 redis 为例,首先安装 @socket.io/redis-adapter 和 ioredis 库,前者在 v7 版本之前叫 socket.io-redis。

  然后是改造服务端,客户端不用做调整,引入两个库。本机已安装 redis 环境,若未安装不知道会不会报错。

const { Server } = require("socket.io");
const { createAdapter } = require("@socket.io/redis-adapter");
const { Cluster } = require("ioredis");

  接着连接 redis 库,调用 adapter() 方法选择适配器。

const io = new Server(server);
const pubClient = new Cluster([
{
host: "localhost",
port: 6380,
}
]);
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));

参考资料:

Node.js + Socket.io 实现一对一即时聊天

socket.io官方文档中文版

基于socket.io构建即时通讯应用

socket.io namespaces and rooms (译)

Socket.io源码分析

Node.js精进(11)——Socket.IO的更多相关文章

  1. [Node.js] Level 6. Socket.io

    6.2 Setting Up socket.io Server-Side So far we've created an Express server. Now we want to start bu ...

  2. node.js中使用socket.io + express进行实时消息推送

    socket.io是一个websocket库,包含客户端的js和服务端的node.js,可以在不同浏览器和移动设备上构建实时应用. 一.安装 socket.io npm install socket. ...

  3. node基于express的socket.io

    前一段事件,我一个同学给他们公司用融云搭建了一套web及时通信系统,然后之前我的公司也用过环云来实现web及时通信,本人对web及时通信还是非常感兴趣的.私下读了融云和环信的开发文档,然后发现如果注册 ...

  4. CentOS 下安装 Node.js 8.11.3 LTS Version

    Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google Chrome V8 JavaScript引擎,简单说是运行在服务端的 JavaScript.下面我们来演示一下Cen ...

  5. dotnet调用node.js写的socket服务(websocket/socket/socket.io)

    https://github.com/jstott/socketio4net/tree/develop socket.io服务端node.js,.里面有js写的客户端:http://socket.io ...

  6. Node.js精进(2)——异步编程

    虽然 Node.js 是单线程的,但是在融合了libuv后,使其有能力非常简单地就构建出高性能和可扩展的网络应用程序. 下图是 Node.js 的简单架构图,基于 V8 和 libuv,其中 Node ...

  7. Node.js入门:异步IO

    异步IO     在操作系统中,程序运行的空间分为内核空间和用户空间.我们常常提起的异步I/O,其实质是用户空间中的程序不用依赖内核空间中的I/O操作实际完成,即可进行后续任务. 同步IO的并行模式 ...

  8. Node.js学习(11)----HTTP服务器与客户端

    Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端.http.Server 是一个基于事件的 HTTP 服务器,它的核心由 Node.js ...

  9. Node.js精进(3)——流

    在 JavaScript 中,一般只处理字符串层面的数据,但是在 Node.js 中,需要处理网络.文件等二进制数据. 由此,引入了Buffer和Stream的概念,两者都是字节层面的操作. Buff ...

随机推荐

  1. ghostnet论文解析:ghost

    创建日期: 2020-03-02 17:02:54 简介: GhostNet是2020CVPR录用的一篇对卷积操作进行改进的论文.文章的核心内容是Ghost模块(Ghost Module),可以用来替 ...

  2. 1.7 Linux系统的优缺点

    本节,我们介绍一下 Linux 系统的优缺点.Linux 不可比拟的优势如下. 1) 大量的可用软件及免费软件 Linux 系统上有着大量的可用软件,且绝大多数是免费的,比如声名赫赫的 Apache. ...

  3. 《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)

    1.简介 上一篇宏哥介绍了如何设计支持不同浏览器测试,宏哥的方法就是通过来切换配置文件设置的浏览器名称的值,来确定启动什么浏览器进行脚本测试.宏哥将这个叫做浏览器引擎类.这个类负责获取浏览器类型和启动 ...

  4. C# 编写一个简单易用的 Windows 截屏增强工具

    半年前我开源了 DreamScene2 一个小而快并且功能强大的 Windows 动态桌面软件.有很多的人喜欢,这使我有了继续做开源的信心.这是我的第二个开源作品 ScreenshotEx 一个简单易 ...

  5. JS 的立即执行函数

    JS 的立即执行函数 本文写于 2019 年 12 月 7 日 其实 ES6 之后有了之后,很多之前的用法都没必要了,立即执行函数就是其一. 今天看到一道面试题: 请「用自己的语言」简述 立即执行函数 ...

  6. 最新版2022年任我行管家婆工贸版ERP M7 V22.0进销存财务生产管理软件网络版——云上的集团化制造管理系统

    在互联网+制造业的时代背景下,制造业在利用互联网技术进行转型升级的同时,也面临着供应链体系和生产模式的重塑,主要呈现出以下特点: 多元化发展 对外,传统企业正在通过"互联网+"逐步 ...

  7. 【Java8新特性】Optional 类

    概述 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. Optional 是个容器:它可以保存类型T的值,或者 ...

  8. There appears to be trouble with your network connection. Retrying…

    yarn 错误There appears to be trouble with your network connection. Retrying- 原因:yarn超时 解决途径: #查看代理 yar ...

  9. Kafka 万亿级消息实践之资源组流量掉零故障排查分析

    作者:vivo 互联网服务器团队-Luo Mingbo 一.Kafka 集群部署架构 为了让读者能与小编在后续的问题分析中有更好的共鸣,小编先与各位读者朋友对齐一下我们 Kafka 集群的部署架构及服 ...

  10. 使用多线程提高REST服务器性能

    异步处理REST服务 1.使用Runnable异步处理Rest服务 释放主线程,启用副线程进行处理,副线程处理完成后直接返回请求 主要代码 import java.util.concurrent.Ca ...