隨着web技術的發展,使用場景和需求也愈來愈複雜,客戶端再也不知足於簡單的請求獲得狀態的需求。實時通信愈來愈多應用於各個領域。javascript
HTTP是最經常使用的客戶端與服務端的通訊技術,可是HTTP通訊只能由客戶端發起,沒法及時獲取服務端的數據改變。只能依靠按期輪詢來獲取最新的狀態。時效性沒法保證,同時更多的請求也會增長服務器的負擔。html
WebSocket技術應運而生。前端
不一樣於HTTP半雙工協議,WebSocket是基於TCP 鏈接的全雙工協議,支持客戶端服務端雙向通訊。java
WebSocket
使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。node
在WebSocket API
中,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。git
WebSocket對象一共支持四個消息 onopen, onmessage, onclose和onerror。github
經過javascript能夠快速的創建一個WebSocket鏈接:web
var Socket = new WebSocket(url, [protocol] );
以上代碼中的第一個參數url
, 指定鏈接的URL。第二個參數 protocol
是可選的,指定了可接受的子協議。express
同http協議使用http://
開頭同樣,WebSocket協議的URL使用ws://
開頭,另外安全的WebSocket協議使用wss://
開頭。npm
Socket.onopen = function(evt) {};
Socket.onerror = function(evt) { };
Socket.onclose = function(evt) { };
Socket.onmessage = function(evt) { };
Socket.send();
WebSocket是跟隨HTML5一同提出的,因此在兼容性上存在問題,這時一個很是好用的庫就登場了——Socket.io。
socket.io封裝了websocket,同時包含了其它的鏈接方式,你在任何瀏覽器裏均可以使用socket.io來創建異步的鏈接。socket.io包含了服務端和客戶端的庫,若是在瀏覽器中使用了socket.io的js,服務端也必須一樣適用。
socket.io是基於 Websocket 的Client-Server 實時通訊庫。
socket.io底層是基於engine.io這個庫。engine.io爲 socket.io 提供跨瀏覽器/跨設備的雙向通訊的底層庫。engine.io使用了 Websocket 和 XHR 方式封裝了一套 socket 協議。在低版本的瀏覽器中,不支持Websocket,爲了兼容使用長輪詢(polling)替代。
Socket.io容許你觸發或響應自定義的事件,除了connect,message,disconnect這些事件的名字不能使用以外,你能夠觸發任何自定義的事件名稱。
const socket = io("ws://0.0.0.0:port"); // port爲本身定義的端口號 let io = require("socket.io")(http); io.on("connection", function(socket) {})
1、發送數據
socket.emit(自定義發送的字段, data);
2、接收數據
socket.on(自定義發送的字段, function(data) { console.log(data); })
1、所有斷開鏈接
let io = require("socket.io")(http); io.close();
2、某個客戶端斷開與服務端的連接
// 客戶端 socket.emit("close", {});
// 服務端 socket.on("close", data => { socket.disconnect(true); });
有時候websocket有以下的使用場景:1.服務端發送的消息有分類,不一樣的客戶端須要接收的分類不一樣;2.服務端並不須要對全部的客戶端都發送消息,只須要針對某個特定羣體發送消息;
針對這種使用場景,socket中很是實用的namespace和room就上場了。
先來一張圖看看namespace與room之間的關係:
服務端
io.of("/post").on("connection", function(socket) { socket.emit("new message", { mess: `這是post的命名空間` }); }); io.of("/get").on("connection", function(socket) { socket.emit("new message", { mess: `這是get的命名空間` }); });
客戶端
// index.js const socket = io("ws://0.0.0.0:****/post"); socket.on("new message", function(data) { console.log('index',data); } //message.js const socket = io("ws://0.0.0.0:****/get"); socket.on("new message", function(data) { console.log('message',data); }
客戶端
//可用於客戶端進入房間; socket.join('room one'); //用於離開房間; socket.leave('room one');
服務端
io.sockets.on('connection',function(socket){ //提交者會被排除在外(即不會收到消息) socket.broadcast.to('room one').emit('new messages', data); // 向全部用戶發送消息 io.sockets.to(data).emit("recive message", "hello,房間中的用戶"); }
終於來到應用的階段啦,服務端用node.js
模擬了服務端接口。如下的例子都在本地服務器中實現。
先來看看服務端,先來開啓一個服務,安裝express
和socket.io
npm install --Dev express npm install --Dev socket.io
let app = require("express")(); let http = require("http").createServer(handler); let io = require("socket.io")(http); let fs = require("fs"); http.listen(port); //port:輸入須要的端口號 function handler(req, res) { fs.readFile(__dirname + "/index.html", function(err, data) { if (err) { res.writeHead(500); return res.end("Error loading index.html"); } res.writeHead(200); res.end(data); }); } io.on("connection", function(socket) { console.log('鏈接成功'); //鏈接成功以後發送消息 socket.emit("new message", { mess: `初始消息` }); });
核心代碼——index.html(向服務端發送數據)
<div>發送信息</div> <input placeholder="請輸入要發送的信息" /> <button onclick="postMessage()">發送</button>
// 接收到服務端傳來的name匹配的消息 socket.on("new message", function(data) { console.log(data); }); function postMessage() { socket.emit("recive message", { message: content, time: new Date() }); messList.push({ message: content, time: new Date() }); }
核心代碼——message.html(從服務端接收數據)
socket.on("new message", function(data) { console.log(data); });
實時通信效果
客戶端所有斷開鏈接
某客戶端斷開鏈接
namespace應用
加入房間
離開房間
npm install socket.io-client
const socket = require('socket.io-client')('http://localhost:port'); componentDidMount() { socket.on('login', (data) => { console.log(data) }); socket.on('add user', (data) => { console.log(data) }); socket.on('new message', (data) => { console.log(data) }); }
Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cache-Control: no-cache Connection: Upgrade Cookie: MEIQIA_VISIT_ID=1IcBRlE1mZhdVi1dEFNtGNAfjyG; token=0b81ffd758ea4a33e7724d9c67efbb26; io=ouI5Vqe7_WnIHlKnAAAG Host: 0.0.0.0:2699 Origin: http://127.0.0.1:5500 Pragma: no-cache Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Key: PJS0iPLxrL0ueNPoAFUSiA== Sec-WebSocket-Version: 13 Upgrade: websocket User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
請求包說明:
應答包說明:
Connection: Upgrade Sec-WebSocket-Accept: I4jyFwm0r1J8lrnD3yN+EvxTABQ= Sec-WebSocket-Extensions: permessage-deflate Upgrade: websocket
EIO: 3 transport: websocket sid: 8Uehk2UumXoHVJRzAAAA
WebSocket協議使用幀(Frame)收發數據,在控制檯->Frames中能夠查看發送的幀數據。
其中幀數據前的數字表明什麼意思呢?
這是 Engine.io協議,其中的數字是數據包編碼:
<Packet type id> [<data>]
2 ping——由客戶端發送。服務器應該用包含相同數據的乓包應答
客戶端發送:2probe探測幀
3 pong——由服務器發送以響應ping數據包。
服務器發送:3probe,響應客戶端
實例
以上的截圖是上述例子中數據傳輸的實例,分析一下大概過程就是:
爲了知道Client和Server連接是否正常,項目中使用的ClientSocket和ServerSocket都有一個心跳的線程,這個線程主要是爲了檢測Client和Server是否正常連接,Client和Server是否正常連接主要是用ping pong流程來保證的。
該心跳按期發送的間隔是socket.io默認設定的25m,在上圖中也可觀察發現。該間隔可經過配置修改。
本文發佈於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。