WebSocket
是一種在單個TCP鏈接上進行全雙工通訊的協議, WebSocket
通訊協議於2011年被IETF定爲標準RFC 6455
並由RFC7936
補充規範.html
WebSocket
使得客戶端和服務器之間的數據交換變得更加簡單, 使用WebSocket
的API只須要完成一次握手
就直接能夠建立持久性的鏈接並進行雙向數據傳輸.git
WebSocket
支持的客戶端不只限於瀏覽器
(Web應用), 在現今應用市場內的衆多App客戶端的長鏈接推送服務都有一大部分是基於WebSocket
協議來實現交互的.github
Websocket
因爲使用HTTP協議升級而來, 在協議交互初期須要根據正常HTTP協議交互流程. 所以, Websocket也很容易創建在SSL數據加密技術的基礎上進行通訊.web
WebSocket
與HTTP協議實現相似但也略有不一樣. 前面提到: WebSocket
協議在進行交互以前須要進行握手
, 握手協議
的交互就是利用HTTP協議
升級而來.chrome
衆所周知, HTTP協議是一種無狀態的協議. 對於這種創建在請求->迴應
模式之上的鏈接, 即便在HTTP/1.1
的規範上實現了Keep-alive
也避免不了這個問題.編程
因此, Websocket
經過HTTP/1.1
協議的101
狀態碼進行協議升級協商, 在服務器支持協議升級的條件下將回應升級請求來完成HTTP->TCP
的協議升級
.瀏覽器
客戶端將在通過TCP3次握手以後發送一次HTTP升級鏈接請求, 請求中不只包含HTTP交互所須要的頭部信息, 同時也會包含Websocket
交互所獨有的加密信息.bash
當服務端在接受到客戶端的協議升級請求的時候, 各種Web服務實現的實際狀況, 對其中的請求版本、加密信息、協議升級詳情進行判斷. 錯誤(無效)的信息將會被拒絕.服務器
在兩端確認完成交互以後, 雙方交互的協議將會從拋棄原有的HTTP協議轉而使用Websocket
特有協議交互方式. 協議規範能夠參考RFC文檔.websocket
在須要消息推送、鏈接保持、交互效率等要求下, 兩種協議的轉變將會帶來交互方式的不一樣.
首先, Websocket
協議使用頭部壓縮技術將頭部壓縮成2-10字節大小而且包含數據載荷長度, 這顯著減小了網絡交互的開銷而且確保信息數據完整性.
若是假設在一個穩定(可能)的網絡環境下將盡量的減小鏈接創建開銷、身份驗證等帶來的網絡開銷, 同時還能擁有比HTTP
協議更方便的數據包解析方式.
其次, 因爲基於Websocket
的協議的在請求->迴應
上是雙向的, 因此不會出現多個請求的阻塞鏈接的狀況. 這也極大程度上減小了正常請求延遲的問題.
最後, Websocket
還能給予開發者更多的鏈接管控能力: 鏈接超時、心跳判斷等. 在合理的鏈接管理規劃下, 這可提供使用者更優質的開發方案.
cf框架中的httpd
庫內置了Websocket
路由, 提供了上述Websocket
鏈接管理能力.
Websocket
路由須要開發者提供一個lua版的class
對象來抽象路由處理的過程, 這樣的抽象能簡化代碼編寫難度.
class
意譯爲'類'. 是對'對象'的一種抽象描述, 多用於各類面相對象編程語言中. lua沒有原生的class
類型, 可是提供了基本構建的元方法.
cf爲了方便描述內置對象與內置庫封裝, 使用lua table的相關元方法創建了最基本的class模型. 幾乎大部份內置庫都依賴cf的class庫.
同時爲了簡化class
的學習成本, 去除了class本來擁有的'多重繼承'概念. 將其僅做爲類
定義, 用於完成從class
->object
的初始化工做.
更多關於class
的詳情, 請參考Wiki中關於class
庫的文檔.
如今咱們開始學習Websocket
與之相關的API
初始化Websocket對象, Websocket客戶端鏈接創建完成以前被調用.
此方法在on_open方法以前被調用, 通常用於告訴httpd
應該如何怎麼進行數據包交互.
function websocket:ctor (opt) self.ws = opt.ws -- websocket對象 self.send_masked = false -- 掩碼(默認爲false, 不建議修改或者使用) self.max_payload_len = 65535 -- 最大有效載荷長度(默認爲65535, 不建議修改或者使用) end
當有鏈接初始化完成以後此方法會被調用. 此方法雖然與Websocket:ctor
相似, 但通常在僅用於內部服務初始化的時候使用.
function websocket:on_open() local cf = require "cf" self.timer = cf.at(0.01, function ( ... ) -- 啓動一個循環定時器 self.count = self.count + 1 self.ws:send(tostring(self.count)) end) end
此方法將在用戶主動發送text/binary數據的時候被回調.
參數data是一個字符串類型的playload; type是一個boolean類型變量, true爲binary類型, 不然爲text類型.
function websocket:on_message(data, typ) print('on_message', self.ws, data, typ) self.ws:send('welcome') -- self.ws:close(data) end
此方法在發生協議錯誤與未知錯誤的時候會被回調, 參數error是字符串類型的錯誤信息.
一般狀況下咱們不會用到這個方法.
function websocket:on_error(error) print('on_error:', error) end
此方法在鏈接關閉時回調. data爲關閉鏈接時發送過來到數據, 因此data可能爲nil
.
不管什麼狀況, 在鏈接被關閉的時候都將會調用此方法, 而此方法一般的做用是清理數據.
function websocket:on_close(data) if self.timer then -- 清理定時器 print("清理定時器") self.timer:stop() self.timer = nil end end
更多關於Websocket
的API請參考Wiki的文檔.
首先! 讓咱們在script
目錄下新建2個文件: main.lua
與ws.lua
, 而後分別填入下列內容:
-- app/script/ws.lua local class = require "class" local ws = class("websocket") function ws:ctor(opt) self.ws = opt.ws self.send_masked = false self.max_payload_len = 65535 end function ws:on_open() end function ws:on_message(data, typ) end function ws:on_error(error) end function ws:on_close(data) end return ws
-- main.lua local httpd = require "httpd" local app = httpd:new("httpd") app:ws('/ws', require "ws") app:listen("", 8080) app:run()
咱們使用httpd
庫啓動了一個Web Server, 同時將ws.lua
內的class
對象註冊爲Websocket
處理對象.
同時, 咱們在Websocket:ctor
方法內部, 爲Websocket路由的鏈接初始化了一些鏈接信息. 以上爲最精簡的Websocket路由處理.
首先, 咱們在ws:on_open
方法內部添加一段定時器代碼, 這個定時器用於在鏈接創建完成以後持續向開發者推送遞增消息.
function ws:on_open() local cf = require "cf" local count = 1 self.timer = cf.at(3, function(...) self.ws:send(tostring(count)) count = count + 1 end) print(self.ws, "客戶端鏈接成功.") end
而後, 咱們爲ws:on_close
方法添加一段定時器銷燬代碼用於防止內存泄露.
function ws:on_close(data) if self.timer then self.timer:stop() self.timer = nil end print(self.ws, "客戶端關閉了鏈接.") end
最後, 爲每次客戶端發送過來的消息執行一次echo迴應.
function ws:on_message(data, type) self.ws:send(data, type) print(self.ws, "接受到客戶端發送的消息.", data) end
運行cfadmin
,
讓咱們使用chrome瀏覽器點擊這裏, 使用提取碼cgwr
下載Websocket
客戶端插件而且安裝.
而後打開剛剛下載的websocket client插件並在其Websocket Address
處輸入咱們的鏈接地址進行鏈接而且查看服務端的推送消息.
開發者能夠在運行cfadmin
的終端查看鏈接創建的消息打印.
[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin [2019/06/18 21:48:36] [INFO] httpd正在監聽: 0.0.0.0:8080 [2019/06/18 21:48:36] [INFO] httpd正在運行Web Server服務... [2019/06/18 21:48:39] - ::1 - ::1 - /ws - GET - 101 - req_time: 0.000080/Sec websocket-server: 0x7f9495e01200 客戶端鏈接成功. websocket-server: 0x7f9495e01200 接受到客戶端發送的消息. hello world websocket-server: 0x7f9495e01200 客戶端關閉了鏈接.
-- main.lua local httpd = require "httpd" local app = httpd:new("httpd") app:ws('/ws', require "ws") app:listen("", 8080) app:run()
-- app/script/ws.lua local class = require "class" local ws = class("websocket") function ws:ctor(opt) self.ws = opt.ws self.send_masked = false self.max_payload_len = 65535 end function ws:on_open() local cf = require "cf" local count = 1 self.timer = cf.at(3, function(...) self.ws:send(tostring(count)) count = count + 1 end) print(self.ws, "客戶端鏈接成功.") end function ws:on_message(data, type) self.ws:send(data, type) print(self.ws, "接受到客戶端發送的消息.", data) end function ws:on_error(error) end function ws:on_close(data) if self.timer then self.timer:stop() self.timer = nil end print(self.ws, "客戶端關閉了鏈接.") end return ws
下一章咱們將學習cf框架內置的異步庫