這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
websocket出來好久了,一直沒有動手去玩玩,今天抽了點時間寫了一個golang的例子,下面簡單記錄一下。
協議
websocket是個二進位協議,需要先通過Http協議進行握手,從而協商完成從Http協議向websocket協議的轉換。一旦握手結束,當前的TCP串連後續將採用二進位websocket協議進行雙向雙工互動,自此與Http協議無關。
可以通過這篇知乎瞭解一下websocket協議的基本原理:《WebSocket 是什麼原理?為什麼可以實現持久串連?》。
粘包
我們開發過TCP服務的都知道,需要通過協議decode從TCP位元組流中解析出一個一個請求,那麼websocket又怎麼樣呢?
websocket以message為單位進行通訊,本身就是一個在TCP層上的一個分包協議,其實並不需要我們再進行粘包處理。但是因為單個message可能很大很大(比如一個視頻檔案),那麼websocket顯然不適合把一個視頻作為一個message傳輸(中途斷了前功盡棄),所以websocket協議其實是支援1個message分多個frame幀傳輸的。
我們的瀏覽器提供的編程API都是message粒度的,把frame拆幀的細節對開發人員隱蔽了,而服務端websocket架構一般也做了同樣的隱藏,會自動幫我們收集所有的frame後拼成messasge再回調,所以結論就是:
websocket以message為單位通訊,不需要開發人員自己處理粘包問題。
golang實現
golang官方標準庫裡有一個websocket的包,但是它提供的就是frame粒度的API,壓根不能用。
不過官方其實已經認可了一個準標準庫實現,它實現了message粒度的API,讓開發人員不需要關心websocket協議細節,開發起來非常方便,其文檔地址:https://godoc.org/github.com/gorilla/websocket。
開發websocket服務時,首先要基於http庫對外暴露介面,然後由websocket庫接管TCP串連進行協議升級,然後進行websocket協議的資料交換,所以開發時總是要用到http庫和websocket庫。
上述websocket文檔中對開發websocket服務有明確的注意事項要求,主要是指:
- 讀和寫API不是並發安全的,需要啟動單個goroutine串列處理。
- 關閉API是安全執行緒的,一旦調用則阻塞的讀和寫API會出錯返回,從而終止處理。
在我的實現中,我對websocket進行了封裝,簡化應用程式層開發的複雜度,主要思路是:
- 請求和應答都放入管道中排隊。
- 讀協程阻塞讀websocket,將message放入請求隊列。
- 寫協程阻塞讀應答channel,將message寫給websocket。
如何處理websocket錯誤和主動關閉websocket呢?
- 讀/寫協程調用websocket若返回錯誤,那麼直接調用websocket的Close關閉串連,協程退出。(此時使用者可能仍舊持有連線物件,繼續向下閱讀!)
- websocket串連關閉後,使用者通常正阻塞在讀/寫channel上而不知情,所以每個串連配套一個closeChan專門用於喚醒使用者代碼,關閉websocket串連同時關閉closeChan,這會令<-closeChan總是立即返回。
- 因為上一條設計,所以使用者讀/寫channel時總是select同時監聽channel和closeChan,以便即時感知到websocket串連的關閉。
- 使用者可以主動關閉串連,websocket串連重複Close沒有影響,而closeChan重複關閉會報錯,所以通過一個上鎖的狀態位判重處理。
描述比較繁瑣,實際並不複雜,看看My Code吧:https://github.com/owenliang/go-websocket。
體驗項目
首先運行server.go,然後開啟client.html頁面,即可體驗所有流程:
nginx反向 Proxy
網上有很多nginx如何反向 Proxywebsocket服務的配置,通過Proxy即可實現,不再示範。