天天看點

WebSocket協定和Socket.IO架構

一、WebSocket介紹:

1,WebSocket是什麼?

WebSocket是一種在單個TCP連接配接上進行全雙工通信的協定。在WebSocket API中,浏覽器和伺服器隻需要完成一次握手(不是指建立TCP連接配接的那個三次握手,是指在建立TCP連接配接後傳輸一次握手資料),兩者之間就直接可以建立持久性的連接配接,并進行雙向資料傳輸。

WebSocket是H5新增的一重通信協定,為了更好的節省伺服器資源和帶寬,并且能夠更實時地進行通訊。

2,WebSocket與TCP、HTTP之間有什麼關系?

WebSocket和HTTP都是基于TCP建立的,是以發送消息之前都要建立連接配接進行三次握手。

WebSocket依賴HTTP協定的一次握手,一次握手成功之後,就不需要HTTP了,而是使用TCP的傳輸通道,開始收發資料,這就是上面所說的雙向連接配接。

3,WebSocket使用場景?

  • 使用者下了訂單,營運管理背景需要向營運人員推送訂單通知。
  • 使用者A關注了使用者B,需要向使用者B推送消息
  • 有一些遊戲,既支援手機端,也支援web端時,需要用到。
  • 網頁版即時聊天、彈幕等

4,為什麼選擇WebSocket而不是其他?

  1. HTTP/1.x 版本不支援伺服器主動推送,隻能在用戶端發起請求後做出回應。
  2. HTTP/2.x版本支援伺服器主動推送,但HTTP/2 還未全面實施
  3. 輪詢 :每隔一段時間就會想伺服器發送請求,詢問是否有新的消息,必須不停連接配接,或者連接配接始終打開,效率低下,浪費資源。
  4. Comet (基于長連接配接)長輪詢是在打開一條連接配接以後保持,等待伺服器推送來資料再關閉的方式。依然需要反複送出請求,而且長連接配接也會消耗伺服器資源。

**綜上所述:

HTTP、輪詢、長輪詢都不太合适**

。而WebSocket呢?看下面介紹:

5,典型的Websocket握手請求如下:

用戶端請求:

GET / HTTP/1.1
Upgrade: websocket    
#Upgrade字段必須設定Websocket,表示希望更新到Websocket協定

Connection: Upgrade   
#Connection必須設定Upgrade,表示用戶端希望連接配接更新

Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: N9cRrP/n9NdMgdcy2VJFQghu== 
#Sec-WebSocket-Key是一串随機的字元串,伺服器使用SHA算法,base64編碼之後,将結果做為“Sec-WebSocket-Accept”頭的值,傳回給用戶端。如此操作,可以**盡量避免普通HTTP請求被誤認為Websocket協定**

Sec-WebSocket-Version: 13
           

伺服器回應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: BooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
           

握手過程解析:

第一步:服務端開啟socket,監聽某個ip和端口。等待連接配接…

第二步:用戶端connect連接配接服務端(ip,端口)

第三步:服務端允許連接配接…

第四部:用戶端發送握手請求,用戶端自己生成的一個特殊值發給伺服器(魔法字元串migic string),發送之前用戶端會自己先加密,儲存。

第五步:伺服器接收到特殊字元串後,經過SHA1加密,加密後把值發給用戶端

第六步:用戶端接收到加密後的值後,與之前的儲存的值校驗,如果校驗通過,說明對方也是websocket協定,ok,握手成功,可以雙方收發資料了。

Websocket總結:

  • 節省資源開銷

    。伺服器到用戶端的内容,頭部大小隻有2至10位元組(和資料包長度有關),而HTTP請求每次都要攜帶完整的頭部。
  • 更強的實時性

    。因為協定是全雙工的,是以伺服器可以随時主動給用戶端下發資料。
  • 保持連接配接狀态

    .Websocket需要先建立連接配接,這就使得其成為一種有狀态的協定,之後通信時可以省略部分狀态資訊。而HTTP請求可能需要在每個請求都攜帶狀态資訊(如身份認證等)。
  • 更好的二進制支援

    。Websocket定義了二進制幀,相對HTTP,可以更輕松地處理二進制内容。
  • 可擴充

    。Websocket定義了擴充,使用者可以擴充協定、實作部分自定義的子協定。如部分浏覽器支援壓縮等。
  • 更好的壓縮效果

    。相對于HTTP壓縮,Websocket在适當的擴充支援下,可以沿用之前内容的上下文,在傳遞類似的資料時,可以顯著地提高壓縮率。
  • 沒有同源限制

    。用戶端可以與任意伺服器通信。
  • 可以發送文本,也可以發送二進制資料

二、Socket.IO介紹:

1,Socket.IO是什麼?

Socket.IO 基于WebSocket協定,現在已成為Web即時通訊應用的架構,WebSocket隻是Socket.IO實作即時通訊的其中一種技術依賴。

2,Socket.IO優點:

Socket.IO 會自動選擇合适雙向通信協定

有Python庫的實作,可以在Python實作的Web應用中去實作IM背景服務。

https://python-socketio.readthedocs.io/en/latest/server.html

3,Socket.IO缺點:

Socket.io要求用戶端與伺服器端均須使用該架構。

三、python 伺服器端開發

1,安裝:

pip install python-socketio
           

2,伺服器部署方案:

下面主要使用協程eventlet庫部署伺服器,其他伺服器部署方案,後面會詳細介紹。

選擇協程方案的原因:

  • 使用eventlet部署的Socket.IO伺服器可以通路長輪詢和WebSocket傳輸。
  • 也可以使用協程gevent部署,隻不過gevent不支援websocket,要安裝gevent-websocket 包才行。
  • 若伺服器采用多程序或多線程方式,受限于伺服器能建立的程序或線程數,能夠支援的并發連接配接用戶端不會很高,伺服器性能有限。
import eventlet
eventlet.monkey_patch()

import socketio
import eventlet.wsgi

sio = socketio.Server(async_mode='eventlet')  # 指明在evenlet模式下
app = socketio.Middleware(sio)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

           

3,事件處理方法如下:

@sio.on('connect')
def on_connect(sid, environ):
    """
    與用戶端建立好連接配接後被執行
    :param sid: string sid是socketio為目前連接配接用戶端生成的識别id
    :param environ: dict 在連接配接握手時用戶端發送的握手資料(HTTP封包解析之後的字典)
    """
    pass

@sio.on('disconnect')
def on_disconnect(sid):
    """
    與用戶端斷開連接配接後被執行
    :param sid: string sid是斷開連接配接的用戶端id
    """
    pass

# 以字元串的形式表示一個自定義事件,事件的定義由前後端約定
@sio.on('my custom event')  
def my_custom_event(sid, data):
    """
    自定義事件消息的處理方法
    :param sid: string sid是發送此事件消息的用戶端id
    :param data: data是用戶端發送的消息資料
    """
    pass

           
提示:

SocketIO:對收發的資料以消息(message)來對待,收發的不同類别的消息資料又以**事件(event)**來區分。

connect 為特殊事件,當用戶端連接配接後自動執行

disconnect 為特殊事件,當用戶端斷開連接配接後自動執行

connect、disconnect與自定義事件處理方法的函數傳入參數不同

4,發送消息(emit)

發送消息可以單獨發給某個使用者,也可以群發,SocketIO提供了

房間(room)

來為用戶端分組,也可以發到某個房間,參考如下方法:

1.發給某個使用者(指定使用者id)

2.群發(不指定id号)

3.将連接配接的用戶端放入一個room中 : sio.enter_room(sid, room_name)

@sio.on('chat')
  def begin_chat(sid):
      sio.enter_room(sid, 'chat_users')
           

4.将客戶從某個房間移除: sio.leave_room(sid, room_name)

@sio.on('exit_chat')
  def exit_chat(sid):
      sio.leave_room(sid, 'chat_users')
           

5.查詢sid所在的房間,給一組使用者發送消息:sio.rooms(sid)

@sio.on('my message')
def message(sid, data):
  sio.emit('my reply', data, room='chat_users')
           

6.也可跳過某個用戶端,使用 skip_sid參數

@sio.event
def message(sid, data):
    sio.emit('my reply', data, room='chat_users', skip_sid=sid)
           

7.使用send發送message事件消息

sio.send({'data': 'foobar'})
sio.send({'data': 'foobar'}, room=user_sid)
           

機器人聊天服務實作

第一步:建立AI檔案夾,建立server.py

import socketio

# 建立sio對象
sio = socketio.Server(async_mode='eventlet')
app = socketio.Middleware(sio)
           

第二步:建立chart.py聊天室

import time
from server import sio

@sio.on("connect")
def on_connect(sid, environ):
    """
    與用戶端建立好連接配接後被執行

    :param sid: string sid是socketio為目前連接配接用戶端生成的識别id
    :param environ: dict 在連接配接握手時用戶端發送的握手資料(HTTP封包解析之後的字典)
    
    # sio.emit(消息事件類型, 消息資料内容, 接收人)
    """
 
    data = {
        "msg": "hello carry",   #發送的内容
        'timestamp': round(time.time() * 1000)   #時間戳
    }

    sio.emit("message", data, room=sid)  


@sio.on("message")
def on_message(sid, data):
    """
    用戶端發送消息,on_message執行

    :param sid: string sid是socketio為目前連接配接用戶端生成的識别id
    :param environ: dict 在連接配接握手時用戶端發送的握手資料(HTTP封包解析之後的字典)

    # sio.emit(消息事件類型, 消息資料内容, 接收人)
    """

    resp_data = {
        "msg": "hello carry~~~,接收到你的消息:{}".format(data.get('msg')),
        'timestamp': round(time.time() * 1000)
    }

    sio.emit("message", resp_data, room=sid)
           

第三步:main.py

import eventlet
eventlet.monkey_patch()   
#使用猴子更新檔,在遇到阻塞時,不用修改源代碼,也能執行下面代碼

from server import app
import chat

# 建立eventlet伺服器對象
sock = eventlet.listen(('0.0.0.0', 8000)) 

# 啟動socketio伺服器
eventlet.wsgi.server(sock, app)

           

溫馨提示:

1.測試之前需要在谷歌上安裝Firecamp插件

WebSocket協定和Socket.IO架構

2.啟動終端指令:

python main.py
           

3.點選網址後面小火苗(Firecamp)标志,進入上圖,點選Socket.IO進入以下圖檔,流程及步驟。

WebSocket協定和Socket.IO架構

參考資料:

AIOHTTP:https://aiohttp.readthedocs.io/en/stable/

python-socket:https://python-socketio.readthedocs.io/en/latest/server.html

flask-socket:https://flask-socketio.readthedocs.io/en/latest/