1. 程式人生 > >WebSocket原理及技術簡介

WebSocket原理及技術簡介

base64編碼 pri agen 發展 工作 format 實現 orm ons

摘要:

WebSocket用於在Web瀏覽器和服務器之間進行任意的雙向數據傳輸的一種技術。WebSocket協議基於TCP協議實現,包含初始的握手過程,以及後續的多次數據幀雙向傳輸過程。其目的是在WebSocket應用和WebSocket服務器進行頻繁雙向通信時,可以使服務器避免打開多個HTTP連接進行工作來節約資源,提高了工作效率和資源利用率。

一、WebSocket誕生需求

互聯網發展的早期,網站上只是一些靜態展示頁面。用戶請求(Request)網站頁面,網站回復(Response)頁面內容給用戶瀏覽器。因為需求簡單,所以也沒有很復雜的協議過程。這種形式的Request/Response交互流程如下圖所示:

技術分享圖片

隨著互聯網技術的發展,帶寬逐步提高,用戶數也越來越龐大。對互聯網的呈現內容提出了要求,隨之出現了動態頁面技術,對同一個頁面,頁面的某些部分對不同的訪問用戶,呈現的內容不同。相關的實現技術有CGI、ASP、PHP、JSP等。由於訪問量的增加,WEB服務器同時處理的用戶數也達到了萬(10K)以上級別,這就是C10K問題:"The C10K problem"。為了緩解服務器壓力,每次Request/Response後連接(TCP連接)繼續保持,以及對同一個TCP連接,多次復用Request/Response的方法(也稱為Pipeline)也提了出來。這就是HTTP/1.1協議中長連接的主要內容。

伴隨移動互聯網的發展,大量移動終端和其上的APP應用接入網絡,HTML5技術也提了出來,以便支持WEB上的音視頻播放、實時遊戲、實時聊天等。催生了這樣一個需求

,當服務器有更新時,需要立即將數據發送給客戶端,這就是基於服務器端的推送技術。

WEBSOCKET之前的解決方法大概這麽幾種: 1)輪詢:客戶端設置一個時間間隔,時間到以後,向服務器發送request詢問有無新數據,服務器立即返回response,如果有更新則攜帶更新的數據。2)長連接(long poll): 和輪詢相似,但是為阻塞模式的輪詢,客戶端請求新的數據request, 服務器會阻塞請求,直到有新數據後才返回response給客戶端;然後客戶端再重復此過程。這兩種方式的特點,不斷的建立HTTP連接,然後發送請求request,之後服務器等待處理。服務端體現的是一種被動性,同時這種處理方式,非常耗費網絡帶寬和服務器資源。

服務器向客戶端推送更新時,因為被動性,對低延遲的應用體驗不好;因為request/response的交互方式,對網絡帶寬和服務器帶來了額外的負擔(例如多次請求的HTTP頭部, TCP連接復用會導致的Head-of-Line Blocking線頭阻塞[2]等)。如果在單一的TCP連接中,使用雙向通信(全雙工通信)就能很好的解決此問題。這就是WebSocket技術的緣由。

二、WebSocket技術及協議

(一)WebSocket技術的優點有:

1)通過第一次HTTP Request建立了連接之後,後續的數據交換都不用再重新發送HTTP Request,節省了帶寬資源;

2) WebSocket的連接是雙向通信的連接,在同一個TCP連接上,既可以發送,也可以接收;

3)具有多路復用的功能(multiplexing),也即幾個不同的URI可以復用同一個WebSocket連接。這些特點非常類似TCP連接,但是因為它借用了HTTP協議的一些概念,所以被稱為了WebSocket。

(二)WebSocket協議

WebSocket看成是一種類似TCP/IP的socket技術;此socket在Web應用中實現,並獲得了和TCP/IP通信一樣靈活方便的全雙向通信功能。

WebSocket協議由RFC 6455定義。協議分為兩個部分: 握手階段和數據通信階段。

WebSocket為應用層協議,其定義在TCP/IP協議棧之上。WebSocket連接服務器的URI以"ws"或者"wss"開頭。ws開頭的默認TCP端口為80,wss開頭的默認端口為443。

1、握手階段

客戶端和服務器建立TCP連接之後,客戶端發送握手請求,隨後服務器發送握手響應即完成握手階段。如下圖所示:

技術分享圖片

客戶端握手請求類似如下:

#首先看一個標準的websocket請求頭

GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:8001
Origin: http://127.0.0.1:8001
Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
Sec-WebSocket-Version: 13

可以看到使用http1.1 協議上面是標準的http的請求信息

但是熟悉http的小夥伴可以明顯看出這裏多出了一些信息。用於實現協議升級

Upgrade: websocket
Connection: Upgrade
origin:xxxx
Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
Sec-WebSocket-Version: 13

upgrade websocket用於告訴服務器此連接需要升級到websocket。

而下面的Sec-WebSocket-Key是客戶端也就是瀏覽器或者其他終端隨機生成一組16位的隨機base64編碼的字節串。

最後Sec-WebSocket-Version就是當前使用協議的版本號了。

服務器在接受到上面的請求之後,會返回一個response 頭包完成握手。

HTTP/1.1 101 Switching Protocols
Content-Length: 0
Upgrade: websocket
Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=
Server: TornadoServer/4.5.1
Connection: Upgrade
Date: Wed, 21 Jun 2017 03:29:14 GMT

由Sec-Websocket-Accept的key完成校驗。 我貼一個生成的Sec-Websocket-Accept的代碼大家感受一下

import socket, base64, hashlib

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((127.0.0.1, 9527))
sock.listen(5)
# 獲取客戶端socket對象
conn, address = sock.accept()
# 獲取客戶端的【握手】信息
data = conn.recv(1024)
print(data)
"""
GET /ws HTTP/1.1\r\n
Host: 127.0.0.1:9527\r\n
Connection: Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\r\n
Upgrade: websocket\r\n
Origin: http://localhost:63342\r\n
Sec-WebSocket-Version: 13\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: session=a6f96c20-c59e-4f33-84d9-c664a2f29dfc\r\n
Sec-WebSocket-Key: MAZZU5DPIxWmhk/UWL2+BA==\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n
"""
# 以下動作是有websockethandler完成的
# magic string為:258EAFA5-E914-47DA-95CA-C5AB0DC85B11


def get_headers(data):
    header_dict = {}
    header_str = data.decode("utf8")
    for i in header_str.split("\r\n"):
        if str(i).startswith("Sec-WebSocket-Key"):
            header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip()

    return header_dict


headers = get_headers(data)  # 提取請求頭信息

magic_string = 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
#Sec-WebSocket-Key: MAZZU5DPIxWmhk/UWL2+BA==
value = headers[Sec-WebSocket-Key] + magic_string    #字符串類型
ac = base64.b64encode(hashlib.sha1(value.encode(utf-8)).digest())

# 對請求頭中的sec-websocket-key進行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n"                "Upgrade:websocket\r\n"                "Connection: Upgrade\r\n"                "Sec-WebSocket-Accept: %s\r\n"                "WebSocket-Location: ws://127.0.0.1:9527\r\n\r\n"
print(ac.decode(utf-8))
response_str = response_tpl % (ac.decode(utf-8))
# 響應【握手】信息
conn.send(response_str.encode("utf8"))
#
while True:
    msg = conn.recv(8096)
    print(msg)

WebSocket原理及技術簡介