WebSocket 是 HTML5 開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議(注意是協議)。前端
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。node
本來的經過HTTP協議的客戶端與服務端的通訊,只能由客戶端發起。HTTP 協議作不到服務器主動向客戶端推送信息。並且一次客戶端與服務端的通訊完成以後,客戶端與服務端的連接就會斷開,可是WebSocket是長連接,當一次鏈接創建之後,若是不是其中一端主動斷開鏈接,兩端的鏈接就會一直鏈接着。web
WebSocket是爲了解決相似聊天室的場景,本來HTTP協議這種單向請求的特色,註定了若是服務器有連續的狀態變化,客戶端要獲知就很是麻煩。咱們只能使用"輪詢":每隔一段時候,就發出一個詢問,瞭解服務器有沒有新的信息。輪詢的效率低,很是浪費資源(由於必須不停鏈接,或者 HTTP 鏈接始終打開)。所以,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發明的。ajax
長鏈接:一個鏈接上能夠連續發送多個數據包,在鏈接期間,若是沒有數據包發送,須要雙方發鏈路檢查包。express
TCP/IP:TCP/IP屬於傳輸層,主要解決數據在網絡中的傳輸問題,只管傳輸數據。可是那樣對傳輸的數據沒有一個規範的封裝、解析等處理,使得傳輸的數據就很難識別,因此纔有了應用層協議對數據的封裝、解析等,如HTTP協議。npm
HTTP:HTTP是應用層協議,封裝解析傳輸的數據。
從HTTP1.1開始其實就默認開啓了長鏈接,也就是請求header中看到的Connection:Keep-alive。可是這個長鏈接只是說保持了(服務器能夠告訴客戶端保持時間Keep-Alive:timeout=200;max=20;)這個TCP通道,直接Request - Response,而不須要再建立一個鏈接通道,作到了一個性能優化。可是HTTP通信自己仍是Request - Response。segmentfault
socket:與HTTP不同,socket不是協議,它是在程序層面上對傳輸層協議(能夠主要理解爲TCP/IP)的接口封裝。
咱們知道傳輸層的協議,是解決數據在網絡中傳輸的,那麼socket就是傳輸通道兩端的接口。因此對於前端而言,socket也能夠簡單的理解爲對TCP/IP的抽象協議。瀏覽器
WebSocket:
WebSocket是包裝成了一個應用層協議做爲socket,從而可以讓客戶端和遠程服務端經過web創建全雙工通訊。websocket提供ws和wss兩種URL方案。性能優化
WebSocket 實例
WebSocket 協議本質上是一個基於 TCP 的協議。服務器
爲了創建一個 WebSocket 鏈接,客戶端瀏覽器首先要向服務器發起一個 HTTP 請求,這個請求和一般的 HTTP 請求不一樣,包含了一些附加頭信息,其中附加頭信息"Upgrade: WebSocket"代表這是一個申請協議升級的 HTTP 請求,服務器端解析這些附加的頭信息而後產生應答信息返回給客戶端,客戶端和服務器端的 WebSocket 鏈接就創建起來了,雙方就能夠經過這個鏈接通道自由的傳遞信息,而且這個鏈接會持續存在直到客戶端或者服務器端的某一方主動的關閉鏈接。
建立WebSocket對象
var ws = new WebSocket(url, [protocol] );
以上代碼中的第一個參數 url, 指定鏈接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。
ws.readyState:
0 - 表示鏈接還沒有創建。
1 - 表示鏈接已創建,能夠進行通訊。
2 - 表示鏈接正在進行關閉。
3 - 表示鏈接已經關閉或者鏈接不能打開。
ws.bufferedAmount 實例對象的bufferedAmount屬性,表示還有多少字節的二進制數據沒有發送出去。它能夠用來判斷髮送是否結束。
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 發送完畢 } else { // 發送還沒結束 }
open: ws.onopen 鏈接創建時觸發
ws.onopen = function () { ws.send('Hello Server!'); }
若是要指定多個回調函數,可使用addEventListener方法。
ws.addEventListener('open', function (event) { ws.send('Hello Server!'); });
message:ws.onmessage 客戶端接收服務端數據時觸發
注意,服務器數據多是文本,也多是二進制數據(blob對象或Arraybuffer對象)。
error: ws.onerror 通訊發生錯誤時觸發
close: ws.onclose 鏈接關閉時觸發
ws.send() 向服務器發送數據
ws.close() 關閉鏈接
在使用websocket的過程當中,有時候會遇到網絡斷開的狀況,可是在網絡斷開的時候服務器端並無觸發onclose的事件。這樣會有:服務器會繼續向客戶端發送多餘的連接,而且這些數據還會丟失。因此就須要一種機制來檢測客戶端和服務端是否處於正常的連接狀態。所以就有了websocket的心跳了。還有心跳,說明還活着,沒有心跳說明已經掛掉了。
實現心跳檢測的思路是:每隔一段固定的時間,向服務器端發送一個ping數據,若是在正常的狀況下,服務器會返回一個pong給客戶端,若是客戶端經過
onmessage事件能監聽到的話,說明請求正常
實現心跳檢測的思路是:每隔一段固定的時間,向服務器端發送一個ping數據,若是在正常的狀況下,服務器會返回一個pong給客戶端,若是客戶端經過 onmessage事件能監聽到的話,說明請求正常 心跳鏈接: //添加心跳防止斷開鏈接 let time=setInterval(() => { ws.send('heartbeat') }, 9*60*1000); //每9分鐘 發送一次 防止斷開
咱們都知道如今咱們廣泛使用的HTTP1.1,也是有長鏈接的,也就是keep-alive。因此在學習的時候咱們必定會有疑問:不都是長鏈接嗎?有啥區別啊?
先說 comet 和 WebSocket 表現的區別:
comet 發送 http 請求後服務端若是沒有返回則鏈接是一直連着的,等服務端有東西要「推送」給瀏覽器時,至關於給以前發送的這個 http 請求回了一個 http 響應。而後這個保持的時間比較長的 http 鏈接就斷了。而後瀏覽器再次發送一個 http 請求,服務器端再 hold 住不返回,等待有東西須要「推送」給瀏覽器時,再給這個 http 請求一個響應,而後斷開鏈接。循環往復。一旦瀏覽器不給服務器發送 http 請求,那麼服務器是不能主動給瀏覽器推送消息的,由於根本沒有連着的鏈接給你推。
WebSocket 則不一樣,它握手後創建的鏈接是不會斷的(除了意外狀況和程序主動掐斷)。不須要瀏覽器在每次收到服務器推送的消息後再發起請求。並且服務器端能夠隨時給瀏覽器推送消息,不須要等瀏覽器發 http 請求,由於 WebSocket 的鏈接一直在沒斷。
爲何會有這樣的區別?
這是協議層面的區別。http 協議規定了 http 鏈接是一個一來(request)一回(response)的過程。一個請求得到一個響應後必須斷掉。並且只有先有請求才會有響應。拿 http1.1 keep-alive 來講,即便底層 tcp 鏈接沒有斷,服務端平白無故給瀏覽器發一個 http 響應,瀏覽器是不收的,他找不到收的人啊,由於這個響應沒有對應的請求。你看 ajax 必須先發請求才會有一個 onsuccess 回調來響應這個請求。這個 onsuccess 的回調會在你 ajax 不發送的狀況下被調用到嗎?
而 WebSocket 協議不一樣,他經過握手以後規定說你鏈接給我保持着,別斷咯。因此瀏覽器服務器在這種狀況下能夠相互的發送消息。瀏覽器端 new 一個 WebSocket 以後註冊 onmessage 回調,那麼這個 onmessage 能夠被反覆調用,只要服務器端有消息過來。而不會說是 new 一個 WebSocket onmessage 只會被調用一次,下次還得再 new 一個 websocket。
上面說到 http 鏈接,tcp 鏈接,websockt 鏈接到底啥區別。其實這是新人最容易搞不懂的地方。接下來我就要胡謅了,爲啥說胡謅,由於我只是看了個皮毛,而後按我本身的理解說下區別。網絡5層分層(自下而上):
物理層
數據鏈路層
網絡層
傳輸層
應用層
http,websocket都是應用層協議,他們規定的是數據怎麼封裝,而他們傳輸的通道是下層提供的。就是說不管是 http 請求,仍是 WebSocket 請求,他們用的鏈接都是傳輸層提供的,即 tcp 鏈接(傳輸層還有 udp 鏈接)。只是說 http1.0 協議規定,你一個請求得到一個響應後,你要把鏈接關掉。因此你用 http 協議發送的請求是沒法作到一直連着的(若是服務器一直不返回也能夠保持至關一段時間,可是也會有超時而被斷掉)。而 WebSocket 協議規定說等握手完成後咱們的鏈接不能斷哈。雖然 WebSocket 握手用的是 http 請求,可是請求頭和響應頭裏面都有特殊字段,當瀏覽器或者服務端收到後會作相應的協議轉換。因此 http 請求被 hold 住不返回的長鏈接和 WebSocket 的鏈接是有本質區別的。
參考鏈接:https://segmentfault.com/a/11...
使用socket.io
socket.io文檔地址:https://socket.io/docs/
不懂得地方能夠看文檔
npm install socket.io socket.io-client --save
let socket = io("ws://localhost:3001"); // 創建連接 socket.emit('msg', {message:"你好服務器,我是客戶端"}); //向服務器發送消息 socket.on('msg',function(data){ // 監聽服務端的消息「msg」 console.log(data); });
let express = require('express'); let router = express.Router(); let app = require('express')(); let server = require('http').createServer(app); let io = require('socket.io')(server); io.on('connection', function(socket){ // socket相關監聽都要放在這個回調裏 console.log('a user connected'); // 鏈接斷開時觸發的回調 socket.on("disconnect", function() { console.log("a user go out"); }); socket.on("msg", function(obj) { // 打印客戶端傳過來的消息 let { message } = obj console.log(message); //延遲3s返回信息給客戶端 setTimeout(function(){ io.emit("msg", '你好客戶端,我是服務器'); },3000); }); }); //開啓端口監聽socket server.listen(3001); router.get('/', function(req, res, next) { res.render('im/imRoom'); }); module.exports = router;
運行結果:
服務端: a user connected 你好服務器,我是客戶端 服務端: 你好客戶端,我是服務器(3秒後)
本人菜鳥,以上就是我對websocket的一些學習,不少不足之後會持續進行補充。