socket.io默认情况下会通过socket.io-client包提供socket.io.min.js和socket.io.js.map下载
运行实例app.js
let app = require('http').createServer() let io = require('socket.io')(app) app.listen(3000);
浏览器访问:3000/socket.io/socket.io.js可以加载压缩的源码,访问:3000/socket.io/socket.io.js.map加载sourcemap
我们可以改变这种行为
禁用socket.io.js下载
方法1: 实例化时传入控制参数serveClient值false
let io = require('socket.io')(app, { serveClient: false })
方法2: 调用函数serverClient
let app = require('http').createServer() let io = require('socket.io')() io.serveClient(false) io.listen(app) // 或者io.attach(app)
如果在调用函数前服务已绑定http.Server,该方法将不起作用
禁用后再次访问将提示{"code":0,"message":"Transport unknown"}
修改静态文件路径
socket.io.js路径可以改变,其默认路径为/socket.io。
实例化时传参
let io = require('socket.io')(app, { path: '/io' })
调用函数path
let app = require('http').createServer() let io = require('socket.io')() io.path('/io') io.listen(app)
如果在调用函数前服务已绑定http.Server,该方法将不起作用
安全策略
socket.io提供了两种安全策略
allowRequest
函数allowRequest有两个参数,第一个参数为收到的握手包(http.request)对象,作为判断依据, success), err是错误对象,success为boolean, false表示阻止建立连接
前端请求带上token
let socket = io('http://localhost:3000?token=abc') socket.on('connect', () => { console.log('connect') }) socket.on('connect_error', err => { socket.disconnect() console.log('connect_error', err) })
后端allowRequest根据token判断是否继续
let app = require('http').createServer() let io = require('socket.io')(app, { allowRequest: (req, cb) => { if (req._query && req._query.token === 'abc') return cb(null, true) cb(null, false) } });
origins
可以对源进行限制
1、实例化时限制源
let app = require('http').createServer() let io = require('socket.io')(app, { origins: 'http://localhost:3000' })
2、origins函数设置源
origins函数有两种形式
origins(string) : 设置运行的源
origins(string, fn(err, success)) : 通过函数判断源是否允许
io.origins('http://localhost:*') io.origins((origin, cb) => { if (origin === 'http://localhost:3000/') return cb(null, true) cb(null, false) })
名称空间
名称空间用来对服务端/客户端的连接隔离,有些地方,也称呼名称空间(namespace)为通道(channel)。下面举例对其意义进行说明
我们需要实现一个协同应用,这个应用有两个功能:
协同编辑: 多个用户可以同时编辑一个文档
消息: 用户间可以发送消息
用socket.io实现这个应用,有如下几种形式
1、完全独立: 协同编辑有一个独立服务edit.socket.test ,消息系统一个独立服务message.socket.test
let editSocket = io('edit.socket.test') let messageSocket = io('message.socket.test')
2、名称空间: 只运行一个独立服务,通过名称空间进行隔离
let app = require('http').createServer() let io = require('socket.io')(app) let editServer = io.of('/edit') let messsageServer = io.of('/message') editServer.on('connection', socket => { //编辑相关 }) messsageServer.on('connection', socket => { /消息相关 })
let editSocket = io('socket.test/edit') let messageSocket = io('socket.test/message')
3、事件名约定: 通过为事件名添加进行隔离
let app = require('http').createServer() let io = require('socket.io')(app) io.on('connection', socket => { //编辑相关 io.emit('edit:test') io.on('edit:test', data => { }) //消息相关 io.emit('message:test') io.on('message:test', data => { }) }
通过事件名约定程序的侵入性太大,不利于拆分和重组,不推荐。 而完全独立的模式需要使用两个socket连接,即浪费浏览器允许的并发连接数,又更多消耗服务器资源。使用名称空间即能实现很好的隔离,又不会对资源造成浪费。
默认名称空间
socket.io实例化时自动绑定路径为/的名称空间
let app = require('http').createServer() let io = require('socket.io')(app) io.sockets // io.of('https://www.jb51.net/').sockets io.emit // 代理io.of('https://www.jb51.net/').emit, 类似函数有'to', 'in', 'use', 'send', 'write', 'clients', 'compress'
中间件
socket.io的名空间通过use注册中间件,中间件在客户端与服务端建立连接成功后,connet事件派发前调用一次。
利用中间件数据校验
io.use((socket, next) => { if (socket.request.headers.cookie) return next() next(new Error('Authentication error')) })
利用中间件提取或转换数据 io.use((socket, next) => {
getInfo(socket.request.query.id, (err, data) => { if (err) return next(err) socket.custom = data next() }) })
与allowRequest对比
allowRequest可以进行一些校验,提取,为什么还要需要中间件?
allowRequest传入的http.request实例,而中间件出入数据socket实例,socket实例包含request实例,且有更多信息
中间件直接支持多个异步流程嵌套,而allowRequest需要自己实现
与connection事件对比
connection事件也传入socket,也可以进行数验,提取,为什么还要需要中间件?
中间件直接支持多个异步流程嵌套,而allowRequest需要自己实现