持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 2 天,点击查看活动详情
Socket.IO
是一个库,可以在客户端和服务器之间实现低延迟、双向和基于事件的通信。
它建立在WebSocket协议之上,并提供额外的保证,例如回退到 HTTP 长轮询或自动重新连接。
注意: Socket.IO
不是WebSocket
实现。也就是说我们不能通过new WebSocket(URL)
的方式来连接服务端,必须使用其提供的客户端的socket.io-client
来链接socket.io
创建的服务。
尽管Socket.io
在可能的情况下使用WebSocket
进行传输,但它为每个数据包,添加了额外的元数据,这就是为什么WebSocket
客户端将无法成功连接到`Socket.IO 服务器。
服务器
yarn add socket.io
复制代码
用法:
const { Server } = require('socket.io')
const io = new Server({
/* options */
})
io.on('connection', socket => {
// ...
})
io.listen(3000)
复制代码
或者将端口做为第一个参数传递个Service
。
const { Server } = require('socket.io')
const io = new Server(3000, {
/* options */
})
io.on('connection', socket => {
// ...
})
复制代码
和 Koa 一起使用
const Koa = require('koa')
const { createServer } = require('http')
const { Server } = require('socket.io')
const app = new Koa()
const httpServer = createServer(app.callback())
const io = new Server(httpServer, {
/* options */
})
io.on('connection', socket => {
// ...
})
httpServer.listen(3000)
复制代码
处理 CORS
从 Socket.IO v3 开始,您需要显式启用跨域资源共享(CORS)。
import { createServer } from 'http'
import { Server } from 'socket.io'
const httpServer = createServer()
const io = new Server(httpServer, {
cors: {
origin: 'https://example.com' // 或者使用 *
}
})
复制代码
中间件
注册中间件:
io.use((socket, next) => {
const toekn = socket.handshake.auth.token
if (toekn) {
next()
} else {
next(new Error('invalid'))
}
})
复制代码
可以注册多个中间件,他们按照注册的顺序执行。
客户端
安装
yarn add socket.io-client
复制代码
建立连接
const socket = io('http://localhost:3000', {
reconnection: true, // 是否重连
reconnectionAttempts: 30, // 重新连接的次数
reconnectionDelay: 1000, // 每过多长时间重连一次
timeout: 5000, // 超时时间
auth: {
token: window.sessionStorage.getItem('token')
}
})
复制代码
事件
-
connect
:在连接和重新连接的时候会被触发。socket.on('connect', () => { console.log('连接成功') }) 复制代码
-
connect
: 在无法和服务器建立连接的时候触发,例如网络或者服务器拒绝连接。由于网络原因引起的会执行自动重连,但是服务器拒绝连接的需要手动调用重新连接。/** * 在无法和服务器建立连接的时候触发(网络原因/服务器拒绝) * 网络原因:会执行自动重连 * 服务器拒接连接:需要手动重新连接 **/ socket.on('connect_error', error => { console.error('连接出错==>', error.message, error.data?.content) if (error.message === 'not authorized') { clearTimeout(connect_timer) connect_timer = setTimeout(() => { socket.auth.token = window.sessionStorage.getItem('token') socket.connect() }, 4000) } }) 复制代码
-
disconnect
:在连接断开的时候触发,例如刷新页面,手动断开连接等等。socket.on('disconnect', () => { console.log('连接断开') }) 复制代码
其余的事件均为自定义事件,使用socket.io(事件名,callback)
来监听。例如:
socket.on('current_user', message => {
console.log(message) // message 为服务器返回的数据
})
复制代码
发送数据
socket.emit('事件名', data)
复制代码
基本上常用的就这些,我也是把一些常用的 API 实践总结了一下,具体更详细的使用可以去查询官方文档
案例
最后贴一下服务端和客户端的两个 demo:
服务器
这一段代码是添加在使用 Koa + TS + ESLlint 搭建 node 服务器中main.ts
中的代码
import { createServer } from 'http'
import { Server } from 'socket.io'
import app from './app/app'
import { app_config, jwtCofnig } from './config/config'
import { verifyToken } from './utils/token'
const { port, hostname = '' } = app_config
const httpServer = createServer(app.callback())
// 这隐士启动了一个Nodejs HTTP 服务器
const io = new Server(httpServer, {
cors: {
origin: '*'
}
})
/**
* 中间件函数,,它们将按照注册的顺序执行
* 执行中间件时候,socket实例实际上并未连接,这意味这 disconnect 如果连接最终失败,将不会发出断开连接事件。
* 验证用户携带的token
*/
io.use((socket, next) => {
const authorization = socket.handshake.auth.token // 客户端携带的token
if (!authorization) {
const err: any = new Error('not authorized')
err.data = { content: '认证失败' }
return next(err)
}
const token = authorization.slice(7)
try {
// @ts-ignore
io.userInfo = verifyToken({ token, public_key: jwtCofnig.secret })
next()
} catch (e) {
const err: any = new Error('not authorized')
err.data = { content: '认证失败' }
return next(err)
}
})
// 此事件在有新的连接的时候触发
io.on('connection', socket => {
console.log(`${socket.handshake.headers.origin} 连接成功`)
const count = io.engine.clientsCount // 前连接的客户端数量:
const count2 = io.of('/').sockets.size
console.log(count, count2)
// 接收客户端的消息
socket.on('current_user', message => {
// @ts-ignore 给客户端 发送消息, message 客户端发送的消息体
socket.emit('current_user', { socket_id: socket.id, ...message, ...io.userInfo })
})
})
httpServer.listen(app_config.port, hostname, () => {
console.log(`服务启动成功,running http://${hostname}:${port}`)
})
复制代码
客户端
<button class="get-token">获取认证</button>
<button class="remove-token">删除认证</button>
<button class="client-socket">建立连接</button>
<button class="close-socket">断开连接</button>
<button class="send-socket">发送消息</button>
<script type="module">
import { io } from 'https://cdn.socket.io/4.3.2/socket.io.esm.min.js'
const getTokenButton = document.querySelector('.get-token')
const removeTokenButton = document.querySelector('.remove-token')
const clientSocketButton = document.querySelector('.client-socket')
const closeSocketButton = document.querySelector('.close-socket')
const sendSocketButton = document.querySelector('.send-socket')
let connect_timer = null
let socket = null
// 获取token
getTokenButton.addEventListener('click', async () => {
const response = await fetch('http://localhost:3000/api/login', {
method: 'POST',
body: JSON.stringify({
username: 'coderlzw',
password: 'coderlzw'
}),
headers: {
'Content-Type': 'application/json'
}
})
const responseData = await response.text()
const { data } = JSON.parse(responseData)
window.sessionStorage.setItem('token', `Bearer ${data.token}`)
})
// 删除token
removeTokenButton.addEventListener('click', () => {
window.sessionStorage.removeItem('token')
})
// 连接socket
clientSocketButton.addEventListener('click', () => {
socket = io('http://localhost:3000', {
reconnection: true, // 是否重连
reconnectionAttempts: 30, // 重新连接的次数
reconnectionDelay: 1000, // 每过多长时间重连一次
timeout: 5000, // 超时时间
auth: {
token: window.sessionStorage.getItem('token')
}
})
// 在连接和重新连接的时候触发
socket.on('connect', () => {
console.log('连接成功')
})
/**
* 在无法和服务器建立连接的时候触发(网络原因/服务器拒绝)
* 网络原因:会执行自动重连
* 服务器拒接连接:需要手动重新连接
**/
socket.on('connect_error', error => {
console.error('连接出错==>', error.message, error.data?.content)
if (error.message === 'not authorized') {
clearTimeout(connect_timer)
connect_timer = setTimeout(() => {
socket.auth.token = window.sessionStorage.getItem('token')
socket.connect()
}, 4000)
}
})
// 在连接断开的时候触发
socket.on('disconnect', () => {
console.log('连接断开')
})
// message
socket.on('current_user', message => {
console.log(message)
})
})
// 发送消息
sendSocketButton.addEventListener('click', res => {
socket?.emit('current_user', {
uid: 10
})
})
// 断开连接
closeSocketButton.addEventListener('click', res => {
socket?.disconnect()
socket = null
})
</script>
复制代码