Socket.IO打造基礎(chǔ)聊天室

Socket.IO

01 Socket.io 簡(jiǎn)介

  • 一個(gè)100%由 JavaScript 實(shí)現(xiàn)颗品、基于Node.js的用于實(shí)時(shí)通信躯枢、跨平臺(tái)的開源框架锄蹂,它包括了客戶端的 JavaScript 庫和 服務(wù)器端的 Node.js 服務(wù)败匹。
  • 實(shí)現(xiàn)了對(duì)于其他語言的支持掀亩,如 Java槽棍、C++炼七、Swift豌拙。
  • 提供了一個(gè)與 WebSocket 類似的通用 API:
Socket.io方法與事件

主要特點(diǎn)】:

(1) 可靠性(Reliability):依賴 Engine.IO, 首先建立長(zhǎng)輪詢按傅,然后試著升級(jí)到更好的傳輸方式唯绍,如 WebSocket况芒。
(2)自動(dòng)重連(Auto-reconnection support):除非手動(dòng)設(shè)置绝骚,否則當(dāng)客戶端斷開連接時(shí)會(huì)一直嘗試重連皮壁。
(3)心跳檢測(cè)(Disconnection detection):在 Engine.IO 層面實(shí)現(xiàn)的心跳檢測(cè)機(jī)制蛾魄,允許服務(wù)器和客戶端知道哪一方不再響應(yīng)滴须。

Engine.IO:為 Socket.IO 實(shí)現(xiàn)的基于傳輸、跨瀏覽器/跨設(shè)備的雙向通信層痛侍。

(4)其它特點(diǎn)(如下圖):

socket.io其它特點(diǎn)

02 工作流程

Socket.IO 底層是 Engine.IO主届,這個(gè)庫實(shí)現(xiàn)了跨平臺(tái)的雙向通信君丁,使用下面的傳輸方式封裝了一套自己的 Socket 協(xié)議(EIO Socket)绘闷。

  • polling: XHR / JSONP polling transport
  • websocket: WebSocket transport

默認(rèn)情況下较坛,一個(gè)完整的 EIO Socket 包括多個(gè) XHR 和 WebSocket 連接:

一個(gè)完整的EIO Socket連接(默認(rèn)情況)
請(qǐng)求流程

EIO Socket 會(huì)首先發(fā)起XHR長(zhǎng)輪詢华嘹,然后服務(wù)端會(huì)返回以下字段:

  • 0:open標(biāo)志
  • sid:當(dāng)前連接的socket id
  • upgrade:表示可以把連接方式從長(zhǎng)輪詢升級(jí)到 websocket
  • pingInterval:心跳間隔
  • pingTimeout:心跳超時(shí)時(shí)間

前端收到握手的 upgrades 后除呵,EIO 會(huì)檢測(cè)瀏覽器是否支持 WebSocket,如果支持秉剑,就會(huì)啟動(dòng)一個(gè) WebSocket 連接侦鹏,然后通過這個(gè) WebSocket 往服務(wù)器發(fā)一條內(nèi)容為 probe, 類型為 ping 的數(shù)據(jù)。如果這時(shí)服務(wù)器返回了內(nèi)容為 probe, 類型為 pong 的數(shù)據(jù)价卤,前端就會(huì)把前面建立的 HTTP 長(zhǎng)輪詢停掉慎璧,后面只使用 WebSocket 通道進(jìn)行收發(fā)數(shù)據(jù)胸私。(socket.io 的詳細(xì)工作流程是怎樣的鳖谈?

心跳檢測(cè):
EIO Socket生命周期內(nèi)缆娃,會(huì)間隔一段時(shí)間 ping - pong 一次贯要,用來測(cè)試網(wǎng)絡(luò)是否正常

WebSocket消息幀

綠色:發(fā)送郭毕;白色:接收显押。
類型—> 2:ping乘碑,3:pong兽肤,4:message。

Socket.IO 在 Engine.IO 的基礎(chǔ)上做了一些封裝电禀,比如 Socket.IO 里面這樣的代碼:

io.emit('add user', 'm') ;

在 Engine.io 里面是這樣:

eio.send('message', '2["add user","m"]') ; // 2 是 socket.io 定義的包類型

因此尖飞,message的類型4后面有個(gè)2政基。

傳輸機(jī)制設(shè)置】:

Socket.io 為我們提供了選項(xiàng)沮明,它的默認(rèn)情況是以長(zhǎng)輪詢開始,我們也可以手動(dòng)設(shè)置成只使用 websocket 方式來進(jìn)行通信酱畅。

// 設(shè)置成只使用 websocket
const socket = io({ 
       transports: ['websocket'] 
}); 

// 重連時(shí)圣贸,重設(shè)選項(xiàng)
// 防止 websocket 可能因?yàn)榇碛蹙⒎阑饓τ煤g覽器等原因連接失敗socket.on('reconnect_attempt', () => { 
       socket.io.opts.transports = ['polling', 'websocket']; 
}); 
只使用websocket

03 核心方法

Socket.io提供的方法

Socket.io 的核心函數(shù):emiton

socket.emit(eventName[, ...args][, ack]):用來發(fā)射(觸發(fā))一個(gè)事件

  • eventName(string):事件名
  • args:要發(fā)送的數(shù)據(jù)
  • ack(Function):回調(diào)函數(shù),一般省略瘟斜,如需對(duì)方接受到信息后立即得到確認(rèn)時(shí)需要用到
  • 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):用來監(jiān)聽一個(gè) emit 發(fā)射的事件

  • eventName(string):監(jiān)聽的事件名
  • callback(Function):匿名函數(shù)虽惭,接收對(duì)方發(fā)來的數(shù)據(jù)芽唇,該匿名函數(shù)的第一個(gè)參數(shù)為接收的數(shù)據(jù)匆笤,若有第二個(gè)參數(shù)炮捧,則為要返回的函數(shù)
  • 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 提供了三種默認(rèn)的事件(客戶端和服務(wù)器都有):

  • connect:當(dāng)與對(duì)方建立連接后自動(dòng)觸發(fā)咆课;
  • message:當(dāng)收到對(duì)方發(fā)來的數(shù)據(jù)后觸發(fā)傀蚌;
  • disconnect:當(dāng)對(duì)方關(guān)閉鏈接后觸發(fā)善炫。

除了 socket.io 自身提供的事件之外箩艺,還支持自定義事件艺谆,豐富了通信:

// 如:
socket.on(‘new message’, function(data) {});
socket.emit(‘new message’, { message: message });

服務(wù)端廣播的三種情況】:

服務(wù)器廣播

客戶端:

const socket = io();
// 監(jiān)聽事件
socket.on(‘message’, (data) => {});
// 觸發(fā)事件
socket.emit(‘message’, { message }); 

服務(wù)端:

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連接數(shù)的同時(shí)區(qū)分不同的通信頻道(在不同的路由層面能體現(xiàn)該作用,具體請(qǐng)參考 socket.io 中namespace 和 room的概念)居凶、實(shí)現(xiàn)私聊

默認(rèn)的命名空間:io.sockets侠碧、io

io.on('connection', function(socket){    
      socket.on('disconnect', function(){ });
}); 

自定義命名空間:

// 服務(wù)器端
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'); // 離開房間
}); 

// 向房間里的所有客戶端發(fā)送消息
io.to('some room').emit('some event'); 

// 默認(rèn)房間(每一個(gè)id一個(gè)room)
socket.on('say to someone', function(id, msg){    
       socket.broadcast.to(id).emit('my message', msg); 
}); 

獲取房間信息:socket.adapter.rooms

rooms對(duì)象

默認(rèn)情況下药蜻,每一個(gè) id 便自成一個(gè)房間语泽,房間名為 socket.id(指定命名空間之后湿弦,前面會(huì)帶上命名空間);
自定義房間之后蝶俱,原先的默認(rèn)房間仍然存在榨呆;
房間為一個(gè)對(duì)象,包含當(dāng)前進(jìn)入房間的 sockets 以及長(zhǎng)度彻消。

05 打造基礎(chǔ)聊天室

從官網(wǎng)最基礎(chǔ)的聊天小例子入門宾尚,又分析了一下 Demos 中的 Chat demo 源碼之后煌贴,自己試著用 react 實(shí)現(xiàn)了一遍牛郑,具體的功能及原理如下:

  • 最基礎(chǔ)聊天功能

    【實(shí)現(xiàn)原理】:服務(wù)端運(yùn)用 io.emit 進(jìn)行廣播給每個(gè)建立連接的客戶端淹朋。

  • 登錄(Chat Demo)

    【核心操作】:用戶登錄時(shí)(login 事件)列林,服務(wù)端為當(dāng)前的客戶端存儲(chǔ) username瑞你。

    socket.username = username
    
  • 顯示用戶進(jìn)入/離開

    【實(shí)現(xiàn)原理】:用戶登錄(login)時(shí),觸發(fā) user joind 事件希痴;用戶斷開連接(disconnection)時(shí)者甲,觸發(fā) user left 事件—> 均通過 socket.broadcase.emit 廣播給其它客戶端。

  • 顯示當(dāng)前聊天室人數(shù)

    【實(shí)現(xiàn)原理】:操作 numUsers 變量—> 監(jiān)聽 connection 事件:++numUsers砌创;監(jiān)聽 disconnection 事件:--numUsers 虏缸。

  • 顯示各自昵稱

    【實(shí)現(xiàn)原理】:觸發(fā) chat 事件的時(shí)候?qū)?dāng)前連接的 username 通過 io.emit 廣播給每個(gè)給客戶端。

  • 提示對(duì)方正在輸入

    【實(shí)現(xiàn)原理】:監(jiān)聽 typingstop typing 事件 —> 均通過 socket.broadcast.emit 廣播給其它客戶端嫩实。(typing 通過監(jiān)聽輸入框的 onKeyPress 事件進(jìn)行觸發(fā))

    注意:當(dāng)發(fā)送完信息之后刽辙,需要清空“***正在輸入”,客戶端在監(jiān)聽到 chat 時(shí)可將提示置空宰缤。

  • 實(shí)現(xiàn)私人聊天

私人聊天

【實(shí)現(xiàn)原理】:運(yùn)用 Room氧骤,通過 socket.adapter.rooms 獲取當(dāng)前 room 的信息,包括每個(gè) room 中的 id并思。

// 加入房間
socket.join('some room'); 
// 離開房間
socket.leave('some room'); 

// 向房間里的所有客戶端發(fā)送消息
io.to('some room').emit('some event'); 

// 向房間中的除了自己的客戶端發(fā)送消息
socket.broadcast.to ('some room')
    .emit('my message', msg);
自娛自樂的實(shí)現(xiàn)版本:

github地址:ioChat

添加昵稱+xxx正在輸入
監(jiān)聽用戶進(jìn)入或離開/顯示在線人數(shù)
Rooms 實(shí)現(xiàn)私人聊天

ps:下一次我會(huì)出一篇聊天室實(shí)現(xiàn) emoji 表情發(fā)送的文章桃熄,敬請(qǐng)期待喲~~?(?>?<?)? ~~

§ 參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末划栓,一起剝皮案震驚了整個(gè)濱河市帅掘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寓免,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠母,死亡現(xiàn)場(chǎng)離奇詭異踩寇,居然都是意外死亡睛榄,警方通過查閱死者的電腦和手機(jī)憎乙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門阵谚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗡午,“玉大人,你說我怎么就攤上這事宵距∩谘迹” “怎么了峡蟋?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮氮发,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘食零。我一直安慰自己百宇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布评肆。 她就那樣靜靜地躺著俄占,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枝哄,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天舶掖,我揣著相機(jī)與錄音,去河邊找鬼秦效。 笑死犀概,一個(gè)胖子當(dāng)著我的面吹牛镊叁,可吹牛的內(nèi)容都是我干的像樊。 我是一名探鬼主播涂滴,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼加缘!你這毒婦竟也來了拣宏?” 一聲冷哼從身側(cè)響起学歧,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤徙融,失蹤者是張志新(化名)和其女友劉穎蛛芥,沒想到半個(gè)月后胸哥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涯竟,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了庐船。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片银酬。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筐钟,靈堂內(nèi)的尸體忽然破棺而出揩瞪,到底是詐尸還是另有隱情,我是刑警寧澤篓冲,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布李破,位于F島的核電站,受9級(jí)特大地震影響壹将,放射性物質(zhì)發(fā)生泄漏嗤攻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一诽俯、第九天 我趴在偏房一處隱蔽的房頂上張望妇菱。 院中可真熱鬧,春花似錦惊畏、人聲如沸恶耽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偷俭。三九已至,卻和暖如春缰盏,著一層夾襖步出監(jiān)牢的瞬間涌萤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工口猜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留负溪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓济炎,卻偏偏與公主長(zhǎng)得像川抡,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子须尚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359