Socket.io源碼分析

Socket.io作為websocket的協議實現,應用比較廣泛。它包括2個組件:Socket.io和Socket.io-client。

筆者是使用socket.io 1.x.x系列最後一個版本1.7.4。

該篇文章主要從客戶端的角度來進行源碼分析。筆者也讀過很多大神的源碼分析文章。可能大神們的文章都是經過加工提煉的,但是作為讀者有時候總有被一些拐彎的地方卡住,導致思路不能通達,進而理解有限,感覺文章晦澀難懂。所以這篇文章筆者基本上是平鋪直述,有遺漏的可以在評論里提出。

先上個Socket.io-client的示例代碼如下

運行起來後的一些日誌

開始運行後,會走到示例代碼中第5行代碼:

1. socket.io-client/index.js

調式到源碼是在socket.io-client/index.js里的lookup函數。

上面源碼第44行解析了url,結果如下:

可以看到解析出port啊,href啊,source啊等。所以在示例代碼中,配置的參數port不起作用。

運行下來應該是到60行。

這個會進入socket.io-client/manager.js。

2. socket.io-client/manager.js

Manager構造函數做的事情是配置屬性,最終在第68行代碼,打開連接(應該說開始連接)。

在manager.js的open方法中,最重要就是213行,調用engine.io-client/socket.js,創建了engine的socket對象。

筆者讀到這裡其實有點疑惑。很多文章也講過socket.io是調用engine.io。但是socket.io-client里有個socket.js,而engine.io-client里也有個socket.js。這點造成了筆者當時讀代碼時的混亂。

3. engine.io-client/socket.js

繼續看engine.io-client/socket.js, socket.js構造函數也是設置屬性

這裡第40行也是解析uri,而且結果和socket.io-client里解析的結果一摸一樣,感覺有點冗餘。

接下來第73行可以發現,默認情況下使用了2個協議polling和websocket。

你也可以在io.connect的opts中設置這個協議,代碼如下。

最終配置好屬性後,最後在第120行繼續打開連接這個功能。

該open函數還是engine.io-client/socket.js的函數。在228行,把剛才設置的協議的第一個polling給了transport變數,然後設置狀態為opening。

在234行,開始構建發送協議的結構,調用了自身的createTransport函數。

在createTransport函數里,第162行中,這個parser是engine.io-parser,這個protocol屬性是個常量3。

源碼上有注釋,但是感覺寫的不夠清楚。這個3會在link上顯示處理EIO=3,具體含義這裡不討論。

繼續剛才的函數,在第170行,調用了transports對象來構建所需的對象。可以看下transports對象里有什麼:polling和websocket兩個對象集合。

從引用角度看,是在第146行代碼

而transports文件夾結構和index源碼可以看到,導出了polling.js和websocket.js兩個文件

4. engine.io-client/transports/polling.js

因為是使用polling協議,所以調用到了engine.io-client/transports/polling.js。

在polling的構造函數里, 在第46行創建了XMLHttpRequest,這行代碼調試進去很奇怪。

因為require的是xmlhttprequest-ssl,實際調用到engine.io-client/xmlhttprequest.js。這個筆者還不清楚為什麼。可以看到在xmlhttprequest.js里,最終創建了瀏覽器的XMLHttpRequest對象。

繼續回到polling函數里。最終走到第49行代碼,創建了XHR對象。這個對象是polling-xhr.js。

其實在這裡有一點不理解。千辛萬苦創建了個xhr對象,結果沒有用到。

從46 48 49行來看,這個xhr就是起了個判斷作用。

5. engine.io-client/transports/polling-xhr.js

進入polling-xhr後,發現第一行代碼竟然是往上回調。

在回調的Polling的構造函數中,繼續回調transports。

這種回調的作用應該是把底層自己對象this(帶上自己的屬性)傳給上層js類,這樣就可以擁有上層的一些特有屬性。

這裡使用了javascript的call的繼承用法,後面會發現這樣的妙處。

到此,transport對象創建完畢。創建出來的transport對象時xhr,具體信息可以看下面:

看到這裡筆者也有點扛不住了。還好回到engine.io-client/socket.js open函數,就要開始打開連接了。

6. engine.io-client/transport.js

調用engine.io-client/transport.js,還是繼續打開。但是可以發現這個this是XHR。所以進去的是engine.io-client/transports/polling.js。

這就是筆者上面提到的妙處。不僅繼承了底層自己的屬性,還能夠把上層的特有屬性加進去。在polling-xhr里沒有doOpen函數,該函數在polling.js和websocket.js里有。而這個this是xhr對象,但是可以調用了polling.js的doOpen函數。

這裡筆者就貼貼調用的順序,不解釋了。

7. engine.io-client/transports/polling.js

一直到調用到engine.io-client/transports/polling-xhr.js。

8. engine.io-client/transports/polling-xhr.js

在polling-xhr.js里,第124行創建一個request對象

其實創建過程中就是發送Ajax請求。看下面代碼。

第194行,又創建一個XMLHttpRequest對象。這次這個是玩真的了。

第199行,準備向服務端發送請求。

第213行和第227行設置xhr的各種屬性。

第247行設置onreadystatechange回調函數。

第262行發送請求。

Server端打開情況下,收到server端返回數據結果:

進入第250行處理數據,調用engine.io-client/transports/polling-xhr.js的onLoad函數。可以看到服務端以位元組方式把數據返回給client端。而且這個數據是用來提升協議為websocket。

回調監聽data的函數。結果是調用engine.io-client/transports/polling-xhr.js,如下第127行。

繼續回調到engine.io-client/transports/polling.js onData函數,又一次體現了剛才那個call繼承的好處。

9. engine.io-client/transports/polling.js

在engine.io-client/transports/polling.js里,第148行會去解析收到的data數據,並且回調到131行定義的callback回調函數。

回調函數里的144行,可以發現又要回調了。

具體解析過程就不介紹了。可以看下面的解析結果。

處理完parser,執行到callback里,因為狀態還是opening中,所以調用到134行代碼。

這一把,就回調到engine.io-client/transport.js,再一次體現call繼承的好處。

10. engine.io-client/transport.js

從代碼可以看出,現在狀態改為open了。

因為transport.js 沒有註冊監聽open的回調函數,所以onOpen函數結束。

回到callback函數,繼續走到到了144行代碼。回調結果又是transport.js

這把回調到engine.io-client/socket.js。

11. engine.io-client/socket.js

在engine.io-client/socket.js里,回調到269行。

調用engine.io-client/socket.js onPacket函數里,你會發現client端又要去握手了。這次是升級協議的握手。解析數據的就不解釋了先。

升級協議為websocket

在onHandshake中,第472行設置屬性upgrades里是[『websocket』](server端傳過來的數據)。關鍵的475行代碼,會重新調用onOpen函數。

調用onOpen函數,第406行代碼會清空原來的polling的回調設置。

第414行代碼是執行升級協議的真正函數probe.。這裡筆者又有點困惑,onOpen函數里的this指向(或者說包含)engine.io-client/socket.js,所以調用了engine.io-client/socket.js probe函數。

在probe函數里,第288行代碼又去創建transport,因為這時name的值是websocket,所以創建出來的transport是WS。在probe函數的功能,是設置很多回調,然後393行又去open。

這次open是WS的open,所以this是WS,指向engine.io-client/transports/websocket.js

12. engine.io-client/transports/websocket.js

在engine.io-client/transports/websocket.js里的doOpen函數比較簡單。而且是使用瀏覽器自帶的websocket對象。

到這裡,websocket對象創建完成(但是還沒有使用),並且polling的使命也即將結束了。所以在engine.io-client/transports/polling.js里onData函數會有這樣的處理。

13. engine.io-client/transport/polling.js

結束poll(154),並且重新請求(157)。

Poll又是走一遍創建XMLHttpRequest對象,並且發送流程。

14. engine.io-client/transports/websocket.js

當請求返回後,調用engine.io-client/transports/websocket.js 的回調函數。

打開了websocket連接後,client會發送個簡單的請求過去,內容是「2probe」。

這樣後續server端發送的數據就都是由engine.io-client/transports/websocket.js的回調函數接收。

這裡基本把socket.io-client啟動連接到升級為websocket請求的代碼流程說完了。實在是太長了。其他的細節,比如用戶代碼如何註冊,和websocket的註冊代碼如何調用等。後續文章再講。


推薦閱讀:

element-ui: 滾動頁面,為什麼date-picker不隨之移動?
2018最新web前端面試題
JS中的原型對象
合格前端系列第十一彈-揭秘組件庫一二事(中)
淺入淺出前端這些技術

TAG:socketio | 源代碼 | 前端開發框架和庫 |