WebSocket原理及技術簡介
摘要:
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原理及技術簡介