本人工作中需要用到flask-socketio,在學習英文文檔時發現,flask-socketio目前并沒有相關的中文文檔。鬥膽利用業餘時間将這個庫的英文文檔翻譯出來,希望能夠幫助那些沒有時間或精力研習英文文檔的朋友。鑒于水準有限,翻譯錯誤在所難免,還望各位不吝賜教。任何問題都可以發送郵件給我。(email: [email protected])
注意:譯者所用的flask-socketio版本号是:2.7.2,無特殊情況,本文檔的一切特性均以2.7.2版本為準。
正文:
flask-SocketIO 為flask應用提供了一個用戶端與伺服器之間低延遲的雙向通信。用戶端應用可以用Javascript,C++,Java,Swift或者其它任意的程式設計語言的socketio官方庫的用戶端去和服務端建立一個永久的連接配接。
1.安裝
你可以使用pip這樣正常的方式來安裝這個包:
2.依賴
Flask-SocketIO相容python2.7和python3.3+。這個異步的服務的包的依賴可以有三個選擇:

eventlet:這是最好的選擇,支援長連接配接(long-polling)和websocket傳輸。

gevent: 支援許多不同的配置,長連接配接傳輸是完全支援的,但是不同于eventlet,gevent并沒有原生支援websocket。添加websocket(功能)有兩種方法:gevent-websocket包為gevent添加了websocket支援,但是不幸的是,這個包隻能用于python2;至于另外一個選擇,是用uWSGI網絡伺服器,這個能夠在功能上支援websocket。gevent依然是可操作的選擇,但是優先級略微地低于eventlet。

基于Werkzeug開發的flask伺服器也是可行的,使用缺乏可操作性的caveat,它僅可以被用于簡化workflow的開發。這個方案僅支援長連接配接方式傳輸。
這個擴充自動尋找已安裝的異步架構來使用。最優先的是eventlet,其次是gevent。在gevent中,對于websocket的支援,uWSGI是優先考慮的,其次是gevent-websocket。如果eventlet和gevent都沒有被安裝,那麼就使用flask-development将會被啟用。
如果使用多程序,一個消息隊列服務将會被程序用來協調操作,例如廣播。支援這個隊列的有Redis,RabbitMQ,還有其他由Kombu支援的包。
在用戶端,Javascript官方的SOcket.IO可以用來建立一個與服務端通信的連接配接。這裡有許多用Swift,Java,C++編寫的官方用戶端。非官方的用戶端也是可以工作的,隻要他們支援了Socket.IO協定。
3.初始化
接下來的代碼例子揭示了,怎樣去把Flask-SocketIO引入到Flask應用:
init_app()風格的初始化也是支援的。注意網絡伺服器的啟動。函數socketio.run()封裝了網絡伺服器的啟動部分,并且代替了flask開發伺服器的标準啟動語句<code>app.run()</code>。當應用在debug模式下,Werkzeug開發伺服器也是在<code>socketio.run()</code>中被合理地應用和配置。如果可用的話,在生産模式下eventlet網絡伺服器也是被應用的,否則,gevent網絡伺服器将會被啟用。如果eventlet和gevent都沒有被安裝,那麼将會使用Werkzeug開發網絡伺服器。
在flask 0.11中被引入的可點選指令行界面也是被支援的。這個擴充提供了一個新版的flask run指令,适合啟動一個Socket.IO伺服器。用法示例:
<code>FLASK_APP = my_app flask run</code>
這個應用隻能為那種連接配接到用戶端的頁面服務,并且用戶端還需引用Socket.IO庫并且建立一個連接配接:
4.接收消息
在使用SocketIO的時候,消息将被作為活動(event)的兩端接收。在用戶端使用JavaScript回叫信号。使用Flask-SocketIO伺服器,需要為這些活動注冊處理器(handler),類似于視圖函數怎樣處理路由。
下面的例子是為一個未命名的活動建立了一個服務端的活動處理器(event handler):
在上面的例子中,使用了字元串消息。此外,另一種未命名的活動使用了JSON資料:
最靈活的一種活動使用了自定義的活動名稱。這些活動的消息資料類型可以是字元串,位元組,整型,或者JSON:
自定義名稱的活動可以支援多參數:
命名活動是極度複雜的,在其消除了額外的中繼資料(metadata)來描述消息類型的時候。
Flask-SocketIO同樣支援命名空間(namespace),這個功能允許用戶端在一個相同的實體socket上多路複用幾個獨立的連接配接:
當一個命名空間沒有具體指出,一個全局的命名空間'/'将會被啟用
有時,裝飾器的文法并不友善,on_event()方法可以作為替代
用戶端要求一個确認回複,來确認消息的接收。任何一個從處理函數(handler function)中傳回的值都會在回調函數中作為一個參數傳回給用戶端。
在上面的例子中,用戶端回調函數将會回調兩個參數,<code>one</code>和<code>2</code>。如果處理函數沒有傳回值,這個用戶端回調函數将以沒有參數的情況傳回。
5.發送消息
之前章節定義的SocketIO活動處理函數可以憑借<code>send()</code>函數和<code>emit()</code>函數來連接配接用戶端
接下來的例子是将接收到的消息退回到發送它們的用戶端:
注釋一下,<code>send()</code>和<code>emit()</code>是怎樣用在已命名和未命名的活動上的
當運作在有命名空間的活動中時,<code>send()</code>和<code>emit()</code>預設用在接下來的消息中。不同的命名空間可以被具體化到可選擇的可選擇的命名空間參數上:
為了實作發送一個多參數的活動,發送一個元組:
使用回調時,JavaScript用戶端使用回調函數在接收到的資訊時回調。在用戶端應用啟用回調函數時,伺服器會啟用服務端相比對的函數去響應。如果用戶端沒有回調任何值,這些将會作為服務端的響應被提供。
用戶端的應用同樣要求一個來自服務端的确認資訊。如果服務端想為一次響應提供一個參數,它必須要在活動處理函數中被傳回。
6.廣播
SocketIO另外一個非常有用的特性就是廣播消息。Flask-SocketIO中,隻要将<code>broadcast = True</code>這個可選參數加到send()和emit()中即可:
當一個消息以廣播選項被開啟的情況下被發出的時候,連接配接到這個命名空間的所有用戶端都會收到這個消息。注意:廣播的消息将不會被回調。
所有的例子表明,直到這個節點伺服器才回複用戶端發出的這個活動。但是另外的應用中,伺服器需要成為消息的發起者。對于起源于伺服器的活動而言,這個有利于發送通知到用戶端,比如在背景線程中。<code>socketio.send()</code>和<code>socketio.emit()</code>方法可以用來對所有的連接配接進行廣播。
注意:通過對send()和emit()的上下文的感覺,<code>socketio.send()</code>和<code>socketio.emit()</code>不是相同的函數。同樣需要注意的是:以上的用法是沒有用戶端内容,是以假定<code>broadcast=True</code>,并且需要被具體化。
7.房間
在許多應用中,有必要将使用者劃分為可以一并處理的幾個子集。最好的例子是,一個包含多個房間的聊天應用,當使用者收到他所在的房間的消息,而不會收到其他人所在房間的消息。Flask-SocketIO支援通過<code>join_room()</code>和<code>leave_room()</code>函數來支援房間的概念:
<code>send()</code>和<code>emit()</code>函數接收<code>room</code>作為一個參數,将消息廣播到所有在給定房間裡的用戶端。
所有連接配接(到伺服器)的用戶端都被配置設定到一個房間,并且以連接配接的會話編号(session ID)命名這個房間的名稱,這個會話編号由request.sid獲得。一個既定的用戶端可以加入任何一個房間,這個房間的名稱可以是任何名稱。當一個用戶端終止(與伺服器的)連接配接,它将會從原來所在的房間裡除名。這兩個上下文無關的函數<code>socketio.send()</code>和<code>socketio.emit()</code>也會接受參數<code>room</code>,把消息廣播到這個房間裡的所有用戶端。
一旦所有的用戶端被配置設定到一個自己的房間,為了将消息發送到一個唯一的用戶端,會話編号可以作為參數room的值。
8.連接配接活動
Flask-SocketIO同樣支援連接配接和斷開的活動。接下來的例子将會展示怎樣為他們注冊一個處理函數:
連接配接活動處理處理函數可以選擇性地傳回一個False去拒絕這個連接配接。這是為了在這一點上進行身份認證。
注意:連接配接和斷開活動可以在各自使用的命名空間内獨立地發送。
9.基于類的命名空間
以上描述的作為基于裝飾器的活動處理函數的替代,屬于命名空間的活動處理函數可以被創造成一個類的方法。Flask_socketio.Namespace提供了一個基于類的方法來創造命名空間。
使用一個基于類的命名空間時,所有伺服器接收到的活動将會被配置設定到一個方法,該方法的活動名稱是以on_為字首的活動。例如,名稱為<code>my_event</code>的活動,将會由<code>on_my_event</code>函數來處理。如果一個接收到的活動在命名空間類中沒有與之相比對的處理方法。這個活動将會被忽略。所有在基于類的命名空間内的活動必須使用具有合法的方法名稱的單詞。
作為一個定義基于類的命名空間的簡便方法,這個命名空間執行個體包括了幾個版本的flask_socketio.SocketIO類,并且他們預設的命名空間參數并沒有給出。
如果一個活動同時具有在基于類的命名空間裡的處理函數和基于裝飾器的處理函數,隻有裝飾器函數會被調用。
10.錯誤處理
Flask-SocketIO也可以處理異常:
錯誤處理函數将異常對象作為一個參數
這個消息和資料參數作為目前的請求将會被察覺<code>request.event</code>變量察覺,這有利于外部活動處理函數的錯誤日志和調試
11.通路flask上下文全局變量
SocketIO活動處理不同于路由處理,在于它引入了許多容易混淆的東西,圍繞着SocketIO什麼可以做,什麼不可以做。最主要的差別就是SocketIO活動發生在單個長期運作在上下文的請求之中。
盡管有所不同,Flask-SocketIO将環境改造成類似于正常HTTP請求,使SocketIO活動處理更加輕松。接下來的清單描述了什麼将會生效,什麼不會。

在活動處理函數之前推送應用的上下文使得<code>current_app</code>和<code>g</code>可以在處理函數中可用。

這個請求的上下文同樣在回調處理函數前被啟用,也使<code>request</code>和<code>session</code>可用。但是注意到WebSocket活動與之并沒有獨立的聯系,是以為連接配接期間分派的所有事件推送

啟動連接配接的請求上下文。


request上下文全局變量由包含了目前處理函數的命名空間和活動參數的<code>argument</code>和<code>event</code>來增加。這個活動成員是一個包含了<code>message</code>和<code>args</code>鍵值的字典。



SocketIO處理器可以使用自定義的裝飾器,但是大多數Flask裝飾器并不适于SocketIO處理器,考慮到SocketIO連接配接中沒有Response對象這一概念。
12.身份認證
應用的共同需要就是驗證他們使用者的身份。自從SocketIO沒有使用HTTP請求和應答,傳統的基于網頁表單和HTTP請求的機制不能用于SocketIO連接配接。如果需要的話,應用可以實施自定義的登陸表單,當使用者按下送出按鈕時,它利用一個SocketIO消息将證書發送到伺服器。
然而,在大多數情況下,在SocketIO連接配接建立之前使用傳統的身份驗證方式會更加友善,使用者的身份資訊可以被記錄下來作為使用者會話或者cookie,之後在SocketIO連接配接建立起來的時候,這些資訊也可以被SocketIO活動處理器得到。
13.使用Flask-SocketIO的Flask-Login子產品
Flask-SocketIO可以獲得由Flask-Login維護的登陸資訊。在一個正常的Flask-Login身份認證被使用的時候,login_user()函數将會被調用去記錄使用者會話中的使用者,任何SocketIO連接配接都可以得到<code>current_user</code>上下文變量:
注意到<code>login_required</code>裝飾器不能和SocketIO活動處理器一起使用,但是一個自定義的關閉連接配接無身份認證的裝飾器可以按下面的方式建立:
14.部署
我們有多種部署Flask-SocketIO伺服器的選擇,從最簡單到瘋狂地複雜。在這一章節裡,我們将會
介紹最普遍的選擇。
嵌入式伺服器
最簡單的政策是安裝eventlet或者gevent,并且就像前面章節的例子中引用<code>socketio.run(app)</code>的方式來啟動網絡伺服器。這個将會在eventlet或者gevent網絡伺服器中啟動這個應用,被嵌入的網絡伺服器是哪一個取決于是安裝的是哪一個。
注意到<code>socketio.run(app)</code>運作在eventlet或gevent已安裝上的生産伺服器中。如果它們中沒有一個被安裝,那麼這個應用運作在Flask開發伺服器中,這并不适于生産環境的使用。
不幸的是,這個選擇并不能在帶有uWSGI的gevent伺服器上使用,你可以在下面擷取更多有關這個選項的資訊。
Gunicorn網絡伺服器
作為<code>socketio.run(app)</code>替代方法的就是使用gunicorn作為網絡伺服器,工作在eventlet或gevent下。這個選擇下,除了gunicorn要安裝,eventlet或者gevent也是不可缺少的。這個條指令将會啟動這個基于gunicorn的eventlet伺服器:
<code>gunnicorn --worker--class eventlet -w 1 module:app</code>
如果你更傾向于使用gevent,啟動伺服器的指令如下:
<code>gunicorn -k gevent -w 1 module:app</code>
當使用gunicorn作為gevent的工作站并且websocket支援也被提供的時候,上述指令就必須被改成選擇一個自定義的gevent網絡伺服器來支援websocket協定。修改後的指令如下:
<code>gunicorn -k geventwebsocket.gunicorn.worker.GeventWebSocketWorker -w 1 module:app</code>
在上述這些指令中,module是python子產品或者是定義了應用執行個體的包,此外,app是應用執行個體本身。
Gunicorn 18.0版本是被推薦和Flask-SocketIO搭配的版本。19.x版本已知在帶有WebSocket的一些特定部署場景下存在不相容的情況。
gunicorn由于使用了有限的負載均衡算法,不可能在使用這種網絡伺服器時調用兩個以上工作程序因為這個原因,上面的所有例子中都包含了<code>-w 1</code>的可選參數。
15.uWSGI網絡伺服器
當使用uWSGI網絡伺服器搭配geventd的時候,Socket.IO伺服器的時候,可以利用uWSGI原生的WebSocket支援。
一個配置和運用uWSGI伺服器完整的解釋超出了本文的論述範圍。uWSGI伺服器确實是一個比較複雜的,它提供了大量而又詳盡的設定選項。它必須使用Websocket和SSL編譯才能支援WebSocket傳輸。作為介紹,下面的指令啟動了一個uWSGI伺服器作為範例,這個應用app.py運作在端口5000:
<code>uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app </code>
6.使用nginx作為反向代理伺服器
使用nginx作為前端的反向代理将請求傳遞給應用是可行的。然而,隻有nginx 1.4版本以上才支援WebSocket協定。下面是nginx代理HTTP和WebSocket請求的一個最基本的配置:
下面的例子增加了對負載平衡多個伺服器的支援:
雖然上面的例子可以作為最初的配置工作,要知道生産環境安裝的nginx需要一個完整的配置,包括部署的其它方面,例如服務于靜态檔案的assert和SSL支援。
17.使用多個工作站
Flask-SocketIO從2.0版本起帶有負載均衡器支援多個工作站。部署多個工作站給了使用Flask-SocketIO的應用程式有能力在多程序和多主機之間傳播用戶端連結,這種方式的擴充支援極大規模的并發用戶端。
使用多個Flask-SocketIO工作站需要兩個依賴:
* 負載均衡器必須要配置成總是将所有的HTTP請求從一個給定的用戶端轉發到同樣的工作站中。這有時會作為<code>sticky session</code>被提及。對于nginx,使用這個ip_bash訓示來達到上述要求。Gunicorn不能用于多工作站,因為它的負載均衡算法并不支援粘性會話(sticky session)。
* 一旦每個伺服器隻擁有一個用戶端連接配接,在Redis、RabbitMQ等例子中,消息隊列将會被使用,來協調複雜的操作,比如:廣播和房間。
當使用消息隊列的時候,有許多額外的依賴包需要被安裝:
* 對于Redis,redis包必須被安裝(<code>pip install redis</code>)。
* 對于RabbitMQ,kombu包必須要被安裝(<code>pip install kombu</code>)。
* 如果使用了eventlet或者gevent,那麼通常需要使用猴子(Monkey)修補Python标準庫來強制消息隊列包使用協同友好的函數和類。
為了啟動多個Flask-SocketIO伺服器,你必須首先確定消息隊列服務正在運作。為了開啟一個Socket.IO伺服器,使他連接配接到一個消息隊列,需要添加參數<code>message_queue</code>到構造函數SockIO:
<code>socketio=SocketIO(app,message_queue='redis://')</code>
參數message_queue的值就是隊列服務所使用的連接配接URL。對于一個運作在同一個作為伺服器的主機中的Redis隊列來說,可以使用<code>redis://</code>這樣的URL。同樣,對于一個預設的RabbitMQ隊列可以使用<code>amqp://</code>開頭的URL。Kombu包有一個文檔章節闡述了對于所有支援隊列的URL格式。
18.外部程序消息
對于許多類型的應用,從非服務端建立會話活動很有必要,例如一個Celery工作站。如果SocketIO伺服器并沒有按照前面章節那樣配置監聽隊列,那麼所有其它的程序可以像伺服器那樣建立它自己的SocketIO執行個體來建立消息活動。
例如,一個運作在eventlet網絡伺服器上的應用,使用了Redis消息隊列,下面的Python腳本将向所有的用戶端廣播一個消息活動。
當使用這種方法引用SocketIO執行個體,Flask應用執行個體将不會傳遞到構造函數。
當SocketIO通過消息隊列使用參數channel來選擇一個具體channel的對話。當很多獨立的SocketIO服務公用一個隊列的時候,使用一個自定義的channel名稱将是很有必要的。
Flask-SocketIO并沒有在使用eventlet或者gevent時應用猴子(monkey)來修補。但是當使用消息隊列的時候,如果Python标準庫沒有使用猴子來修補,那麼消息隊列服務的Python包很可能會挂起。
很重要的一點是:外部程序想連接配接到SocketIO伺服器并不需要像主伺服器那樣使用eventlet或者gevent。使一個伺服器使用了協同架構,外部程序不是一個阻力。例如,Celery工作站并不需要配置使用eventlet或者gevent,是因為主伺服器已經有了。但是,如果你的外部程序因為某種原因
使用了協同架構,那麼monkey修複就很可能是需要的,那麼消息隊列就可以獲得協同友好的函數和類。
19.從Flask-SocketIO 0.x 更新到 1.x 和 2.x 版本
老版本的Flask-SocketIO有完全不同的一系列依賴包。老版本依賴gevent-socketio和gevent-websocket,這些包 1.0 版本都不需要了。
盡管依賴的改變,但是 1.0 版本卻沒有太多重要的改變。下面是一個實際改變的詳細的清單:
* 1.0 版本放棄支援Python 2.6,增加了對Python 3.3, Python 3.4 和 pypy 的支援。
* 0.x 版本需要老版本的Socket.IO javascript用戶端。從 1.0 版本開始,支援新釋出的Socket.IO和Engin.IO。1.0版本以前的Socket.IO将不再被支援。Swift和C++官方的Socket.IO用戶端也被支援。
* 0.x 版本依賴gevent,gevent-socketio和gevent-websocket.1.0 版本以後将不再使用。在Flask開發的網絡伺服器中,gevent是三種後端網絡伺服器選擇之一,另外兩個是eventlet和其它正常多線程WSGI伺服器。
* Socket.IO伺服器選項在 1.0 版本中也有所改變。它們可以由SocketIO構造函數來提供,或者由<code>run()</code>調用。這些選項在使用前在這兩者中被合并。
* 0.x 版本暴露了gevent-socketio在連接配接中作為<code>request.namespace</code>。在 1.0 版本中它不再被使用。這個請求對象定義了<code>request.namespace</code>作為待處理的指令空間。并且增加了<code>request.aid</code>,為用戶端連接配接定義了一個獨有的會話ID,<code>request.event</code>包含了活動名稱和參數。
* 為了獲得房間清單,0.x版本需要應用使用私有gevent-socketio結構,包含<code>request.namespace.rooms</code>表達式。這是在 1.0 版本中将不再出現,因為它包含了一個合适的<code>room()</code>函數。
* 這個推薦的“把戲(trick)”發送消息到一個獨立的用戶端将消息分發到每個用戶端所在的獨立的房間内,這個位址消息對應着目的房間(desired room)。這個特性在 1.0 版本中被正式化了,當用戶端連接配接到伺服器時,它會立即自動地被配置設定到一個特定的房間内。
* 全局命名空間的<code>connect</code>活動在 1.0 版本之前并沒有被觸發。這bug已經被修複了并且按照預期觸發。
* 在 1.0 版本增加了對用戶端的回調函數的支援。
為了更新到新的Flask-SocketIO版本,你需要更新你的Socket.IO用戶端到相容Socket.IO 1.0 協定。對于Javascript用戶端,1.3.x和1.4.x版本經過充分地測試,發現是相容的。
在服務端,有一些要點是要被考慮到的:
* 如果你想繼續使用gevent,那麼gevent-socketio需要從你的虛拟環境中解除安裝,因為這個包将不再需要并且可能會與它的替代——python-socketio相沖突。
* 如果你想輕微地提高性能和穩定性,那麼推薦你轉而使用eventlet。為了做到這一點,需要解除安裝gevent、gevent-socketio和gevent-websocket,然後安裝eventlet。
* 如果你的應用使用了猴子修複了并轉向了eventlet,需要調用<code>eventlet.monkey_patch()</code>來代替gevent中的<code>monkey.patch_all()</code>。此外,任何對gevent的調用必須被同等條件下的對eventlet調用替代。
* 任何使用<code>request.namespace</code>需要被直接調用Flask-SocketIO函數替代。例如,<code>request.namespace.rooms</code>要用<code>rooms()</code>函數替換。
* 任何使用内置的gevent-socketio的對象都必須被去除,當這個包不再是所需的依賴的時候。
原文釋出時間為:2017-01-16
本文作者:詹聰聰