socket.io學(xué)習(xí)教程之深入學(xué)習(xí)篇(三)
前言
socket.io提供了基于事件的實(shí)時(shí)雙向通訊,本文深入的介紹了socket.io,下面來看看詳細(xì)的內(nèi)容吧。
靜態(tài)文件
socket.io默認(rèn)情況下會(huì)通過socket.io-client包提供socket.io.min.js和socket.io.js.map下載
運(yùn)行實(shí)例app.js
let app = require('http').createServer() let io = require('socket.io')(app) app.listen(3000);
瀏覽器訪問http://localhost:3000/socket.io/socket.io.js可以加載壓縮的源碼,訪問http://localhost:3000/socket.io/socket.io.js.map加載sourcemap
我們可以改變這種行為
禁用socket.io.js下載
方法1: 實(shí)例化時(shí)傳入控制參數(shù)serveClient值false
let io = require('socket.io')(app, { serveClient: false })
方法2: 調(diào)用函數(shù)serverClient
let app = require('http').createServer() let io = require('socket.io')() io.serveClient(false) io.listen(app) // 或者io.attach(app)
如果在調(diào)用函數(shù)前服務(wù)已綁定http.Server
,該方法將不起作用
禁用后再次訪問將提示{"code":0,"message":"Transport unknown"}
修改靜態(tài)文件路徑
socket.io.js路徑可以改變,其默認(rèn)路徑為/socket.io。
實(shí)例化時(shí)傳參
let io = require('socket.io')(app, { path: '/io' })
調(diào)用函數(shù)path
let app = require('http').createServer() let io = require('socket.io')() io.path('/io') io.listen(app)
如果在調(diào)用函數(shù)前服務(wù)已綁定http.Server
,該方法將不起作用
安全策略
socket.io提供了兩種安全策略
allowRequest
函數(shù)allowRequest有兩個(gè)參數(shù),第一個(gè)參數(shù)為收到的握手包(http.request
)對(duì)象,作為判斷依據(jù), success), err是錯(cuò)誤對(duì)象,success為boolean, false表示阻止建立連接
前端請(qǐng)求帶上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根據(jù)token判斷是否繼續(xù)
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
可以對(duì)源進(jìn)行限制
1、實(shí)例化時(shí)限制源
let app = require('http').createServer() let io = require('socket.io')(app, { origins: 'http://localhost:3000' })
2、origins函數(shù)設(shè)置源
origins函數(shù)有兩種形式
origins(string)
: 設(shè)置運(yùn)行的源
origins(string, fn(err, success))
: 通過函數(shù)判斷源是否允許
io.origins('http://localhost:*') io.origins((origin, cb) => { if (origin === 'http://localhost:3000/') return cb(null, true) cb(null, false) })
名稱空間
名稱空間用來對(duì)服務(wù)端/客戶端的連接隔離,有些地方,也稱呼名稱空間(namespace)為通道(channel)。下面舉例對(duì)其意義進(jìn)行說明
我們需要實(shí)現(xiàn)一個(gè)協(xié)同應(yīng)用,這個(gè)應(yīng)用有兩個(gè)功能:
- 協(xié)同編輯: 多個(gè)用戶可以同時(shí)編輯一個(gè)文檔
- 消息: 用戶間可以發(fā)送消息
用socket.io實(shí)現(xiàn)這個(gè)應(yīng)用,有如下幾種形式
1、完全獨(dú)立: 協(xié)同編輯有一個(gè)獨(dú)立服務(wù)edit.socket.test
,消息系統(tǒng)一個(gè)獨(dú)立服務(wù)message.socket.test
let editSocket = io('edit.socket.test') let messageSocket = io('message.socket.test')
2、名稱空間: 只運(yùn)行一個(gè)獨(dú)立服務(wù),通過名稱空間進(jìn)行隔離
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 => { //編輯相關(guān) }) messsageServer.on('connection', socket => { /消息相關(guān) })
let editSocket = io('socket.test/edit') let messageSocket = io('socket.test/message')
3、事件名約定: 通過為事件名添加進(jìn)行隔離
let app = require('http').createServer() let io = require('socket.io')(app) io.on('connection', socket => { //編輯相關(guān) io.emit('edit:test') io.on('edit:test', data => { }) //消息相關(guān) io.emit('message:test') io.on('message:test', data => { }) }
通過事件名約定程序的侵入性太大,不利于拆分和重組,不推薦。 而完全獨(dú)立的模式需要使用兩個(gè)socket連接,即浪費(fèi)瀏覽器允許的并發(fā)連接數(shù),又更多消耗服務(wù)器資源。使用名稱空間即能實(shí)現(xiàn)很好的隔離,又不會(huì)對(duì)資源造成浪費(fèi)。
默認(rèn)名稱空間
socket.io實(shí)例化時(shí)自動(dòng)綁定路徑為/的名稱空間
let app = require('http').createServer() let io = require('socket.io')(app) io.sockets // io.of('/').sockets io.emit // 代理io.of('/').emit, 類似函數(shù)有'to', 'in', 'use', 'send', 'write', 'clients', 'compress'
中間件
socket.io的名空間通過use注冊(cè)中間件,中間件在客戶端與服務(wù)端建立連接成功后,connet事件派發(fā)前調(diào)用一次。
利用中間件數(shù)據(jù)校驗(yàn)
io.use((socket, next) => { if (socket.request.headers.cookie) return next() next(new Error('Authentication error')) })
利用中間件提取或轉(zhuǎn)換數(shù)據(jù) io.use((socket, next) => {
getInfo(socket.request.query.id, (err, data) => { if (err) return next(err) socket.custom = data next() }) })
與allowRequest對(duì)比
allowRequest可以進(jìn)行一些校驗(yàn),提取,為什么還要需要中間件?
- allowRequest傳入的http.request實(shí)例,而中間件出入數(shù)據(jù)socket實(shí)例,socket實(shí)例包含request實(shí)例,且有更多信息
- 中間件直接支持多個(gè)異步流程嵌套,而allowRequest需要自己實(shí)現(xiàn)
與connection事件對(duì)比
connection事件也傳入socket,也可以進(jìn)行數(shù)驗(yàn),提取,為什么還要需要中間件?
- 中間件直接支持多個(gè)異步流程嵌套,而allowRequest需要自己實(shí)現(xiàn)
- 中間件成功后到connection事件發(fā)送成功前,socket.io還做了一些工作,比如把socket實(shí)例添加到connected對(duì)象中,加入聊天室等。如果因?yàn)闄?quán)限中斷連接,在中間件中處理更省資源.
聊天室
聊天室是對(duì)當(dāng)前連接的socket集合根據(jù)特定規(guī)則進(jìn)行歸組,方便群發(fā)消息??梢灶惐萉Q群的概率.
socket.join('room name') //進(jìn)入 socket.leave('room name') //退出
io.to('some room').emit('some event') // io.to與io.in同義,向某個(gè)聊天室的所有成員發(fā)送消息
默認(rèn)聊天室
每個(gè)socket在連接成功后會(huì)自動(dòng)創(chuàng)建一個(gè)默認(rèn)個(gè)聊天室,這個(gè)聊天室的名字是當(dāng)前socket的id,可以通過默認(rèn)聊天室實(shí)現(xiàn)向特定用戶發(fā)送消息
socket.on('say to someone', (id, msg) => { socket.broadcast.to(id).emit('my message', msg) })
消息發(fā)送
應(yīng)答消息
普通消息不需要回應(yīng),而應(yīng)答消息提供了應(yīng)答機(jī)制
io.on('connection', socket => { socket.emit('an event', { some: 'data' }) //普通消息 socket.emit('ferret', 'tobi', function (data) { //應(yīng)答消息 console.log(data); // data will be 'woot' }) })
socket.on('ferret', (name, fn) => { fn('woot') })
壓縮
socket.compress(true)
啟用壓縮,調(diào)用后當(dāng)前連接的所有數(shù)據(jù)在傳遞給客戶端前都會(huì)進(jìn)行壓縮
volatile標(biāo)志
socket.io在正常情況下對(duì)發(fā)送的消息進(jìn)行追蹤,確保消息發(fā)送成功,而設(shè)置volatile后發(fā)送消息,socket.io不會(huì)對(duì)消息追蹤,消息可能丟失
分類
// 客戶端發(fā)送消息 socket.emit('hello', 'can you hear me?', 1, 2, 'abc'); // 向所有連接的客戶端(除了自己)發(fā)送消息 socket.broadcast.emit('broadcast', 'hello friends!'); // 向game聊天室發(fā)送消息,自己不算 socket.to('game').emit('nice game', "let's play a game"); // 同時(shí)向game1和game2聊天室發(fā)送消息,自己不算 socket.to('game1').to('game2').emit('nice game', "let's play a game (too)"); // 向game聊天室的所有人發(fā)送消息 io.in('game').emit('big-announcement', 'the game will start soon'); // 發(fā)送消息到<socketid>客戶端 socket.to(<socketid>).emit('hey', 'I just met you'); // 發(fā)送應(yīng)答消息 socket.emit('question', 'do you think so?', function (answer) {});
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Node.js完整實(shí)現(xiàn)博客系統(tǒng)詳解
這篇文章主要介紹了Node.js完整實(shí)現(xiàn)一個(gè)博客系統(tǒng)的流程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Node.js創(chuàng)建Web、TCP服務(wù)器
這篇文章主要介紹了用Node.js創(chuàng)建Web服務(wù)器和TCP服務(wù)器的方法和處理技巧,需要的讀者們學(xué)習(xí)一下吧。2017-12-12基于promise.js實(shí)現(xiàn)nodejs的promises庫
promise是JavaScript實(shí)現(xiàn)優(yōu)雅編程的一個(gè)非常不錯(cuò)的輕量級(jí)框架。該框架可以讓你從雜亂的多重異步回調(diào)代碼中解脫出來,并把精力集中到你的業(yè)務(wù)邏輯上。2014-07-07基于nodejs使用express創(chuàng)建web服務(wù)器的操作步驟
express實(shí)際上是對(duì)nodejs內(nèi)置http進(jìn)行封裝后的第三方包,其中提供了快捷創(chuàng)建web服務(wù)器以及處理請(qǐng)求路由的方法,使我們可以更加方便快捷的實(shí)現(xiàn)一個(gè)web服務(wù)器項(xiàng)目,本文件給大家詳細(xì)介紹基于nodejs使用express?創(chuàng)建web服務(wù)器的操作步驟2023-07-07node.js使用zlib模塊進(jìn)行數(shù)據(jù)壓縮和解壓操作示例
這篇文章主要介紹了node.js使用zlib模塊進(jìn)行數(shù)據(jù)壓縮和解壓操作,結(jié)合實(shí)例形式詳細(xì)分析了node.js基于zlib模塊創(chuàng)建數(shù)據(jù)流以及壓縮和解壓縮等相關(guān)操作技巧,需要的朋友可以參考下2020-02-02window系統(tǒng) nodejs安裝opencv環(huán)境配置圖文詳解
這篇文章主要介紹了window系統(tǒng) nodejs安裝opencv環(huán)境配置,結(jié)合圖文形式詳細(xì)分析了window環(huán)境下 nodejs安裝opencv的具體步驟、注意事項(xiàng)2023-04-04