01 Socket.io 簡介
- 一個100%由 JavaScript 實現、基於Node.js的用於實時通信、跨平臺的開源框架,它包括了客戶端的 JavaScript 庫和 服務器端的 Node.js 服務。
- 實現了對於其他語言的支持,如 Java、C++、Swift。
- 提供了一個與 WebSocket 類似的通用 API:
Socket.io方法與事件
【主要特點】:
(1) 可靠性(Reliability):依賴 Engine.IO, 首先建立長輪詢,然後試着升級到更好的傳輸方式,如 WebSocket。
(2)自動重連(Auto-reconnection support):除非手動設置,否則當客戶端斷開連接時會一直嘗試重連。
(3)心跳檢測(Disconnection detection):在 Engine.IO 層面實現的心跳檢測機制,允許服務器和客戶端知道哪一方不再響應。
Engine.IO:爲 Socket.IO 實現的基於傳輸、跨瀏覽器/跨設備的雙向通信層。
(4)其它特點(如下圖):
socket.io其它特點
02 工作流程
Socket.IO 底層是 Engine.IO,這個庫實現了跨平臺的雙向通信,使用下面的傳輸方式封裝了一套自己的 Socket 協議(EIO Socket)。
- polling: XHR / JSONP polling transport
- websocket: WebSocket transport
默認情況下,一個完整的 EIO Socket 包括多個 XHR 和 WebSocket 連接:
一個完整的EIO Socket連接(默認情況)
請求流程
EIO Socket 會首先發起XHR長輪詢,然後服務端會返回以下字段:
- 0:open標誌
sid
:當前連接的socket idupgrade
:表示可以把連接方式從長輪詢升級到 websocketpingInterval
:心跳間隔pingTimeout
:心跳超時時間
前端收到握手的 upgrades 後,EIO 會檢測瀏覽器是否支持 WebSocket,如果支持,就會啓動一個 WebSocket 連接,然後通過這個 WebSocket 往服務器發一條內容爲 probe, 類型爲 ping 的數據。如果這時服務器返回了內容爲 probe, 類型爲 pong 的數據,前端就會把前面建立的 HTTP 長輪詢停掉,後面只使用 WebSocket 通道進行收發數據。(socket.io 的詳細工作流程是怎樣的?)
心跳檢測:
EIO Socket生命週期內,會間隔一段時間 ping - pong 一次,用來測試網絡是否正常
WebSocket消息幀
綠色:發送;白色:接收。
類型—> 2:ping,3:pong,4:message。
Socket.IO 在 Engine.IO 的基礎上做了一些封裝,比如 Socket.IO 裏面這樣的代碼:
io.emit('add user', 'm') ;
在 Engine.io 裏面是這樣:
eio.send('message', '2["add user","m"]') ; // 2 是 socket.io 定義的包類型
因此,message的類型4後面有個2。
【傳輸機制設置】:
Socket.io 爲我們提供了選項,它的默認情況是以長輪詢開始,我們也可以手動設置成只使用 websocket 方式來進行通信。
// 設置成只使用 websocket
const socket = io({
transports: ['websocket']
});
// 重連時,重設選項
// 防止 websocket 可能因爲代理、防火牆、瀏覽器等原因連接失敗socket.on('reconnect_attempt', () => {
socket.io.opts.transports = ['polling', 'websocket'];
});
只使用websocket
03 核心方法
Socket.io提供的方法
Socket.io 的核心函數:emit
和 on
socket.emit(eventName[, ...args][, ack]):用來發射(觸發)一個事件
eventName
(string):事件名args
:要發送的數據ack
(Function):回調函數,一般省略,如需對方接受到信息後立即得到確認時需要用到- Returns
Socket
socket.emit('ferret', 'tobi', (data) => {
console.log(data); // data will be 'woot'
});
// server:
// io.on('connection', (socket) => {
// socket.on('ferret', (name, fn) => {
// fn('woot');
// });
// });
socket.on(eventName, callback):用來監聽一個 emit 發射的事件
eventName
(string):監聽的事件名callback
(Function):匿名函數,接收對方發來的數據,該匿名函數的第一個參數爲接收的數據,若有第二個參數,則爲要返回的函數- Returns
Socket
socket.on('news', (data) => {
console.log(data);
});
// with multiple arguments
socket.on('news', (arg1, arg2, arg3, arg4) => {
// ...
});
// with callback
socket.on('news', (cb) => {
cb(0);
});
Socket.io 提供了三種默認的事件(客戶端和服務器都有):
connect
:當與對方建立連接後自動觸發;message
:當收到對方發來的數據後觸發;disconnect
:當對方關閉鏈接後觸發。
除了 socket.io 自身提供的事件之外,還支持自定義事件,豐富了通信:
// 如:
socket.on(‘new message’, function(data) {});
socket.emit(‘new message’, { message: message });
【服務端廣播的三種情況】:
服務器廣播
客戶端:
const socket = io();
// 監聽事件
socket.on(‘message’, (data) => {});
// 觸發事件
socket.emit(‘message’, { message });
服務端:
io.on(‘connection’, function(socket) {
socket.on(‘message’, function(data) {
// 1.廣播給自己
socket.emit(‘message’, data);
// 2. 廣播給除了自己的其它客戶端
socket.broadcast.emit(‘message’, data);
// 3. 廣播給所有客戶端
io.emit(‘message’, data); // 等同於 io.sockets.emit()
});
});
04 Rooms 和命名空間
【作用】:減少TCP連接數的同時區分不同的通信頻道(在不同的路由層面能體現該作用,具體請參考 socket.io 中namespace 和 room的概念)、實現私聊
默認的命名空間:io.sockets、io
io.on('connection', function(socket){
socket.on('disconnect', function(){ });
});
自定義命名空間:
// 服務器端
var nsp = io.of('/my-namespace');
nsp.on('connection', function(socket){
socket.on('disconnect', function(){ });
});
// 客戶端
var socket = io('/my-namespace');
Rooms:
// 自定義room
io.on('connection', function(socket){
socket.join('some room')); // 加入房間
socket.leave('some room'); // 離開房間
});
// 向房間裏的所有客戶端發送消息
io.to('some room').emit('some event');
// 默認房間(每一個id一個room)
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
獲取房間信息:socket.adapter.rooms
rooms對象
默認情況下,每一個 id 便自成一個房間,房間名爲
socket.id
(指定命名空間之後,前面會帶上命名空間);
自定義房間之後,原先的默認房間仍然存在;
房間爲一個對象,包含當前進入房間的 sockets 以及長度。
05 打造基礎聊天室
從官網最基礎的聊天小例子入門,又分析了一下 Demos 中的 Chat demo 源碼之後,自己試着用 react 實現了一遍,具體的功能及原理如下:
-
最基礎聊天功能
【實現原理】:服務端運用
io.emit
進行廣播給每個建立連接的客戶端。 -
登錄(Chat Demo)
【核心操作】:用戶登錄時(
login
事件),服務端爲當前的客戶端存儲 username。socket.username = username
-
顯示用戶進入/離開
【實現原理】:用戶登錄(
login
)時,觸發user joind
事件;用戶斷開連接(disconnection
)時,觸發user left
事件—> 均通過socket.broadcase.emit
廣播給其它客戶端。 -
顯示當前聊天室人數
【實現原理】:操作 numUsers 變量—> 監聽
connection
事件:++numUsers;監聽disconnection
事件:--numUsers 。 -
顯示各自暱稱
【實現原理】:觸發
chat
事件的時候將當前連接的 username 通過io.emit
廣播給每個給客戶端。 -
提示對方正在輸入
【實現原理】:監聽
typing
和stop typing
事件 —> 均通過socket.broadcast.emit
廣播給其它客戶端。(typing
通過監聽輸入框的onKeyPress
事件進行觸發)注意:當發送完信息之後,需要清空“***正在輸入”,客戶端在監聽到
chat
時可將提示置空。 -
實現私人聊天
私人聊天
【實現原理】:運用 Room,通過 socket.adapter.rooms
獲取當前 room 的信息,包括每個 room 中的 id。
// 加入房間
socket.join('some room');
// 離開房間
socket.leave('some room');
// 向房間裏的所有客戶端發送消息
io.to('some room').emit('some event');
// 向房間中的除了自己的客戶端發送消息
socket.broadcast.to ('some room')
.emit('my message', msg);
自娛自樂的實現版本:
github地址:ioChat
添加暱稱+xxx正在輸入
監聽用戶進入或離開/顯示在線人數
Rooms 實現私人聊天
ps:下一次我會出一篇聊天室實現 emoji 表情發送的文章,敬請期待喲~~٩(๑>◡<๑)۶ ~~
§ 參考資料
- socket.io官網
- socketio/chat-example
- React socket.io express 開發一個聊天室
- Vue全家桶+Socket.io+Koa2打造一個智能聊天室
- Express結合Webpack的全棧自動刷新
- 在 Express 開發中使用 nodemon(使用 nodemon 自動重啓服務器)
- 使用socket.io打造公共聊天室
- socket.io 的詳細工作流程是怎樣的?
- socket.io 中namespace 和 room的概念
轉自https://www.jianshu.com/p/51b0d1f80392