作者:詹聰聰
序言:
本人工作中需要用到flask-socketio,在學習英文文檔時發(fā)現(xiàn),flask-socketio目前并沒有相關的中文文檔。斗膽利用業(yè)余時間將這個庫的英文文檔翻譯出來,希望能夠幫助那些沒有時間或精力研習英文文檔的朋友。鑒于水平有限,翻譯錯誤在所難免,還望各位不吝賜教。
注意:譯者所用的flask-socketio版本號是:2.7.2,無特殊情況,本文檔的一切特性均以2.7.2版本為準。
正文:
flask-SocketIO 為flask應用提供了一個客戶端與服務器之間低延遲的雙向通信。客戶端應用可以用Javascript,C++,Java,Swift或者其它任意的編程語言的socketio官方庫的客戶端去和服務端創(chuàng)建一個永久的連接。
1.安裝
你可以使用pip這樣常規(guī)的方式來安裝這個包:
> pip install flask-socketio
2.依賴
Flask-SocketIO兼容python2.7和python3.3+。這個異步的服務的包的依賴可以有三個選擇:
eventlet:這是最好的選擇,支持長連接(long-polling)和websocket傳輸。
gevent: 支持許多不同的配置,長連接傳輸是完全支持的,但是不同于eventlet,gevent并沒有原生支持websocket。添加websocket(功能)有兩種方法:gevent-websocket包為gevent添加了websocket支持,但是不幸的是,這個包只能用于python2;至于另外一個選擇,是用uWSGI網(wǎng)絡服務器,這個能夠在功能上支持websocket。gevent依然是可操作的選擇,但是優(yōu)先級略微地低于eventlet。
基于Werkzeug開發(fā)的flask服務器也是可行的,使用缺乏可操作性的caveat,它僅可以被用于簡化workflow的開發(fā)。這個方案僅支持長連接方式傳輸。
這個擴展自動尋找已安裝的異步框架來使用。最優(yōu)先的是eventlet,其次是gevent。在gevent中,對于websocket的支持,uWSGI是優(yōu)先考慮的,其次是gevent-websocket。如果eventlet和gevent都沒有被安裝,那么就使用flask-development將會被啟用。
如果使用多進程,一個消息隊列服務將會被進程用來協(xié)調(diào)操作,例如廣播。支持這個隊列的有Redis,RabbitMQ,還有其他由Kombu支持的包。
在客戶端,Javascript官方的SOcket.IO可以用來創(chuàng)建一個與服務端通信的連接。這里有許多用Swift,Java,C++編寫的官方客戶端。非官方的客戶端也是可以工作的,只要他們支持了Socket.IO協(xié)議。
3.初始化
接下來的代碼例子揭示了,怎樣去把Flask-SocketIO引入到Flask應用:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.configp['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
if __name__ == '__main__':
socketio.run(app)
init_app()風格的初始化也是支持的。注意網(wǎng)絡服務器的啟動。函數(shù)socketio.run()封裝了網(wǎng)絡服務器的啟動部分,并且代替了flask開發(fā)服務器的標準啟動語句app.run()。當應用在debug模式下,Werkzeug開發(fā)服務器也是在socketio.run()中被合理地應用和配置。如果可用的話,在生產(chǎn)模式下eventlet網(wǎng)絡服務器也是被應用的,否則,gevent網(wǎng)絡服務器將會被啟用。如果eventlet和gevent都沒有被安裝,那么將會使用Werkzeug開發(fā)網(wǎng)絡服務器。
在flask 0.11中被引入的可點擊命令行界面也是被支持的。這個擴展提供了一個新版的flask run命令,適合啟動一個Socket.IO服務器。用法示例:
FLASK_APP = my_app flask run
這個應用只能為那種連接到客戶端的頁面服務,并且客戶端還需引用Socket.IO庫并且建立一個連接:
<script type="text/javascript" scr="http://cdn.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(location.protocol + '//' + document.domain + ':' + location.port);
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
</script>
4.接收消息
在使用SocketIO的時候,消息將被作為活動(event)的兩端接收。在客戶端使用JavaScript回叫信號。使用Flask-SocketIO服務器,需要為這些活動注冊處理器(handler),類似于視圖函數(shù)怎樣處理路由。
下面的例子是為一個未命名的活動創(chuàng)建了一個服務端的活動處理器(event handler):
@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))
最靈活的一種活動使用了自定義的活動名稱。這些活動的消息數(shù)據(jù)類型可以是字符串,字節(jié),整型,或者JSON:
@socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json))
自定義名稱的活動可以支持多參數(shù):
@socketio.on('my event')
def handle_my_sustom_event(arg1, arg2, arg3):
print('received args: ' + arg1 + arg2 + arg3)
命名活動是極度復雜的,在其消除了額外的元數(shù)據(jù)(metadata)來描述消息類型的時候。
Flask-SocketIO同樣支持命名空間(namespace),這個功能允許客戶端在一個相同的物理socket上多路復用幾個獨立的連接:
@scoketio.on('my event', namespace='/test')
def handle_my_custom_namespace_event(json):
print('received json: ' + str(json))
當一個命名空間沒有具體指出,一個全局的命名空間'/'將會被啟用
有時,裝飾器的語法并不方便,on_event()方法可以作為替代
def my_function_handler(data):
pass
socketio.on_event('my event', my_function_handler, namespace='/test')
客戶端要求一個確認回復,來確認消息的接收。任何一個從處理函數(shù)(handler function)中返回的值都會在回調(diào)函數(shù)中作為一個參數(shù)返回給客戶端。
@socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json))
return('one', 2)
在上面的例子中,客戶端回調(diào)函數(shù)將會回調(diào)兩個參數(shù),one和2。如果處理函數(shù)沒有返回值,這個客戶端回調(diào)函數(shù)將以沒有參數(shù)的情況返回。
5.發(fā)送消息
之前章節(jié)定義的SocketIO活動處理函數(shù)可以憑借send()函數(shù)和emit()函數(shù)來連接客戶端
接下來的例子是將接收到的消息退回到發(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)
@socket.on('my event')
def handle_my_custom_event(json):
emit('my response', json)
注釋一下,send()和emit()是怎樣用在已命名和未命名的活動上的
當運作在有命名空間的活動中時,send()和emit()默認用在接下來的消息中。不同的命名空間可以被具體化到可選擇的可選擇的命名空間參數(shù)上:
@socketio.on('message')
def handle_message(message):
send(message, namespace='/chat')
@socketio.on('message')
def handle_my_sustom_event(json):
emit('my response', json, namespace='/chat')
為了實現(xiàn)發(fā)送一個多參數(shù)的活動,發(fā)送一個元組:
def ack():
print('message was received!')
@socketio.on('my event')
def handle_my_custom_event(json):
emit('my response', json, callback=ack)
使用回調(diào)時,JavaScript客戶端使用回調(diào)函數(shù)在接收到的信息時回調(diào)。在客戶端應用啟用回調(diào)函數(shù)時,服務器會啟用服務端相匹配的函數(shù)去響應。如果客戶端沒有回調(diào)任何值,這些將會作為服務端的響應被提供。
客戶端的應用同樣要求一個來自服務端的確認信息。如果服務端想為一次響應提供一個參數(shù),它必須要在活動處理函數(shù)中被返回。
@socketio.on('my event')
def handle_my_custom_event(json):
# ... handle the event
return 'foo', 'bar', 123 # client callback will receive these 3 arguments
6.廣播
SocketIO另外一個非常有用的特性就是廣播消息。Flask-SocketIO中,只要將broadcast = True這個可選參數(shù)加到send()和emit()中即可:
@socketio.on('my event')
def handle_my_custom_event(data):
emit('my response', data, broadcast=True)
當一個消息以廣播選項被開啟的情況下被發(fā)出的時候,連接到這個命名空間的所有客戶端都會收到這個消息。注意:廣播的消息將不會被回調(diào)。
所有的例子表明,直到這個節(jié)點服務器才回復客戶端發(fā)出的這個活動。但是另外的應用中,服務器需要成為消息的發(fā)起者。對于起源于服務器的活動而言,這個有利于發(fā)送通知到客戶端,比如在后臺線程中。socketio.send()和socketio.emit()方法可以用來對所有的連接進行廣播。
def some_function():
socketio.emit('some event', {'data': 42})
注意:通過對send()和emit()的上下文的感知,socketio.send()和socketio.emit()不是相同的函數(shù)。同樣需要注意的是:以上的用法是沒有客戶端內(nèi)容,所以假定broadcast=True,并且需要被具體化。
7.房間
在許多應用中,有必要將用戶劃分為可以一并處理的幾個子集。最好的例子是,一個包含多個房間的聊天應用,當用戶收到他所在的房間的消息,而不會收到其他人所在房間的消息。Flask-SocketIO支持通過join_room()和leave_room()函數(shù)來支持房間的概念:
from flask_socketio import join_room, leave_room
@socketio.on('join')
def on_join(data):
uername=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ù)接收room作為一個參數(shù),將消息廣播到所有在給定房間里的客戶端。
所有連接(到服務器)的客戶端都被分配到一個房間,并且以連接的會話編號(session ID)命名這個房間的名稱,這個會話編號由request.sid獲得。一個既定的客戶端可以加入任何一個房間,這個房間的名稱可以是任何名稱。當一個客戶端終止(與服務器的)連接,它將會從原來所在的房間里除名。這兩個上下文無關的函數(shù)socketio.send()和socketio.emit()也會接受參數(shù)room,把消息廣播到這個房間里的所有客戶端。
一旦所有的客戶端被分配到一個自己的房間,為了將消息發(fā)送到一個唯一的客戶端,會話編號可以作為參數(shù)room的值。
8.連接活動
Flask-SocketIO同樣支持連接和斷開的活動。接下來的例子將會展示怎樣為他們注冊一個處理函數(shù):
@socketio.on('connect', namespace='/chat')
def test_connect():
emit('my response', {'data': 'Connected'})
@socketio.on('disconnected', namespace='/chat')
def test_disconnected():
print('Client disconnected')
連接活動處理處理函數(shù)可以選擇性地返回一個False去拒絕這個連接。這是為了在這一點上進行身份認證。
注意:連接和斷開活動可以在各自使用的命名空間內(nèi)獨立地發(fā)送。
9.基于類的命名空間
以上描述的作為基于裝飾器的活動處理函數(shù)的替代,屬于命名空間的活動處理函數(shù)可以被創(chuàng)造成一個類的方法。Flask_socketio.Namespace提供了一個基于類的方法來創(chuàng)造命名空間。
from flask_socketio import Namespace, emit
class MyCustomNamespace(Namespace):
def on_conect():
pass
def on_disconnect():
pass
def on_my_event(data):
emit('my_response', data)
socket.on_namespace(MyCustomNamespace('/test'))
使用一個基于類的命名空間時,所有服務器接收到的活動將會被分配到一個方法,該方法的活動名稱是以on_為前綴的活動。例如,名稱為my_event的活動,將會由on_my_event函數(shù)來處理。如果一個接收到的活動在命名空間類中沒有與之相匹配的處理方法。這個活動將會被忽略。所有在基于類的命名空間內(nèi)的活動必須使用具有合法的方法名稱的單詞。
作為一個定義基于類的命名空間的簡便方法,這個命名空間實例包括了幾個版本的flask_socketio.SocketIO類,并且他們默認的命名空間參數(shù)并沒有給出。
如果一個活動同時具有在基于類的命名空間里的處理函數(shù)和基于裝飾器的處理函數(shù),只有裝飾器函數(shù)會被調(diào)用。
10.錯誤處理
Flask-SocketIO也可以處理異常:
@socketio.on_error() # handles the dafault 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
錯誤處理函數(shù)將異常對象作為一個參數(shù)
這個消息和數(shù)據(jù)參數(shù)作為當前的請求將會被察覺request.event變量察覺,這有利于外部活動處理函數(shù)的錯誤日志和調(diào)試
from flask import request
@socketio.on("my error event")
def on_my_event(data):
raise RuntimeError()
@socket.on_error_default
def default_error_handler(e):
print(request.event["message"]) # "my error event"
print(request.event["args"]) # (data,)
11.訪問flask上下文全局變量
SocketIO活動處理不同于路由處理,在于它引入了許多容易混淆的東西,圍繞著SocketIO什么可以做,什么不可以做。最主要的區(qū)別就是SocketIO活動發(fā)生在單個長期運行在上下文的請求之中。
盡管有所不同,F(xiàn)lask-SocketIO將環(huán)境改造成類似于常規(guī)HTTP請求,使SocketIO活動處理更加輕松。接下來的列表描述了什么將會生效,什么不會。
在活動處理函數(shù)之前推送應用的上下文使得
current_app和g可以在處理函數(shù)中可用。這個請求的上下文同樣在回調(diào)處理函數(shù)前被啟用,也使
request和session可用。但是注意到WebSocket活動與之并沒有獨立的聯(lián)系,因此為連接期間分派的所有事件推送啟動連接的請求上下文。request上下文全局變量隨一個sid成員增加,這個成員是為了給連接一個獨特的會話編號(session ID)。這個值在客戶端剛剛添加的時候,就被最初的房間使用了。request上下文全局變量由包含了當前處理函數(shù)的命名空間和活動參數(shù)的
argument和event來增加。這個活動成員是一個包含了message和args鍵值的字典。session上下文全局變量表現(xiàn)得和通常的請求不一樣。在連接開始建立的時候,就會復制一份用戶的會話在這個連接上下文中給處理器調(diào)用。如果SocketIO處理器修改了這個會話,這個修改過的會話就會為未來的SocketIO處理器保留,但是正常的HTTP路由處理器不會察覺這些改變。有效率的是,當SocketIO處理器改變這個會話的時候,會話就會為這些處理器創(chuàng)建一個“分支”(fork)。這個限制的技術(shù)原因是用戶的會話cookie必須要發(fā)送到客戶端,這需要HTTP請求和應答而不是SocketIO連接。在使用服務端的會話時,比如那些由Flask-Session或者Flask-KVSession擴展提供的會話,在HTTP處理器中的會話改變也可以在SocketIO處理器中可見,只要這個會話不是在SocketIO處理器中修改的。before_request和after_request鉤子不會調(diào)用SocketIO活動處理器。SocketIO處理器可以使用自定義的裝飾器,但是大多數(shù)Flask裝飾器并不適于SocketIO處理器,考慮到SocketIO連接中沒有Response對象這一概念。
12.身份認證
應用的共同需要就是驗證他們用戶的身份。自從SocketIO沒有使用HTTP請求和應答,傳統(tǒng)的基于網(wǎng)頁表單和HTTP請求的機制不能用于SocketIO連接。如果需要的話,應用可以實施自定義的登陸表單,當用戶按下提交按鈕時,它利用一個SocketIO消息將證書發(fā)送到服務器。
然而,在大多數(shù)情況下,在SocketIO連接建立之前使用傳統(tǒng)的身份驗證方式會更加方便,用戶的身份信息可以被記錄下來作為用戶會話或者cookie,之后在SocketIO連接建立起來的時候,這些信息也可以被SocketIO活動處理器得到。
13.使用Flask-SocketIO的Flask-Login模塊
Flask-SocketIO可以獲得由Flask-Login維護的登陸信息。在一個正常的Flask-Login身份認證被使用的時候,login_user()函數(shù)將會被調(diào)用去記錄用戶會話中的用戶,任何SocketIO連接都可以得到current_user上下文變量:
@socketio.on('connect')
def connect_handler():
if current_user.is_authenticated:
emit('my response',
{'message':'{0} has joined'.format(current_user.name)},
broadcast=True
)
else:
return False # not allowed here
注意到login_required裝飾器不能和SocketIO活動處理器一起使用,但是一個自定義的關閉連接無身份認證的裝飾器可以按下面的方式創(chuàng)建:
import functools
from flask import request
from flask_login import current_user
from flask_socketio import disconnect
def authenticated_only(f):
@functools.wraps(f):
def wraped(*args, **kwargs):
if not current_user.is_authenticated:
disconnect()
else:
return f(*args, **kwargs)
return wraped
@socketio.on('my event')
@authenticated_only
def handle_my_custom_event(data)
emit('my response',
{'message': '{0} has joined'.format(current_user.name)},
broadcast=True
)
14.部署
我們有多種部署Flask-SocketIO服務器的選擇,從最簡單到瘋狂地復雜。在這一章節(jié)里,我們將會
介紹最普遍的選擇。
嵌入式服務器
最簡單的策略是安裝eventlet或者gevent,并且就像前面章節(jié)的例子中引用socketio.run(app)的方式來啟動網(wǎng)絡服務器。這個將會在eventlet或者gevent網(wǎng)絡服務器中啟動這個應用,被嵌入的網(wǎng)絡服務器是哪一個取決于是安裝的是哪一個。
注意到socketio.run(app)運行在eventlet或gevent已安裝上的生產(chǎn)服務器中。如果它們中沒有一個被安裝,那么這個應用運行在Flask開發(fā)服務器中,這并不適于生產(chǎn)環(huán)境的使用。
不幸的是,這個選擇并不能在帶有uWSGI的gevent服務器上使用,你可以在下面獲取更多有關這個選項的信息。
Gunicorn網(wǎng)絡服務器
作為socketio.run(app)替代方法的就是使用gunicorn作為網(wǎng)絡服務器,工作在eventlet或gevent下。這個選擇下,除了gunicorn要安裝,eventlet或者gevent也是不可缺少的。這個條命令將會啟動這個基于gunicorn的eventlet服務器:
gunnicorn --worker--class eventlet -w 1 module:app
如果你更傾向于使用gevent,啟動服務器的命令如下:
gunicorn -k gevent -w 1 module:app
當使用gunicorn作為gevent的工作站并且websocket支持也被提供的時候,上述命令就必須被改成選擇一個自定義的gevent網(wǎng)絡服務器來支持websocket協(xié)議。修改后的命令如下:
gunicorn -k geventwebsocket.gunicorn.worker.GeventWebSocketWorker -w 1 module:app
在上述這些命令中,module是python模塊或者是定義了應用實例的包,此外,app是應用實例本身。
Gunicorn 18.0版本是被推薦和Flask-SocketIO搭配的版本。19.x版本已知在帶有WebSocket的一些特定部署場景下存在不兼容的情況。
gunicorn由于使用了有限的負載均衡算法,不可能在使用這種網(wǎng)絡服務器時調(diào)用兩個以上工作進程因為這個原因,上面的所有例子中都包含了-w 1的可選參數(shù)。
15.uWSGI網(wǎng)絡服務器
當使用uWSGI網(wǎng)絡服務器搭配geventd的時候,Socket.IO服務器的時候,可以利用uWSGI原生的WebSocket支持。
一個配置和運用uWSGI服務器完整的解釋超出了本文的論述范圍。uWSGI服務器確實是一個比較復雜的,它提供了大量而又詳盡的設置選項。它必須使用Websocket和SSL編譯才能支持WebSocket傳輸。作為介紹,下面的命令啟動了一個uWSGI服務器作為范例,這個應用app.py運行在端口5000:
uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app
16.使用nginx作為反向代理服務器
使用nginx作為前端的反向代理將請求傳遞給應用是可行的。然而,只有nginx 1.4版本以上才支持WebSocket協(xié)議。下面是nginx代理HTTP和WebSocket請求的一個最基本的配置:
server {
listen 80;
server_name _;
location / {
include proxy_params;
proxy_pass http://127.0.0.1:5000;
}
location /socket.io {
include proxy_params;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://127.0.0.1:5000/socket.io;
}
}
下面的例子增加了對負載平衡多個服務器的支持:
upstream socketio_nodes {
ip_hash;
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
# to scale the app, just add more nodes here!
}
server {
listen 80;
server_name _;
location / {
include proxy_params;
proxy_pass http://127.0.0.1:5000;
}
location /socket.io {
include proxy_params;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://socketio_nodes/socket.io;
}
}
雖然上面的例子可以作為最初的配置工作,要知道生產(chǎn)環(huán)境安裝的nginx需要一個完整的配置,包括部署的其它方面,例如服務于靜態(tài)文件的assert和SSL支持。
17.使用多個工作站
Flask-SocketIO從2.0版本起帶有負載均衡器支持多個工作站。部署多個工作站給了使用Flask-SocketIO的應用程序有能力在多進程和多主機之間傳播客戶端鏈接,這種方式的擴展支持極大規(guī)模的并發(fā)客戶端。
使用多個Flask-SocketIO工作站需要兩個依賴:
- 負載均衡器必須要配置成總是將所有的HTTP請求從一個給定的客戶端轉(zhuǎn)發(fā)到同樣的工作站中。這有時會作為
sticky session被提及。對于nginx,使用這個ip_bash指示來達到上述要求。Gunicorn不能用于多工作站,因為它的負載均衡算法并不支持粘性會話(sticky session)。 - 一旦每個服務器只擁有一個客戶端連接,在Redis、RabbitMQ等例子中,消息隊列將會被使用,來協(xié)調(diào)復雜的操作,比如:廣播和房間。
當使用消息隊列的時候,有許多額外的依賴包需要被安裝:
- 對于Redis,redis包必須被安裝(
pip install redis)。 - 對于RabbitMQ,kombu包必須要被安裝(
pip install kombu)。 - 對于其它Kombu支持的消息隊列,Kombu documentation里可以找到需要的依賴。
- 如果使用了eventlet或者gevent,那么通常需要使用猴子(Monkey)修補Python標準庫來強制消息隊列包使用協(xié)同友好的函數(shù)和類。
為了啟動多個Flask-SocketIO服務器,你必須首先確保消息隊列服務正在運行。為了開啟一個Socket.IO服務器,使他連接到一個消息隊列,需要添加參數(shù)message_queue到構(gòu)造函數(shù)SockIO:
socketio=SocketIO(app,message_queue='redis://')
參數(shù)message_queue的值就是隊列服務所使用的連接URL。對于一個運行在同一個作為服務器的主機中的Redis隊列來說,可以使用redis://這樣的URL。同樣,對于一個默認的RabbitMQ隊列可以使用amqp://開頭的URL。Kombu包有一個文檔章節(jié)闡述了對于所有支持隊列的URL格式。
18.外部進程消息
對于許多類型的應用,從非服務端創(chuàng)建會話活動很有必要,例如一個Celery工作站。如果SocketIO服務器并沒有按照前面章節(jié)那樣配置監(jiān)聽隊列,那么所有其它的進程可以像服務器那樣創(chuàng)建它自己的SocketIO實例來創(chuàng)建消息活動。
例如,一個運行在eventlet網(wǎng)絡服務器上的應用,使用了Redis消息隊列,下面的Python腳本將向所有的客戶端廣播一個消息活動。
socketio=SocketIO(message_queue='redis://')
socketio.emit('my event', {'data': 'foo'}, namespace='/test')
當使用這種方法引用SocketIO實例,F(xiàn)lask應用實例將不會傳遞到構(gòu)造函數(shù)。
當SocketIO通過消息隊列使用參數(shù)channel來選擇一個具體channel的對話。當很多獨立的SocketIO服務公用一個隊列的時候,使用一個自定義的channel名稱將是很有必要的。
Flask-SocketIO并沒有在使用eventlet或者gevent時應用猴子(monkey)來修補。但是當使用消息隊列的時候,如果Python標準庫沒有使用猴子來修補,那么消息隊列服務的Python包很可能會掛起。
很重要的一點是:外部進程想連接到SocketIO服務器并不需要像主服務器那樣使用eventlet或者gevent。使一個服務器使用了協(xié)同框架,外部進程不是一個阻力。例如,Celery工作站并不需要配置使用eventlet或者gevent,是因為主服務器已經(jīng)有了。但是,如果你的外部進程因為某種原因
使用了協(xié)同框架,那么monkey修復就很可能是需要的,那么消息隊列就可以獲得協(xié)同友好的函數(shù)和類。
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 版本開始,支持新發(fā)布的Socket.IO和Engin.IO。1.0版本以前的Socket.IO將不再被支持。Swift和C++官方的Socket.IO客戶端也被支持。
- 0.x 版本依賴gevent,gevent-socketio和gevent-websocket.1.0 版本以后將不再使用。在Flask開發(fā)的網(wǎng)絡服務器中,gevent是三種后端網(wǎng)絡服務器選擇之一,另外兩個是eventlet和其它常規(guī)多線程WSGI服務器。
- Socket.IO服務器選項在 1.0 版本中也有所改變。它們可以由SocketIO構(gòu)造函數(shù)來提供,或者由
run()調(diào)用。這些選項在使用前在這兩者中被合并。 - 0.x 版本暴露了gevent-socketio在連接中作為
request.namespace。在 1.0 版本中它不再被使用。這個請求對象定義了request.namespace作為待處理的命令空間。并且增加了request.aid,為客戶端連接定義了一個獨有的會話ID,request.event包含了活動名稱和參數(shù)。 - 為了獲得房間列表,0.x版本需要應用使用私有g(shù)event-socketio結(jié)構(gòu),包含
request.namespace.rooms表達式。這是在 1.0 版本中將不再出現(xiàn),因為它包含了一個合適的room()函數(shù)。 - 這個推薦的“把戲(trick)”發(fā)送消息到一個獨立的客戶端將消息分發(fā)到每個客戶端所在的獨立的房間內(nèi),這個地址消息對應著目的房間(desired room)。這個特性在 1.0 版本中被正式化了,當客戶端連接到服務器時,它會立即自動地被分配到一個特定的房間內(nèi)。
- 全局命名空間的
connect活動在 1.0 版本之前并沒有被觸發(fā)。這bug已經(jīng)被修復了并且按照預期觸發(fā)。 - 在 1.0 版本增加了對客戶端的回調(diào)函數(shù)的支持。
為了升級到新的Flask-SocketIO版本,你需要升級你的Socket.IO客戶端到兼容Socket.IO 1.0 協(xié)議。對于Javascript客戶端,1.3.x和1.4.x版本經(jīng)過充分地測試,發(fā)現(xiàn)是兼容的。
在服務端,有一些要點是要被考慮到的:
- 如果你想繼續(xù)使用gevent,那么gevent-socketio需要從你的虛擬環(huán)境中卸載,因為這個包將不再需要并且可能會與它的替代——python-socketio相沖突。
- 如果你想輕微地提高性能和穩(wěn)定性,那么推薦你轉(zhuǎn)而使用eventlet。為了做到這一點,需要卸載gevent、gevent-socketio和gevent-websocket,然后安裝eventlet。
- 如果你的應用使用了猴子修復了并轉(zhuǎn)向了eventlet,需要調(diào)用
eventlet.monkey_patch()來代替gevent中的monkey.patch_all()。此外,任何對gevent的調(diào)用必須被同等條件下的對eventlet調(diào)用替代。 - 任何使用
request.namespace需要被直接調(diào)用Flask-SocketIO函數(shù)替代。例如,request.namespace.rooms要用rooms()函數(shù)替換。 - 任何使用內(nèi)置的gevent-socketio的對象都必須被去除,當這個包不再是所需的依賴的時候。