前言:
大家剛學socket編程的時候, 往往以聊天室作為學習DEMO, 實現簡單且上手容易. 該Demo被不同語言實現和演繹, 網上相關資料亦不勝枚舉. 以至於很多技術書籍在講解網絡相關的編程時, 不再采用聊天室做為基礎案列, 而采用其他案例. 比如之前火熱一時的"你猜我畫", 以避免顯得很大眾.
但說實話, 幾乎所有的網絡程序追蹤溯源, 都可以從聊天室中找到影子. 在線聊天室也不局限於簡單的單機服務, 其分布式實現技術含量十足. 猶如達芬奇畫雞蛋, 中間雖枯燥, 但堅持不懈, 精益求精. 終於水滴石穿, 從量變到質變.
本章將講講, websocket的協議和javascript版的API.
websocket協議:
websocket基於tcp的雙向通訊協議. 其協議可以分為兩個部分, 握手和數據傳輸. 其握手協議構建於http/https, 而數據傳輸協議則脫離於http/https.
websocket協議歷經了很多版本的修改和升級, 字段和約定的差異, 使得編碼時需要注意版本的兼容性. 當前使用最普遍的是版本13.
其協議uri可以表示為"ws://{host}:{port}/{path}", 在ssl/tls下是"wss://{host}:{port}/{path}".
• 握手協議
在該階段, 其交換為request/response的方式進行.
如簡單的例子為案例, 其http請求頭中包含如下的字段:
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: A2mIDkRXEgl0+79uPwhsOw==
Sec-WebSocket-Version: 13
服務端通過識別header中的Upgrade:websocket字段來區分正常的http請求還是websocket協議.
而Sec-WebSocket-Key則是客戶端生成的一個隨機key, 用於和服務端進行的驗證工作.
服務端的響應如下所示:
Sec-WebSocket-Accept: hQCy41pGdjZ222NKXfyrxQUHZEQ=
其http響應碼為101, Sec-WebSocket-Accept為服務端對應於客戶端Sec-WebSocket-Key的驗證值.
其具體的算法, 可以描述如下:
${Sec-WebSocket-Accept}=base64(sha1(${Sec-WebSocket-Key}+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
• 數據協議
在該階段, 客戶端和服務器其數據交換格式, 就約定為Frame模式了. 一般一條消息為一個frame, 當然一個消息可以由多個frame組成. 這樣的好處, 就像http的chunk模式一樣, 一邊生成一邊傳輸.
Frame具體的定義如下所示:
每個字段都有其具體的含義, 這邊就不再具體的展開了.
其Frame的分類可以大致如下:
Ping/Pong Frame由應用層協議本身完成, 這樣基於websocket開發的網絡應用服務, 就可以少去了死鏈檢測/重連這一環節了.
Text Frame往往用的比較多, 也有使用Binary Frame的, 比如基於websocket實現的MQTT服務.
Connection Close Frame是一種友好協商斷掉連接的一種方式.
Javascript版API:
WebSocket其對應的javascript代碼如下:
// *) websocket鏈接本身的狀態 WebSocket.CONNECTING = 0; WebSocket.OPEN = 1; WebSocket.CLOSING = 2; WebSocket.CLOSED = 3; // *) websocket實體對象的成員 WebSocket.prototype.url = null; WebSocket.prototype.readyState = 0; WebSocket.prototype.bufferedAmount = 0; WebSocket.prototype.extensions = null; WebSocket.prototype.protocol = null; // *) websocket實體對應的回調函數, 需要被繼承實現 WebSocket.prototype.onopen = 0; WebSocket.prototype.onmessage = 0; WebSocket.prototype.onerror = 0; WebSocket.prototype.onclose = 0; // *) websocket實體的send/close方法 function WebSocket(url,protocols) {} WebSocket.prototype.send = function(data) {}; WebSocket.prototype.close = function(code,reason) {};
主要還是websocket對應的回調函數實現, 當然也需知道websocket本身所處的狀態.
總結:
該篇博文有堆砌之感, 但如果你想實現一個基於websocket實現的聊天室, 對其內部的協議和流程需要有個清晰的了解. 特別是之后的基於Netty開發的服務器, 雖然可以照貓畫虎, 但知其然不知其所以然.
本文參考了websocket的wiki, 以及rfc說明.
寫在最后:
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.