原創博文地址:django進階02websocket
本文適合有一定websocket基礎的,至少完整看過前後端demo的讀者,一竅不通的小白建議先閱讀“參考”部分的博文掃掃盲。
基於django的dwebsocket組件(目前雖然不在維護,但正常使用沒問題)
前端方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script type="text/javascript"> var socket = new WebSocket("ws:" + window.location.host + "/drug/drug_connect/"); socket.onopen = function () { console.log('WebSocket open');//成功連接上Websocket socket.send('adasdasda。。。。');//發送數據到服務端 }; socket.onmessage = function (e) { console.log('message: ' + e.data);//打印服務端返回的數據 }; socket.onclose=function(e){ console.log(e); socket.close(); //關閉TCP連接 }; if (socket.readyState == WebSocket.OPEN) socket.onopen(); </sctipt> |
由於js的異步友好性,所以代碼看起來非常清爽,也容易理解。socket連接,成功後幹什麼(onopen),收到消息後幹嘛(onmessage),如何關閉等。而且也會自動發送Pingpong包確保連接的保持。
後端方法
dwebsocket的一些內置方法:
request.is_websocket():判斷請求是否是websocket方式,是返回true,否則返回false
request.websocket: 當請求爲websocket的時候,會在request中增加一個websocket屬性,
WebSocket.wait() 返回客戶端發送的一條消息,沒有收到消息則會導致阻塞
WebSocket.read() 和wait一樣可以接受返回的消息,只是這種是非阻塞的,沒有消息返回None
WebSocket.count_messages()返回消息的數量
WebSocket.has_messages()返回是否有新的消息過來
WebSocket.send(message)像客戶端發送消息,message爲byte類型
後端方法相對前端就沒有那麼友好了。如果面對一個特定需求如何實現呢?
場景1,1v0聊天
這個實際業務中沒啥用,僅用來驗證websocket接口性質,瞭解接口特性而已。
1 2 3 4 5 6 7 8 9 10 |
@accept_websocket def start_server_script(request): if request.is_websocket(): # "這裏實現wbsocket連接邏輯" for info in request.websocket:# 這裏需要注意的是,這個for後面的對象,request.websocket是阻塞的,也就是說如果對方發送消息,info=新消息,循環走一圈,如果對方不發消息,for這裏會“卡住”,這裏容易忽略 request.websocket.send("你剛纔對我說:%s"%info) print('這裏其實不會被執行') else: # "這裏實現http連接邏輯" pass |
場景2,多v多聊天
上面的例子很簡單吧,自己和自己聊天,多v多聊天代碼和上面差不多!
將request.websocket看做普通對象,將所所有連接的websocket保存全局變量中,依次send(msg)即可.
1 2 3 4 5 6 7 8 9 10 11 12 |
wobsocket_map=dict() @accept_websocket def start_server_script(request): if request.is_websocket(): wobsocket_map[id(request.websocket)]=request.websocket # "這裏實現wbsocket連接邏輯" for info in request.websocket:# 這裏需要注意的是,這個for後面的對象,request.websocket是阻塞的,也就是說如果對方發送消息,info=新消息,循環走一圈,如果對方不發消息,for這裏會“卡住”,這裏容易忽略 [websocket.send("你剛纔對我說:%s"%info) for websocket in wobsocket_map.values()] print('這裏其實不會被執行') else: # "這裏實現http連接邏輯" pass |
可以實現效果,將msg發送給所有連接到此websocket的對象!
場景3,視頻播放
視頻播放是一種類似死循環的處理邏輯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 僞代碼,不保證能跑,意思差不多 # 其他函數通過next(),or .send()調用此生成器,不斷生成各個視頻幀的base64編碼(看不懂的話,百度"yield") url='rtmp://xxxx' @accept_websocket def show_video(request): video=video_img_base64(url) base64=next(video) while base64 is not None: # 這個就是類似死循環的東西(當然這個並非真正死循環,但很多情況退出只能放到循環裏break,這裏只能採用while True) request.websocket.send({'img_base64':base64}) base64=video.send(None) def video_img_base64(url): cap=cv2.VideoCapture(url) ret,frame=cap.read() while ret: yield base64(frame.tobytes()).decode('utf8') ret,frame=cap.read() else: yield None #調用方一旦收到None,避免調用next() or send()否則拋出異常,需要做異常處理 |
場景4,視頻播放及控制
相比前面的例子,多了控制邏輯,那麼問題來了,控制邏輯放哪裏?
如果後臺也可以向js那樣,onmessage(xxx),這樣就簡單多了,onmessage(),根據message修改一個類似全局變量的東西就行。但是並沒有。
從場景1的例子可以看出,websocket非常擅長處理request-response的情況。例子3,看到其也可以處理 持續response推送的情況,那麼如何實現類似異步裏面交互式響應呢?
這裏提供一個簡單模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
send_queue=Queue() @accept_websocket def websocket_ctrl(request): if request.is_websocket: while True:# 由於使用同步方式處理異步,所以這裏必然死循環 if request.websocket.count_messages()>0: message=request.websocket.read() while message: onmessage(message) message=request.websocket.read() if not send_queue.empty:# 需向send_queue放東西,send函數中會放message messsage=send_queue.pop() while message: message=request.websocket.send(message) messsage=send_queue.pop() def onmessage(message): pass # 這裏可以放你想在收到消息時做的事情,類似異步方法的onmessage,如果需要反饋修改,比如改變上一級的執行邏輯,則可以通過返回值的方式,傳遞給上一級調用者,讓上一級調用者通過返回值調整自身執行邏輯 # 他人調用這個方法,將消息加入發送隊列,在websocket_ctrl的循環中會取得,併發出 def send(message): send_queue.put(message) |
注意:儘可能避免使用
1 2 |
for message in request.websocket: print(message) |
由於其會導致阻塞,特別強調這一點,因爲大多數情況,我們看到for循環,會想當然以爲”它很快會結束“,其實未必。
還有一點就是需要加異常處理,對方可能主動關閉連接,此時後臺如果發送消息,會拋出異常。
總結
方法 | 典型使用形態 | 阻塞 | 開啓連接 | (客戶端)發送消息 | (客戶端)斷開連接 |
---|---|---|---|---|---|
request.websocket | for msg in request.websocket:func(msg) | 是 | halt:request.websocket | msg<=’common msg’ | msg<=None |
request.websocket.wait() | while True: msg=request.websocket.wait() | 是 | halt:request.websocket.wait() | msg<=’common msg’ | msg<=None |
request.websocket.read() | while True:msg=request.websocket.read() | 否 | loop:msg<=request.websocket.read() | msg<=request.websocket.read() | except |
request.websocket.has_messages() | while True:has_msg=request.websocket.has_messages();if has_msg:msg=request.websocket.read() | 否 | loop:has_msg<=request.websocket.has_messages() | msg<=request.websocket.read() | has_msg=True and msg=None |
request.websocket.count_messages() | while True:count=request.websocket.count_messages();if count>0:msg=request.websocket.read() | 否 | loop:count<=request.websocket.count_messages() | msg<=request.websocket.read() | count>0 and msg=None |
多重捕獲會怎樣?直觀理解即可,只能有一個捕獲到消息,不會重複捕獲。
參考
https://www.520pf.cn/article/135.html
https://blog.csdn.net/xianailili/article/details/82180114