什么是websocket
WebSocket是一種在單個TCP連接上進行全雙工通訊的協議。WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
Websocket是一個持久化的協議,相對於HTTP這種非持久的協議來說的。
舉個例子:
HTTP的生命周期通過 Request 來界定,也就是發送一次 Request,收到一次 Response ,那么在 HTTP1.0 中,這次HTTP請求就結束了
在HTTP1.1中進行了改進,使得有一個keep-alive,也就是說,在一個HTTP連接中,可以發送多個Request,接收多個Response。但是請記住 Request = Response , 在HTTP中永遠是這樣,也就是說一個request只能有一個response。而且這個response也是被動的,不能主動發起。
而對於websocket來說,在HTTP的握手基礎上建立起鏈接,服務器端可以主動的向客戶端發送數據。
為什么要使用websocket
現在,很多網站為了實現頁面上的數據更新,都是通過ajax技術輪詢去服務器拉取數據。輪詢是在特定的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
而比較新的技術去做輪詢的效果是Comet)。Comet技術又可以分為長輪詢和流技術。長輪詢改進了上述的輪詢技術,減小了無用的請求。它會為某些數據設定過期時間,當數據過期后才會向服務端發送請求;這種機制適合數據的改動不是特別頻繁的情況。流技術通常是指客戶端使用一個隱藏的窗口與服務端建立一個HTTP長連接,服務端會不斷更新連接狀態以保持HTTP長連接存活;這樣的話,服務端就可以通過這條長連接主動將數據發送給客戶端;流技術在大並發環境下,可能會考驗到服務端的性能。這兩種技術都是基於請求-應答模式,都不算是真正意義上的實時技術;它們的每一次請求、應答,都浪費了一定流量在相同的頭部信息上,並且開發復雜度也較大。
在這種情況下,HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。
Websocket使用ws或wss的統一資源標志符,類似於HTTPS,其中wss表示在TLS之上的Websocket。如:
ws://example.com/wsapi
wss://secure.example.com/
Websocket使用和 HTTP 相同的 TCP 端口,可以繞過大多數防火牆的限制。默認情況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。
WebSocket 目前由 W3C 進行標准化。WebSocket 已經受到 Firefox 4、Chrome 、Opera 10.70 以及 Safari 5 等瀏覽器的支持。
一個典型的websocket的握手請求
- 客戶端請求:
GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: http://example.com Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
- 服務端回應:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Location: ws://example.com/ 字段說明
字段說明
- Connection必須設置Upgrade,表示客戶端希望連接升級。
- Upgrade字段必須設置Websocket,表示希望升級到Websocket協議。
- Sec-WebSocket-Key是隨機的字符串,服務器端會用這些數據來構造出一個SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一個特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后計算SHA-1摘要,之后進行BASE-64編碼,將結果做為“Sec-WebSocket-Accept”頭的值,返回給客戶端。如此操作,可以盡量避免普通HTTP請求被誤認為Websocket協議。
- Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均應當棄用。
- Origin字段是可選的,通常用來表示在瀏覽器中發起此Websocket連接所在的頁面,類似於Referer。但是,與Referer不同的是,Origin只包含了協議和主機名稱。
- 其他一些定義在HTTP協議中的字段,如Cookie等,也可以在Websocket中使用。
如何使用websocket
關於如何在Python后端使用websocket的技術有多種方式,可以參考這篇文章《python使用websocket的幾種方式》https://jingniao.github.io/2016/04/10/python-websocket/
我們的項目使用的是Django的框架,我這次的實踐主要圍繞Django 的websocket插件 dwebsocket 展開記錄。
安裝dwesocket
話不多說, pip大法。
pip install dwesocket
使用
如果希望一個view視圖既可以處理HTTP請求,也可以處理websocket請求,只需要將這個view函數用裝飾器 @accept_websocket 包裹即可。也可以使用 @require_websocket 裝飾器,這樣的話這個視圖只允許接受websocket請求,會拒絕正常的HTTP請求。
如果你希望在應用程序中為所有的url提供websocket請求支持,則可以使用中間件。只需要將dwebsocket.middleware.websocketmiddleware 添加到設置中的的 MIDDLEWARE_CLASSES 中。 (如果沒有需要自己定義,不定義會報錯,這個有點尷尬,對於這一點的優化,我已經對主庫提了一個Pull Request,希望作者還在維護....)
MIDDLEWARE_CLASSES = ['dwebsocket.middleware.WebSocketMiddleware']
如果允許每個單獨的視圖接受websocket請求,在settings中設置 WEBSOCKET_ACCEPT_ALL 為 True.
WEBSOCKET_ACCEPT_ALL = True
接口和屬性
-
request.is_websocket()
如果是個websocket請求返回True,如果是個普通的http請求返回False,可以用這個方法區分它們。
-
request.websocket
在一個websocket請求建立之后,這個請求將會有一個websocket屬性,用來給客戶端提供一個簡單的api通訊,如果request.is_websocket()是False,這個屬性將是None。
-
WebSocket.wait()
返回一個客戶端發送的信息,在客戶端關閉連接之前他不會返回任何值,這種情況下,方法將返回None
-
WebSocket.read()
如果沒有從客戶端接收到新的消息,read方法會返回一個新的消息,如果沒有,就不返回。這是一個替代wait的非阻塞方法
-
WebSocket.count_messages()
返回消息隊列數量
-
WebSocket.has_messages()
如果有新消息返回True,否則返回False
-
WebSocket.send(message)
向客戶端發送消息
-
WebSocket.iter()
websocket迭代器
實踐例程
從客戶端接收一條消息,將該消息發送回客戶端並關閉連接(通過視圖返回):
from dwebsocket import require_websocket @require_websocket def echo(request): message = request.websocket.wait() request.websocket.send(message)
我們也可以讓服務端不自動關閉連接,下面的例程中,服務器端會將客戶端發來的消息轉為小寫發送回去,並增加了普通的HTTP請求的響應,也做同樣操作返回:
from django.http import HttpResponse from dwebsocket import accept_websocket def modify_message(message): return message.lower() @accept_websocket def lower_case(request): if not request.is_websocket(): # 普通HTTP請求 message = request.GET['message'] message = modify_message(message) return HttpResponse(message) else: # websocket請求 for message in request.websocket: message = modify_message(message) request.websocket.send(message)
改變dwebsocket后端
目前dwebsocket支持兩種后端,分別是 default 的和 uwsgi。
默認 default 支持 Django自身的開發服務器, eventlent, gevent, 和gunicore。
如果你想使用uwsgi后端,在settings.py文件中添加 WEBSOCKET_FACTORY_CLASS:
WEBSOCKET_FACTORY_CLASS = 'dwebsocket.backends.uwsgi.factory.uWsgiWebSocketFactory'
運行 uwsgi :
uwsgi --http :8080 --http-websockets --processes 1 \ --wsgi-file wsgi.py--async 30 --ugreen --http-timeout 300
websocket的優缺點
優點
-
服務器與客戶端之間交換的標頭信息很小,大概只有2至10字節(和數據包長度有關);
-
客戶端與服務器都可以主動傳送數據給對方;
-
不用頻率創建 TCP 請求及銷毀請求,減少網絡帶寬資源的占用,同時也節省服務器資源;
-
更強的實時性。由於協議是全雙工的,所以服務器可以隨時主動給客戶端下發數據。相對於HTTP請求需要等待客戶端發起請求服務端才能響應,延遲明顯更少;
-
更好的二進制支持。Websocket定義了二進制幀,相對HTTP,可以更輕松地處理二進制內容;
-
可以支持擴展。Websocket定義了擴展,用戶可以擴展協議、實現部分自定義的子協議。如部分瀏覽器支持壓縮等。
缺點
下面這些出自知乎,優點雞肋的缺點,就是對開發者技術要求高點應該不算缺點,新的技術總有個普及的過程。
對前端開發者:
- 往往要具備數據驅動使用javascript的能力,且需要維持住ws連接(否則消息無法推送)
對於后端開發者:
-
一是長連接需要后端處理業務的代碼更穩定(不要隨便把進程和框架都crash掉)
-
二是推送消息相對復雜一些
-
三是成熟的http生態下有大量的組件可以復用,websocket比較新
