python 實現 websocket

1、websocket概要:javascript

  websocket是基於TCP傳輸層協議實現的一種標準協議(關於網絡協議,能夠看看文末的圖片),用於在客戶端和服務端雙向傳輸數據html

  傳統的客戶端想要知道服務端處理進度有兩個途徑:前端

  1)經過ajax不斷輪詢,因爲http的無狀態性,每次輪詢服務器都須要去解析http協議,對服務器壓力也很大java

  2)採用long poll的方式,服務端不給客戶端反饋,客戶端就一直等待,服務就一直被掛起,此階段一直是阻塞狀態python

  而當服務器完成升級(http-->websocket)後,上面兩個問題就獲得解決了:web

    1)被動性,升級後,服務端能夠主動推送消息給客戶端,解決了輪詢形成的同步延遲問題ajax

    2)升級後,websocket只須要一次http握手,服務端就能一直與客戶端保持通訊,直到關閉鏈接,這樣就解決了服務器須要反覆解析http協議,減小了資源的開銷算法

2、websocket通訊過程:瀏覽器

  websocket目前基本主流瀏覽器都已經支持,IE10如下不支持。安全

 

  一、創建鏈接

    在客戶端,new WebSocket實例化一個新的WebSocket客戶端對象,鏈接相似 ws://yourdomain:port/path 的服務端 WebSocket URL, WebSocket 客戶端對象會自動解析並識別爲 WebSocket 請求,從而鏈接服務端接口,執行雙方握手過程,客戶端發送數據格式相似:

    1)客戶端請求報文:

GET / HTTP/1.1

Upgrade:websocket  #line1

Connection:Upgrade  #line2 :與http請求報文比,多了line1和line2這兩行,它告訴服務器這次發起的是websocket協議,而不是http協議了,記得要升級哦

Host:example.com

Origin:http://example.com

Sec-WebSocket-Key:sN9cRrP/n9NdMgdcy2VJFQ==  # line3:這個是瀏覽器隨機生成的一個base64加密值,提供基本的防禦,告訴服務器,我有提供的密碼的,我會作驗證的,防止惡意或無心的鏈接
  
Sec-WebSocket-Version:13  #line4 :告訴服務器使用的websocket版本,若是服務器不支持該版本,會返回一個Sec-WebSocket-Versionheader,裏面包含服務器支持的版本號

    客戶端建立websocket鏈接

      var ws = new websocket("ws:127.0.0.1:8000")

    完整客戶端代碼以下:

<script type="text/javascript">
    var ws;
    var box = document.getElementById("box");
    
    function startWS(){
        ws = new websocket("ws:127.0.0.1:8000");
        ws.onopen = function(msg){
            console.log("websocket opened!");
        }
        ws.onomessage = function(message){
            console.log("receive message:"+message.data);
            box.insertAdjacentHTML("beforeend", "<p>"+message.data+"</p>");
        }
        ws.onerror = function(err){
            console.log("error:"+err.name+err.number);
        }
        ws.onclose = function(){
            console.log("websocket closed!")
        }
    }

    function sendMsg(){
        console.log("sending a message...");
        var text = document.getElementById("text");
        ws.send(text.value);
    }

    window.onbeforeunload = function(){
        ws.onclose = function(){}
        ws.close()
    }
</script>

    2)服務端響應報文:

HTTP/1.1 101 Switching Protocols  # 101表示服務端已經理解了客戶端的請求,並將經過Upgrade消息頭通知客戶端採用不一樣的協議來完成這個請求

Upgrade:websocket

Connection:Upgrade  # 這裏兩行是告訴瀏覽器,我已經成功切換協議了,協議是websocket

Sec-WebSocket-Accept:HSmrc0sM1YUkAGmm50PpG2HaGwK=  #通過服務器確認並加密後的Sec-WebSocket-Key

Sec-WebSocket-Protocol:chat  # 表示最終使用的協議,至此http就已經完成所有的工做,接下來就是徹底按照websocket協議進行了。

     上文的Sec-WebSocket-Accept加密算法:

        a)將Sec-WebSocket-Key和258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接

        b)經過SHA1計算出摘要,並轉成Base64字符串

          如token = base64.b64encode(hashlib.sha1(key+magic_str).encode("utf8").degist())

          這裏在作加密以前,key必定要記得看先後有沒有空白,有的話要記得去空白,否則加密的結果會一直報錯不匹配,這個坑被坑了好久

        注意:此處的Sec-WebSocket-Key/Sec-WebSocket-Accept的換算,只能帶來基本保障,但鏈接是否安全,數據是否安全,客戶端 服務端是不是合法的ws客戶端 ws服務端,並無實際保證

      Sec-WebSocket-Protocol:表示最終使用的協議

完整的服務端代碼:

  1)建立websocket服務端

import socket
import threading

global clients
clients = {}


class Websocket_Server(threading.Thread):
    def __init__(self, port):
        self.port = port
        super(Websocket_Server, self).__init__()

    def run(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(("127.0.0.1", self.port))
        sock.listen(5)
        
        while(True):
       # 等待客戶端鏈接 conn, addr
= sock.accept() print("客戶端{}鏈接成功:".format(addr)) conn.send(("welcome...").encode("utf8")) while(True): try: info = conn.recv(1024) connId = "ID:"+str(addr[1]) clients[connId] = conn print("{0}:{1}".format(connId, info.decode("utf8"))) except Exception as e: print(e) msg = input() conn.send(msg.encode("utf8")) if info == b"bye": print("客戶端退出") conn.close() break

上面建立了websocket服務端,經過socket.socket()建立了TCP服務對象

    接收兩個參數:family和type

      family:有三種:

        AF_INET :即IPV4(默認)

        AF_INET6:即IPV6

        AF_UNIX:只可以用於單一的Unix系統進程間通訊

      type:套接字類型:

        流套接字(SOCK_STREAM)(默認):只讀取TCP協議的數據,用於提供面向鏈接,可靠的數據傳輸服務。該服務能夠保證數據能夠實現無差錯無重複發送,並按序接收。之因此可以實現可靠的數據傳輸,緣由在於使用了傳輸控制協議(TCP)

        數據報套接字(SOCK_DGRAM):只讀取UDP協議的數據。提供了一種無鏈接服務,該服務並不能保證數據的可靠性。有可能在數據傳輸過程當中出現數據丟失,錯亂重複等。因爲數據包套接字不能保證數據的可靠性,對於有可能出現數據丟失的狀況,在程序中要作相應的處理。

        原始套接字(SOCK_RAW):原始套接字和標準套接字(上面兩種)的區別是:原始套接字能夠讀取內核沒有處理的IP數據包。而流套接字只能讀取TCP協議的數據;數據包套接字只能讀取UDP協議的數據

        可靠UDP形式:(SOCK_RDM),會對數據進行校驗,通常不會使用

        可靠的連續數據包:(SOCK_SEQPACKET)通常也不會使用

  2)建立websocket客戶端

import socket
import threading

class Websocket_Client(threading.Thread):
    def __init__(self):
        self.host ="localhost"
        self.port = 8000
        self.address = (host, port)
        self.buffer = 1024
    
    def run():
        #建立TCP客戶端程序
        tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 鏈接服務端
        tcp_client.connect(self.address)
        while True:
            info = tcp_client.recv(self.buffer)
            print("{}".format(str(info, encoding="utf8")))
            
            msg = input()
            tcp_client.send(msg.encode("utf8"))
            if info.lower().decode("utf8")=="bye":
                tcp_client.close()
                break

完成上述代碼,分別打開兩個終端,運行WebSocket_Server和WebSocket_Client代碼,控制檯交互結果以下,能夠看到已經成功實現了客戶端和服務端的通訊:

 

  3)如何監聽瀏覽發送過來的socket鏈接請求?修改上面的WebSocket_Client以下:

class WebSocket_Client():
  def __init__(self, conn, connId):
    self.connection = conn
    self.connId= connId
    super(WebSocket_Client, self).__init__()
  
  def run():
    print("new socket joined")
    # 前端傳遞過來的請求鏈接數據
    data = self.connection.recv(1024)
    try:
      headers = self.parse_header(data)
      token = self.generate_token(headers["Sec-WebSocket-Key"]).decode("utf8")
      response ="HTTP/1.1 101 Switching Protocols\r\n" \
            "Connection:Upgrade\r\n" \
            "Upgrade:websocket\r\n" \
            "Sec-WebSocket-Accept:{0}\r\n" \
            "WebSocket-Protocol:chat\r\n\r\n".format(token)
      # 通知前端已經成功創建鏈接
      self.connection.send(response.encode("utf8"))
    except socket.error as e:
      print("unexpect error:", e)
      clients.pop(self.connId)
    while True:
      
# 啓動 Socket 並監聽鏈接,使用socket中的同名方法,建立TCP服務對象 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # socket.AF_INET是指使用哪一種IP地址,socket.AF_INET即便用IPv4,若是是socket.AF_INET6的話,則使用IPv6;socket.SOCKET_STREAM是指使用的套接字類型,在這裏咱們使用流式套接字。 try: sock.bind(("127.0.0.1",8000)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.listen(5) except Exception as e: logging.error(e) return else: logging.info(Server running...)   # 等待訪問 while True: conn, addr = sock.accept() #此時會進入waiting狀態 data = str(conn.recv(1024)) logging.debug(data) header_dict = {} header, _ = data.split(r"\r\n\r\n", 1) for line in header.split(r"\r\n")[1:]: key, val = line.split(":",1) header_dict[key] = val if "Sec-WebSocket-Key" not in header_dict: logging.error(this socket is not websocket, connection closed...) conn.close() return magic_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" sec_key = header_dic["Sec-WebSocket-Key"]+magic_key key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding="utf-8")).digest()) key_str = str(key)[2:30] logging.debug(key_str) response = "HTTP/1.1 101 Switching Protocols\r\n"\ "Connection:Upgrade\r\n"\ "Upgrade:websocket\r\n"\ "Sec-WebSocket-Accept:{0}\r\n"\ "WebSocket-Protocol:chat\r\n\r\n".format(key_str) conn.send(bytes(response),encoding="utf-8") logging.debug("send the handshake data") WebsocketThread(conn).start()

    3)進行通訊:

      Sever端接收到client發來的報文進行解析

      

 

下面是軟件通訊的七層結構:

  下三層結構偏向於數據通訊,上三層更偏向於數據處理,中間的傳輸層是上三層與下三層之間的鏈接橋樑,每一層作不一樣的工做,上層協議依賴於下層協議,上層協議須要調用下層協議的接口,下層協議使用上層協議的參數,分工合做。

  socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。屬於傳輸控制層協議,websocket是一個典型的應用層協議。

 

 

參考資料:https://www.cnblogs.com/JetpropelledSnake/p/9033064.html

        https://www.cnblogs.com/lichmama/p/3931212.html

相關文章
相關標籤/搜索