Socket.io源碼分析
http://Socket.io作為websocket的協議實現,應用比較廣泛。它包括2個組件:http://Socket.io和Socket.io-client。
筆者是使用http://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對象。
筆者讀到這裡其實有點疑惑。很多文章也講過http://socket.io是調用http://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中的原型對象
※合格前端系列第十一彈-揭秘組件庫一二事(中)
※淺入淺出前端這些技術