1. 程式人生 > >Websocket 學習

Websocket 學習

ket ade sha1 字典 pytho code 包含 浪費 特定

一、含義

  WebSocket 是一種在單個TCP連接上進行全雙工通訊的協議。

  WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸

二、Websocket 產生背景

  很多網站為了實現推送技術,所用的技術都是輪詢

  輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然後由服務器返回最新的數據給客戶端的瀏覽器。

  傳統的模式的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。

  比較新的技術去做輪詢的效果是Comet

  Comet 是一種用於web的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求,目前有兩種實現方式,長輪詢iframe流

  長輪詢 是在打開一條連接以後保持,等待服務器推送來數據再關閉的方式。

  iframe流 方式是在頁面中插入一個隱藏的iframe,利用其src屬性在服務器和客戶端之間創建一條長鏈接,服務器向iframe傳輸數據(通常是HTML,內有負責插入信息的javascript),來實時更新頁面。

  Comet技術雖然可以雙向通信,但依然需要反復發出請求。

  Comet中,普遍采用的長鏈接,也會消耗服務器資源。

  Websocket使用和 HTTP 相同的 TCP 端口,可以繞過大多數防火墻的限制。

  默認情況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。

  WebSocket 是獨立的、創建在 TCP 上的協議。

  Websocket 通過 HTTP/1.1 協議的101狀態碼進行握手。

  為了創建Websocket連接,需要通過瀏覽器發出請求,之後服務器進行回應,這個過程通常稱為“握手”(handshaking)。

三、Python編寫Socket服務端

1.客戶端
客戶端:瀏覽器(必須要有socket包或者類庫,一般自帶,個別瀏覽器沒有,所以websocket有局限性)
  <script type="text/javascript">
    # 創建連接
    # 發送消息
    # 接收驗證消息 下面的一句話做了上面的三件事
    var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); 
			
    # 與服務器端連接成功後,自動執行 
    socket.onopen = function () {

    };
			
    # 服務器端向客戶端發送數據時,自動執行
    socket.onmessage = function (event) {

    };
  </script>
2.服務端
import socket
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((‘127.0.0.1‘, 8002))
sock.listen(5)
# 獲取客戶端socket對象
conn, address = sock.accept()
# 獲取客戶端的【握手】信息
data = conn.recv(1024)
...
...
...
conn.send(‘響應【握手】信息‘)

  請求握手時,瀏覽器發來的請求信息

GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

請求和響應的【握手】信息需要遵循規則:

  • 從請求【握手】信息中提取 Sec-WebSocket-Key
  • 利用magic_string 和 Sec-WebSocket-Key 進行hmac1加密,再進行base64加密
  • 將加密結果響應給客戶端

註:magic string為:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

  其中 Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg== 是瀏覽器給服務端的一個隨機字符串(mnwFxiOlctXFN/DeMt1Amg==),服務端需要給這個隨機字符串進行加密,然後在給瀏覽器send數據的時候帶上這個加密後的隨機字符串,如果瀏覽器可以解密,那麽就是說服務端支持websocket,進而進行建立連接通信。

  提取Sec-WebSocket-Key值並加密:

import socket
import base64
import hashlib
 
def get_headers(data):
    """
    將請求頭格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding=‘utf-8‘)
 
    for i in data.split(‘\r\n‘):
        print(i)
    header, body = data.split(‘\r\n\r\n‘, 1)
    header_list = header.split(‘\r\n‘)
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(‘ ‘)) == 3:
                header_dict[‘method‘], header_dict[‘url‘], header_dict[‘protocol‘] = header_list[i].split(‘ ‘)
        else:
            k, v = header_list[i].split(‘:‘, 1)
            header_dict[k] = v.strip()
    return header_dict
 
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((‘127.0.0.1‘, 8002))
sock.listen(5)
 
conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data) # 提取請求頭信息
# 對請求頭中的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://%s%s\r\n\r\n"
magic_string = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11‘
value = headers[‘Sec-WebSocket-Key‘] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode(‘utf-8‘)).digest())
response_str = response_tpl % (ac.decode(‘utf-8‘), headers[‘Host‘], headers[‘url‘])
# 響應【握手】信息
conn.send(bytes(response_str, encoding=‘utf-8‘))
...
...
...

  此外還有服務端需要實現封包解包的功能,而客戶端瀏覽器的JS已經幫我們實現了封包解包了。

  封包解包詳見:武沛齊

  

Websocket 學習