1. Socket.IO简介
Socket.IO是一个基于WebSocket协议开发的实时通信框架,借助它可以轻松地实现客户端和服务器之间的双向通信,而且可以兼容不支持WebSocket协议的浏览器,这是它相对于原生WebSocket协议的优势之一。它不仅可以用于Web开发中,还可以用于移动端和桌面端开发中。Socket.IO的核心理念是事件驱动编程,服务端和客户端之间通过事件进行通信,这种方式非常灵活,可以随时增加新的事件以满足不断变化的需求。
2. 在Node中使用Socket.IO
2.1 安装和简单使用
在Node中使用Socket.IO非常简单,只需要使用npm包管理器安装Socket.IO模块并在Node中引入即可。我们可以先创建一个简单的Socket.IO服务器,监听3000端口:
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
http.listen(3000, () => {
console.log('listening on *:3000');
});
以上代码中,我们创建了一个Express应用,创建了一个HTTP服务器,并在该HTTP服务器上基于Socket.IO创建了一个实例io,我们通过io.on()监听'connection'事件,该事件会在有新的客户端连接时被触发,我们可以在回调函数中处理连接事件。我们可以通过socket.on()为每个socket实例添加事件监听器,例如上述代码中我们为每个socket实例添加了一个'disconnect'事件监听器,该事件会在客户端断开连接时被触发。
当我们在命令行中运行该app并访问http://localhost:3000时,我们将在服务器的控制台上看到'a user connected'信息,当我们关闭该网页时,控制台会打印'user disconnected'信息。
2.2 事件的发送和接收
Socket.IO的核心理念就是基于事件进行通信,那么我们该如何发送和接收事件呢?
发送事件的语法是socket.emit(eventName[, ...args][, acknowledge]),其中eventName是事件名称,它可以是任意字符串,...args是可选参数,用于传递给监听器的参数,acknowledge是可选参数,它是一个回调函数,当事件的接收方接收到事件后可以立即调用回调函数。
接收事件的语法是socket.on(eventName, callback),其中eventName是要监听的事件名称,callback是回调函数,当收到事件时将被触发。
让我们来看一下一个例子,我们在服务器端发送一个名为'chat message'的事件,客户端接收该事件并打印事件的参数:
// 服务器端
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
});
});
// 客户端
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
});
// 在服务器端发送消息
io.emit('chat message', 'hello world');
// 在客户端发送消息
socket.emit('chat message', 'hello world');
在客户端和服务器端分别监听'chat message'事件,当收到该事件后都会打印收到的消息。在服务器端我们使用io.emit()发送了一个事件,这将会向所有连接到该服务器的客户端发送该事件,而在客户端我们使用了socket.emit()发送了一个事件,这将会向与该socket实例关联的唯一客户端发送该事件。
3. 优雅使用Socket.IO
3.1 使用命名空间
在Socket.IO中,我们可以通过命名空间来划分不同的模块,每个命名空间都有一个独立的事件监听器。使用命名空间可以帮助我们更好地管理事件,降低事件之间的耦合性。
// 创建命名空间
const nsp = io.of('/my-namespace');
// 监听连接事件
nsp.on('connection', (socket) => {
console.log('someone connected');
// 向该命名空间内的所有客户端发送事件
nsp.emit('hi', 'everyone');
});
在以上示例中,我们创建了一个名为'/my-namespace'的命名空间,并在该命名空间中监听'connection'事件,当有新的客户端连接时,我们向该命名空间内的所有客户端发送了一个名为'hi'的事件。
3.2 使用房间(Room)
在Socket.IO中,我们可以使用房间来把不同的客户端分组,这样我们可以针对某个特定的房间发送事件。使用房间可以方便我们实现群聊功能。
io.on('connection', (socket) => {
console.log('a user connected');
// 加入房间
socket.join('room1');
// 发送消息到房间
io.to('room1').emit('chat message', 'hello room1');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
以上代码中,我们在连接事件中,调用socket.join()让该socket加入一个名为'room1'的房间。之后我们使用io.to()向'room1'中的所有socket实例发送了一个'chat message'事件。
3.3 使用中间件(middleware)
在Socket.IO中,我们可以使用中间件(middleware)来拦截事件并对事件进行处理。中间件可以在服务器端和客户端都使用。
在服务器端,我们可以使用use()方法注册中间件,middleware函数将会被调用两次:一次是在socket.emit()调用时,另一次是在socket.on()调用时。use()方法中间件函数的作用局限于该socket的生命周期内。我们可以使用socket.request属性来获取该socket绑定的请求对象,使用socket.id属性获取该socket的唯一标识ID,使用socket.emit()和socket.broadcast.emit()发送事件。
io.use((socket, next) => {
console.log('middleware:', socket.id);
next();
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.emit('chat message', 'welcome');
socket.on('chat message', (msg) => {
console.log('message:', msg);
socket.broadcast.emit('chat message', msg);
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
以上代码中我们通过io.use()注册了一个中间件函数,当有新的客户端连接时,中间件函数将会被调用,并打印该socket的唯一标识ID。之后我们在连接事件中发送了一个'chat message'事件,并在该事件的监听器中发送了一个广播事件,这将会将该事件发送给所有连接到该服务器的客户端(除了这个客户端)。
在客户端中,使用中间件的方法和在服务器端有些不同,我们可以通过在connect()方法中传递options参数来实现,其中options.middlewares是该socket实例的中间件列表。
const socket = io({
middlewares: [function (packet, next) {
console.log('middleware:', packet[0]);
next();
}]
});
socket.on('connect', () => {
console.log('connected');
});
socket.on('chat message', (msg) => {
console.log('message:', msg);
});
socket.emit('chat message', 'hello');
以上代码中,我们在创建socket实例时提供了middlewares选项,其中包含了一个中间件函数,该函数将会在socket.emit()方法调用时被调用,并打印发送的事件名称。之后我们又在'chat message'事件上注册了一个监听器,该监听器将会在收到'chat message'事件时被调用。
3.4 使用Promise和async/await
如果我们想使用Promise和async/await来处理Socket.IO事件怎么办呢?可以使用eventToPromise模块将Socket.IO事件转换成Promise,从而方便我们使用Promise和async/await。
const eventToPromise = require('event-to-promise');
async function foo(socket) {
const data = await eventToPromise(socket, 'data');
console.log(data);
}
以上代码中,我们使用eventToPromise模块将socket实例上的'data'事件转换为Promise,之后就可以方便地使用async/await来处理事件,并且可以设置超时时限等选项。
4. 总结
Socket.IO是一个非常强大的实时通讯框架,可以用于Web、移动端和桌面端开发中,它提供了很多优秀的功能,例如命名空间、房间、中间件、Promise等等。Socket.IO的核心理念是基于事件进行通信,这种方式非常灵活,可以随时增加新的事件以满足不断变化的需求。在Node.js中使用Socket.IO也非常简单,只需要安装Socket.IO模块并在Node.js中引入即可。Socket.IO的广泛应用使得它成为了Web开发的必备技术之一,相信我们在实际开发中一定会用到它。