django進階02websocket

原創博文地址: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章