socket.io搭建websocket服务器

持续创作,加速成长!这是我参与「掘金日新计划 · 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')
	}
})
复制代码

事件

  1. connect:在连接和重新连接的时候会被触发。

    socket.on('connect', () => {
    	console.log('连接成功')
    })
    复制代码
  2. 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)
    	}
    })
    复制代码
  3. 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>
复制代码

猜你喜欢

转载自juejin.im/post/7102751053833043999
今日推荐