一、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而不是其他?
- HTTP/1.x 版本不支援伺服器主動推送,隻能在用戶端發起請求後做出回應。
- HTTP/2.x版本支援伺服器主動推送,但HTTP/2 還未全面實施
- 輪詢 :每隔一段時間就會想伺服器發送請求,詢問是否有新的消息,必須不停連接配接,或者連接配接始終打開,效率低下,浪費資源。
- 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插件
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcsQXYtJ3bm9CXldWYtlWPzNXZj9mcw1ycz9WL49jb1c0Y1lVbZp3a65EbSRVWoxmaNRTTH9UasRFTy0EVPhXVU5UN4k3YsR2VZRHbyg1aGJjYzJEWkZHOXFWdVhUY6VzVZBHctxkeWJjWoFzVhRXUXlld4d0YxkTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
2.啟動終端指令:
python main.py
3.點選網址後面小火苗(Firecamp)标志,進入上圖,點選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/