websocket的原理和使用


什么是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

接口和屬性

  1. request.is_websocket()

    如果是個websocket請求返回True,如果是個普通的http請求返回False,可以用這個方法區分它們。

  2. request.websocket

    在一個websocket請求建立之后,這個請求將會有一個websocket屬性,用來給客戶端提供一個簡單的api通訊,如果request.is_websocket()是False,這個屬性將是None。

  3. WebSocket.wait()

    返回一個客戶端發送的信息,在客戶端關閉連接之前他不會返回任何值,這種情況下,方法將返回None

  4. WebSocket.read()

    如果沒有從客戶端接收到新的消息,read方法會返回一個新的消息,如果沒有,就不返回。這是一個替代wait的非阻塞方法

  5. WebSocket.count_messages()

    返回消息隊列數量

  6. WebSocket.has_messages()

    如果有新消息返回True,否則返回False

  7. WebSocket.send(message)

    向客戶端發送消息

  8. 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比較新

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM