socket.io是一個跨瀏覽器支持WebSocket的實時通訊的JS。
由于HTTP是無狀態(tài)的協(xié)議,要實現(xiàn)即時通訊非常困難。因為當(dāng)對方發(fā)送一條消息時,服務(wù)器并不知道當(dāng)前有哪些用戶等著接收消息珊泳,當(dāng)前實現(xiàn)即時通訊功能最為普遍的方式就是輪詢機(jī)制。即客戶端定期發(fā)起一個請求拷沸,看看有沒有人發(fā)送消息到服務(wù)器色查,如果有服務(wù)端就將消息發(fā)給客戶端。這種做法的缺點顯而易見撞芍,那么多的請求將消耗大量資源秧了,大量的請求其實是浪費(fèi)的。
現(xiàn)在勤庐,我們有了WebSocket示惊,它是HTML5的新API。WebSocket連接本質(zhì)上就是建立一個TCP連接愉镰,WebSocket會通過HTTP請求建立米罚,建立后的WebSocket會在客戶端和服務(wù)端建立一個持久的連接,直到有一方主動關(guān)閉該連接丈探。所以录择,現(xiàn)在服務(wù)器就知道有哪些用戶正在連接了,這樣通訊就變得相對容易了碗降。
Socket.io支持及時隘竭、雙向、基于事件的交流讼渊,可在不同平臺动看、瀏覽器、設(shè)備上工作爪幻,可靠性和速度穩(wěn)定菱皆。最典型的應(yīng)用場景如:
- 實時分析:將數(shù)據(jù)推送到客戶端,客戶端表現(xiàn)為實時計數(shù)器挨稿、圖表仇轻、日志客戶。
- 實時通訊:聊天應(yīng)用
- 二進(jìn)制流傳輸:socket.io支持任何形式的二進(jìn)制文件傳輸奶甘,例如圖片篷店、視頻、音頻等臭家。
- 文檔合并:允許多個用戶同時編輯一個文檔疲陕,并能夠看到每個用戶做出的修改。
Socket.io實際上是WebSocket的父集钉赁,Socket.io封裝了WebSocket和輪詢等方法鸭轮,會根據(jù)情況選擇方法來進(jìn)行通訊。
Node.js提供了高效的服務(wù)端運(yùn)行環(huán)境橄霉,但由于Browser對HTML5的支持不一窃爷,為了兼容所有瀏覽器,提供實時的用戶體驗姓蜂,并為開發(fā)者提供客戶端與服務(wù)端一致的編程體驗按厘,于是Socket.io誕生了。
# npm安裝socket.op
$ npm install --save socket.io
Socket.io將WebSocket和Polling機(jī)制以及其它的實時通信方式封裝成通用的接口钱慢,并在服務(wù)端實現(xiàn)了這些實時機(jī)制相應(yīng)代碼逮京。這就是說,WebSocket僅僅是Socket.io實現(xiàn)實時通信的一個子集束莫,那么Socket.io都實現(xiàn)了Polling中那些通信機(jī)制呢懒棉?
- Adobe Flash Socket
大部分PC瀏覽器都支持的Socket模式草描,不過是通過第三方嵌入到瀏覽器,不在W3C規(guī)范內(nèi)策严,可能將逐步被淘汰穗慕。況且,大部分手機(jī)瀏覽器并不支持此種模式妻导。 - AJAX Long Polling
定時向服務(wù)端發(fā)送請求逛绵,缺點是給服務(wù)端帶來壓力并出現(xiàn)信息更新不及時的現(xiàn)象。 - AJAX multipart streaming
在XMLHttpRequest對象上使用某些瀏覽器支持的multi-part標(biāo)志倔韭,AJAX請求被發(fā)送給服務(wù)端并保持打開狀態(tài)(掛起狀態(tài))术浪,每次需要向客戶端發(fā)送信息,就尋找一個掛起的HTTP請求響應(yīng)給客戶端寿酌,并且所有的響應(yīng)都會通過統(tǒng)一連接來寫入胰苏。 - Forever Iframem
永存的Iframe設(shè)計了一個置于頁面中隱藏的iframe標(biāo)簽,該標(biāo)簽的src屬性指向返回服務(wù)端時間的Servlet路徑醇疼。每次在事件到達(dá)時碟联,Servlet寫入并刷新一個新的Script標(biāo)簽,該標(biāo)簽內(nèi)部帶有JS代碼僵腺,iframe的內(nèi)容被附加上script標(biāo)簽鲤孵,標(biāo)簽中的內(nèi)容就會得到執(zhí)行。這種方式的缺點是接收數(shù)據(jù)都是由瀏覽器通過HTML標(biāo)簽來處理的辰如,因此無法知道連接何時在哪一端被斷開普监,而且iframe標(biāo)簽在瀏覽器中將被逐步取消。 - JSONP Polling
JSONP輪詢基本與HTTP輪詢一樣琉兜,不同之處則是JSONP可發(fā)出跨域請求凯正。
Socket.io 基本應(yīng)用
socket.io提供了基于事件的實時雙向通訊,它同時提供了服務(wù)端和客戶端的API豌蟋。
服務(wù)端
服務(wù)端socket.io必須綁定一個http.Server
實例廊散,因為WebSocket協(xié)議是構(gòu)建在HTTP協(xié)議之上的,所以在創(chuàng)建WebSocket服務(wù)時需調(diào)用HTTP模塊并調(diào)用其下createServer()
方法梧疲,將生成的server作為參數(shù)傳入socket.io允睹。
var httpServer = require('http').createServer();
var io = require('socket.io')(httpServer);
httpServer.listen(3000);
綁定http.Server
可使用隱式綁定和顯式綁定
- 隱式綁定
socket.io內(nèi)部實例化并監(jiān)聽http.Server
,通過實例化時傳入端口或者在實例化后調(diào)用listen
或attach
函數(shù)進(jìn)行隱式綁定幌氮。
// 實例化時傳入端口
require('socket.io')(3000)
// 通過listen或attach函數(shù)綁定
let io = require('socket.io')
io.listen(3000);
// io.attach(3000);
- 顯式綁定
// 實例化時綁定
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);
//通過listen或attach綁定
let httpServer = require('http').Server();
let io = require('socket.io')();
io.listen(httpServer);
// io.attach(httpServer);
httpServer.listen(3000);
Express框架中使用
let app = require('express');
let httpServer= require('http').Server(app);
let io = require('socket.io')(httpServer);
app.listen(3000);
KOA框架中使用
let app = require('koa')();
let httpServer = require('http').Server(app.callback());
let io = require('socket.io')(httpServer);
app.listen(3000);
建立連接
當(dāng)服務(wù)端和客戶端連接成功時缭受,服務(wù)端會監(jiān)聽到connection
和connect
事件,客戶端會監(jiān)聽到connect
事件该互,斷開連接時服務(wù)端對應(yīng)到客戶端的socket與客戶端均會監(jiān)聽到disconcect
事件米者。
/*客戶端*/
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
// socket.io引入成功后,可通過io()生成客戶端所需的socket對象。
let socket = io('http://127.0.0.0:3000');
// socket.emmit()用戶客戶端向服務(wù)端發(fā)送消息蔓搞,服務(wù)端與之對應(yīng)的是socket.on()來接收信息胰丁。
socket.emmit('client message', {msg:'hi, server'});
// socket.on()用于接收服務(wù)端發(fā)來的消息
socket.on('connect', ()=>{
console.log('client connect server');
});
socket.on('disconnect', ()=>{
console.log('client disconnect');
});
</script>
/*服務(wù)端*/
// 服務(wù)端綁定HTTP服務(wù)器實例
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);
// 服務(wù)端監(jiān)聽連接狀態(tài):io的connection事件表示客戶端與服務(wù)端成功建立連接,它接收一個回調(diào)函數(shù)喂分,回調(diào)函數(shù)會接收一個socket參數(shù)锦庸。
io.on('connection', (socket)=>{
console.log('client connect server, ok!');
// io.emit()方法用于向服務(wù)端發(fā)送消息,參數(shù)1表示自定義的數(shù)據(jù)名妻顶,參數(shù)2表示需要配合事件傳入的參數(shù)
io.emmit('server message', {msg:'client connect server success'});
// socket.broadcast.emmit()表示向除了自己以外的客戶端發(fā)送消息
socket.broadcast.emmit('server message', {msg:'broadcast'});
// 監(jiān)聽斷開連接狀態(tài):socket的disconnect事件表示客戶端與服務(wù)端斷開連接
socket.on('disconnect', ()=>{
console.log('connect disconnect');
});
// 與客戶端對應(yīng)的接收指定的消息
socket.on('client message', (data)=>{
cosnole.log(data);// hi server
});
socket.disconnect();
});
傳輸數(shù)據(jù)
服務(wù)端和客戶端的socket是一個關(guān)聯(lián)的EventEmitter
對象酸员,客戶端socket派發(fā)的事件可以通過被服務(wù)端的socket接收蜒车,服務(wù)端socket派發(fā)的事件也可以被客戶端接收讳嘱。基于這種機(jī)制酿愧,可以實現(xiàn)雙向交流沥潭。
# 模擬:客戶端不斷發(fā)送隨機(jī)數(shù),當(dāng)隨機(jī)數(shù)大于0.95時嬉挡,服務(wù)端延遲1s后向客戶端發(fā)送警告以及警告次數(shù)钝鸽。
/*客戶端*/
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
let socket = io('http://127.0.0.1:3000');
let interval = setTimeInterval(()=>{
socket.emit('random', Math.random());
}, 500);
socket.on('warn', count=>{
console.log('warning count : '+count);
});
socket.on('disconnect', ()=>{
clearInterval(interval);
});
</script>
/*服務(wù)端*/
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);
io.on('connection', socket=>{
socket.on('random', value=>{
console.log(value);
if(value>0.95){
if(typeof socket.warnign==='undefined'){
socket.warning = 0;// socket對象可用來存儲狀態(tài)和自定義數(shù)據(jù)
}
setTimeout(()=>{
socket.emit('warn', ++socket.warning);
}, 1000);
}
});
});