Flask-SocketIO 簡單使用指南

Flask-SocketIO 使 Flask 應(yīng)用程序能夠訪問客戶端和服務(wù)器之間的低延遲雙向通信坷备。客戶端應(yīng)用程序可以使用 Javascript挨务,C ++击你,Java 和 Swift 中的任何 SocketIO 官方客戶端庫或任何兼容的客戶端來建立與服務(wù)器的永久連接玉组。

安裝

直接使用 pip 來安裝:

pip install flask-socketio

要求

Flask-SocketIO 兼容 Python 2.7 和 Python 3.3+谎柄。可以從以下三個(gè)選項(xiàng)中選擇此程序包所依賴的異步服務(wù):

  • eventlet 性能最佳惯雳,支持長輪詢和 WebSocket 傳輸朝巫。
  • gevent 在許多不同的配置中得到支持。gevent 包完全支持長輪詢傳輸石景,但與 eventlet 不同劈猿,gevent 沒有本機(jī) WebSocket 支持拙吉。要添加對 WebSocket 的支持,目前有兩種選擇:安裝 gevent-websocket 包為 gevent 增加 WebSocket 支持揪荣,或者可以使用帶有 WebSocket 功能的 uWSGI Web 服務(wù)器筷黔。gevent 的使用也是一種高性能選項(xiàng),但略低于 eventlet仗颈。
  • 也可以使用基于 Werkzeug 的 Flask 開發(fā)服務(wù)器佛舱,但需要注意的是,它缺乏其他兩個(gè)選項(xiàng)的性能挨决,因此它只應(yīng)用于簡單的開發(fā)環(huán)境请祖。此選項(xiàng)僅支持長輪詢傳輸。

擴(kuò)展會根據(jù)安裝的內(nèi)容自動(dòng)檢測要使用的異步框架脖祈。優(yōu)先考慮 eventlet肆捕,然后是 gevent。對于 gevent 中的WebSocket 支持盖高,首選 uWSGI慎陵,然后是 gevent-websocket。如果既未安裝 eventlet 也未安裝 gevent或舞,則使用 Flask 開發(fā)服務(wù)器荆姆。

如果使用多個(gè)進(jìn)程,則進(jìn)程使用消息隊(duì)列服務(wù)來協(xié)調(diào)諸如廣播之類的操作映凳。支持的隊(duì)列是 Redis胆筒,RabbitMQ以及 Kombu 軟件包支持的任何其他消息隊(duì)列 。

在客戶端诈豌,官方 Socket.IO Javascript 客戶端庫可用于建立與服務(wù)器的連接仆救。還有使用 Swift,Java 和 C ++ 編寫的官方客戶端矫渔。非官方客戶端也可以工作彤蔽,只要它們實(shí)現(xiàn) Socket.IO協(xié)議

初始化

以下代碼示例演示如何將 Flask-SocketIO 添加到 Flask 應(yīng)用程序:

from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', debug=True)

以上代碼即完成了一個(gè)簡單的 Web 服務(wù)器庙洼。

socketio.run()函數(shù)封裝了 Web 服務(wù)器的啟動(dòng)顿痪,并替換了app.run()標(biāo)準(zhǔn)的 Flask 開發(fā)服務(wù)器啟動(dòng)。

當(dāng)應(yīng)用程序處于調(diào)試模式時(shí)油够,Werkzeug 開發(fā)服務(wù)器仍然在內(nèi)部使用和配置正確socketio.run()蚁袭。

在生產(chǎn)模式中,如果可用石咬,則使用 eventlet Web 服務(wù)器揩悄,否則使用 gevent Web 服務(wù)器。如果未安裝 eventlet 和gevent鬼悠,則使用 Werkzeug 開發(fā) Web 服務(wù)器删性。

基于 Flask 0.11 中引入的單擊的命令行界面亏娜。此擴(kuò)展提供了適用于啟動(dòng) Socket.IO 服務(wù)器的新版本命令。用法示例:flask run

$ FLASK_APP=my_app.py flask run

或者直接使用下面方式蹬挺,也可以啟動(dòng)項(xiàng)目:

$ python2.7 app.py

連接事件

Flask-SocketIO 調(diào)度連接和斷開事件维贺。以下示例顯示如何為它們注冊處理程序:

@socketio.on('connect', namespace='/test')
def test_connect():
    emit('my response', {'data': 'Connected'})

@socketio.on('disconnect', namespace='/test')
def test_disconnect():
    print('Client disconnected')

連接事件處理程序可以選擇返回False以拒絕連接。這樣就可以在此時(shí)對客戶端進(jìn)行身份驗(yàn)證巴帮。

請注意幸缕,連接和斷開連接事件將在使用的每個(gè)命名空間上單獨(dú)發(fā)送。

接收消息

使用 SocketIO 時(shí)晰韵,雙方都會將消息作為事件接收发乔。在客戶端使用 Javascript 回調(diào)。使用 Flask-SocketIO雪猪,服務(wù)器需要為這些事件注冊處理程序栏尚,類似于視圖函數(shù)處理路由的方式。

以下示例為未命名的事件創(chuàng)建服務(wù)器端事件處理程序:

@socketio.on('message')
def handle_message(message):
    print('received message: ' + message)

上面的示例使用字符串消息只恨。另一種類型的未命名事件使用 JSON 數(shù)據(jù):

@socketio.on('json')
def handle_json(json):
    print('received json: ' + str(json))

最靈活的方式是使用自定義事件名稱译仗,在開發(fā)過程中最常用的也是這種方式。

事件的消息數(shù)據(jù)可以是字符串官觅,字節(jié)纵菌,整數(shù)或 JSON:

@socketio.on('my event')
def handle_my_custom_event(json):
    print('received json: ' + str(json))

自定義命名事件也可以支持多個(gè)參數(shù):

@socketio.on('my event')
def handle_my_custom_event(arg1, arg2, arg3):
    print('received args: ' + arg1 + arg2 + arg3)

Flask-SocketIO 支持 SocketIO 命名空間,允許客戶端在同一物理套接字上復(fù)用多個(gè)獨(dú)立連接:

@socketio.on('my event', namespace='/test')
def handle_my_custom_namespace_event(json):
    print('received json: ' + str(json))

如果未指定名稱空間休涤,'/'則使用具有名稱的默認(rèn)全局名稱空間 咱圆。

對于裝飾器語法不方便的情況,on_event可以使用該方法:

def my_function_handler(data):
    pass

socketio.on_event('my event', my_function_handler, namespace='/test')

客戶端可以請求確認(rèn)回叫功氨,確認(rèn)收到他們發(fā)送的消息序苏。處理函數(shù)返回的任何值都將作為回調(diào)函數(shù)中的參數(shù)傳遞給客戶端:

@socketio.on('my event')
def handle_my_custom_event(json):
    print('received json: ' + str(json))
    return 'one', 2

在上面的示例中,將使用兩個(gè)參數(shù)調(diào)用客戶端回調(diào)函數(shù)捷凄,'one'2忱详。如果處理程序函數(shù)未返回任何值,則將調(diào)用客戶端回調(diào)函數(shù)而不帶參數(shù)跺涤。

發(fā)送消息

如上一節(jié)所示定義的 SocketIO 事件處理程序可以使用send()emit() 函數(shù)將回復(fù)消息發(fā)送到連接的客戶端匈睁。

以下示例將收到的事件退回給發(fā)送它們的客戶端:

from flask_socketio import send, emit

@socketio.on('message')
def handle_message(message):
    send(message)

@socketio.on('json')
def handle_json(json):
    send(json, json=True)

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json)

注意如何send()emit()分別用于無名和命名事件。

當(dāng)有命名空間的工作桶错,send()emit()默認(rèn)使用傳入消息的命名空間航唆。可以使用可選namespace參數(shù)指定不同的命名空間:

@socketio.on('message')
def handle_message(message):
    send(message, namespace='/chat')

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json, namespace='/chat')

要發(fā)送具有多個(gè)參數(shù)的事件牛曹,請發(fā)送元組:

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', ('foo', 'bar', json), namespace='/chat')

SocketIO 支持確認(rèn)回調(diào)佛点,確認(rèn)客戶端收到了一條消息:

def ack():
    print 'message was received!'

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json, callback=ack)

使用回調(diào)時(shí)醇滥,Javascript 客戶端會收到一個(gè)回調(diào)函數(shù)黎比,以便在收到消息時(shí)調(diào)用超营。客戶端應(yīng)用程序調(diào)用回調(diào)函數(shù)后阅虫,服務(wù)器將調(diào)用相應(yīng)的服務(wù)器端回調(diào)演闭。如果使用參數(shù)調(diào)用客戶端回調(diào),則這些回調(diào)也作為服務(wù)器端回調(diào)的參數(shù)提供颓帝。

廣播

SocketIO 的另一個(gè)非常有用的功能是廣播消息米碰。SocketIO 支持通過此功能broadcast=True可選參數(shù)send()emit()

@socketio.on('my event')
def handle_my_custom_event(data):
    emit('my response', data, broadcast=True)

在啟用廣播選項(xiàng)的情況下發(fā)送消息時(shí),連接到命名空間的所有客戶端都會接收它购城,包括發(fā)件人吕座。如果未使用名稱空間,則連接到全局名稱空間的客戶端將收到該消息瘪板。請注意吴趴,不會為廣播消息調(diào)用回調(diào)。

在此處顯示的所有示例中侮攀,服務(wù)器響應(yīng)客戶端發(fā)送的事件锣枝。但對于某些應(yīng)用程序,服務(wù)器需要是消息的發(fā)起者兰英。這對于向客戶端發(fā)送通知在服務(wù)器中的事件(例如在后臺線程中)非常有用撇叁。socketio.send()socketio.emit()方法可用于廣播到所有連接的客戶端:

def some_function():
    socketio.emit('some event', {'data': 42})

請注意,socketio.send()socketio.emit()在上下文理解上和send()emit()功能不同畦贸。另請注意陨闹,在上面的用法中沒有客戶端上下文,因此broadcast=True是默認(rèn)的薄坏,不需要指定正林。

房間

對于許多應(yīng)用程序,有必要將用戶分組為可以一起尋址的子集颤殴。最好的例子是具有多個(gè)房間的聊天應(yīng)用程序觅廓,其中用戶從他們所在的房間接收消息,而不是從其他用戶所在的其他房間接收消息涵但。SocketIO 支持通過房間的概念join_room()leave_room()功能:

from flask_socketio import join_room, leave_room

@socketio.on('join')
def on_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    send(username + ' has entered the room.', room=room)

@socketio.on('leave')
def on_leave(data):
    username = data['username']
    room = data['room']
    leave_room(room)
    send(username + ' has left the room.', room=room)

send()emit()函數(shù)接受一個(gè)可選room導(dǎo)致被發(fā)送到所有的都在定房客戶端的消息的說法杈绸。

所有客戶端在連接時(shí)都會被分配一個(gè)房間,以連接的會話ID命名矮瘟,可以從中獲取request.sid瞳脓。給定的客戶可以加入任何房間,可以給出任何名稱澈侠。當(dāng)客戶端斷開連接時(shí)劫侧,它將從其所在的所有房間中刪除。無上下文socketio.send()socketio.emit()函數(shù)也接受一個(gè)room參數(shù),以廣播給房間中的所有客戶端烧栋。

由于為所有客戶端分配了個(gè)人房間写妥,為了向單個(gè)客戶端發(fā)送消息,客戶端的會話 ID 可以用作房間參數(shù)审姓。

錯(cuò)誤處理

Flask-SocketIO還可以處理異常:

@socketio.on_error()        # Handles the default namespace
def error_handler(e):
    pass

@socketio.on_error('/chat') # handles the '/chat' namespace
def error_handler_chat(e):
    pass

@socketio.on_error_default  # handles all namespaces without an explicit error handler
def default_error_handler(e):
    pass

錯(cuò)誤處理函數(shù)將異常對象作為參數(shù)珍特。

還可以使用request.event變量檢查當(dāng)前請求的消息和數(shù)據(jù)參數(shù),這對于事件處理程序外部的錯(cuò)誤記錄和調(diào)試很有用:

from flask import request

@socketio.on("my error event")
def on_my_event(data):
    raise RuntimeError()

@socketio.on_error_default
def default_error_handler(e):
    print(request.event["message"]) # "my error event"
    print(request.event["args"])    # (data,)

基于類的命名空間

作為上述基于裝飾器的事件處理程序的替代魔吐,屬于命名空間的事件處理程序可以創(chuàng)建為類的方法扎筒。flask_socketio.Namespace作為基類提供,用于創(chuàng)建基于類的命名空間:

from flask_socketio import Namespace, emit

class MyCustomNamespace(Namespace):
    def on_connect(self):
        pass

    def on_disconnect(self):
        pass

    def on_my_event(self, data):
        emit('my_response', data)

socketio.on_namespace(MyCustomNamespace('/test'))

使用基于類的命名空間時(shí)酬姆,服務(wù)器接收的任何事件都將調(diào)度到名為帶有on_前綴的事件名稱的方法近范。例如骡送,事件my_event將由名為的方法處理on_my_event。如果收到的事件沒有在命名空間類中定義的相應(yīng)方法,則忽略該事件训柴∥芘耄基于類的命名空間中使用的所有事件名稱必須使用方法名稱中合法的字符溪胶。

為了方便在基于類的命名空間中定義的方法阔拳,命名空間實(shí)例包括類中的幾個(gè)方法的版本,flask_socketio.SocketIO當(dāng)namespace沒有給出參數(shù)時(shí)雳灵,這些方法 默認(rèn)為正確的命名空間棕所。

如果事件在基于類的命名空間中具有處理程序,并且還有基于裝飾器的函數(shù)處理程序悯辙,則僅調(diào)用修飾的函數(shù)處理程序琳省。

測試

以上是作為官網(wǎng)文檔的翻譯,下面來說說寫完了代碼之后躲撰,應(yīng)該怎么來調(diào)試针贬。

<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    socket.on('connect', function() {
        socket.emit('my event', {data: 'I\'m connected!'});
    });
</script>

使用 JavaScript 來連接服務(wù)端,這里說一個(gè)我遇到的問題拢蛋,最開始使用的是 jsbin 來測試桦他,但怎么都連不到后端,原因就是 jsbin 是 HTTPS 的谆棱,而我的請求是 HTTP快压,于是還是老老實(shí)實(shí)寫了一個(gè) HTML 文件,源碼可以直接在 Github 下載垃瞧。

<!DOCTYPE HTML>
<html>
<head>
    <title>Flask-SocketIO Test</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
    <script type="text/javascript" charset="utf-8">
        $(document).ready(function() {
            namespace = '/test';
            var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);

            socket.on('connect', function() {
                socket.emit('my_event', {data: 'I\'m connected!'});
            });
            
            socket.on('my_response', function(msg) {
                $('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
            });

            $('form#emit').submit(function(event) {
                socket.emit('my_event', {data: $('#emit_data').val()});
                return false;
            });
        });
    </script>
</head>
<body>
    <h1>Flask-SocketIO Test</h1>
    <p>Async mode is: <b>{{ async_mode }}</b></p>
    <h2>Send:</h2>
    <form id="emit" method="POST" action='#'>
        <input type="text" name="emit_data" id="emit_data" placeholder="Message">
        <input type="submit" value="Echo">
    </form>
    <h2>Receive:</h2>
    <div id="log"></div>
</body>
</html>

有了這個(gè)頁面之后蔫劣,就可以直接在瀏覽器中輸入 http://127.0.0.1:5000 訪問服務(wù)端了,更多功能可以隨意折騰个从。

相關(guān)文檔:

https://github.com/miguelgrinberg/Flask-SocketIO

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

http://windrocblog.sinaapp.com/?p=1628

https://zhuanlan.zhihu.com/p/31118736

https://letus.club/2016/04/10/python-websocket/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脉幢,一起剝皮案震驚了整個(gè)濱河市歪沃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫌松,老刑警劉巖沪曙,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豆瘫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)菊值,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門外驱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腻窒,你說我怎么就攤上這事昵宇。” “怎么了儿子?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵瓦哎,是天一觀的道長。 經(jīng)常有香客問我柔逼,道長蒋譬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任愉适,我火速辦了婚禮犯助,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘维咸。我一直安慰自己剂买,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布癌蓖。 她就那樣靜靜地躺著瞬哼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪租副。 梳的紋絲不亂的頭發(fā)上坐慰,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天,我揣著相機(jī)與錄音用僧,去河邊找鬼讨越。 笑死,一個(gè)胖子當(dāng)著我的面吹牛永毅,可吹牛的內(nèi)容都是我干的把跨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼沼死,長吁一口氣:“原來是場噩夢啊……” “哼着逐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耸别,失蹤者是張志新(化名)和其女友劉穎健芭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秀姐,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慈迈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了省有。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痒留。...
    茶點(diǎn)故事閱讀 40,435評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蠢沿,靈堂內(nèi)的尸體忽然破棺而出伸头,到底是詐尸還是另有隱情,我是刑警寧澤舷蟀,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布恤磷,位于F島的核電站,受9級特大地震影響野宜,放射性物質(zhì)發(fā)生泄漏扫步。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一匈子、第九天 我趴在偏房一處隱蔽的房頂上張望锌妻。 院中可真熱鬧,春花似錦旬牲、人聲如沸仿粹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吭历。三九已至,卻和暖如春擂橘,著一層夾襖步出監(jiān)牢的瞬間晌区,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工通贞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朗若,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓昌罩,卻偏偏與公主長得像哭懈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子茎用,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 上回我們說到了一些小功能遣总,這次再對flask應(yīng)用做一個(gè)補(bǔ)充睬罗,交代flask的上下文變量,身份認(rèn)證等功能旭斥。 當(dāng)我們的...
    天涯待歸客閱讀 5,825評論 0 5
  • 筆者最近在學(xué)習(xí)flask-SocketIO容达,由于找不到中文文檔,就選擇了自己翻譯官方的英文文檔垂券,并分享給大家花盐,希望...
    天涯待歸客閱讀 11,578評論 9 7
  • 很抱歉,第二部分來得太遲菇爪。不過算芯,我向讀者朋友們保證:在2016年結(jié)束前,一定會將完整的翻譯呈現(xiàn)給大家娄帖。 第二部分將...
    天涯待歸客閱讀 1,692評論 5 3
  • SocketIO是一個(gè)基于websocket的封裝的傳輸框架也祠。在大多數(shù)對數(shù)據(jù)量要求不高的場景里昙楚,可以用于快速搭建實(shí)...
    凌峰閱讀 6,450評論 3 2
  • 2月11 1266 不去小區(qū)卡點(diǎn)值守的日子有點(diǎn)單調(diào)近速,可能是和小區(qū)有了深深的感情,不去有點(diǎn)懷念堪旧,但不去不是因?yàn)樽约翰?..