本文來源:https://www.jianshu.com/p/d81397edd2b1javascript
websocket是html5中實現了服務端和客戶端進行雙向文本或二進制數據通訊的一種新協議,其實已經低於HTTP協議自己和HTTP本質上沒有什麼關係了。不過形式上二者仍是有想象之處。所以websocket的鏈接地址是長這樣的:ws://localhost:8080。能夠看到,協議修飾符不是http了。html
另外,websocket在鏈接創建階段是經過HTTP的握手方式進行的,這能夠看作是爲了兼容瀏覽器或者使用一些現成的功能來實現,這樣一種捷徑。當鏈接創建以後,客戶端和服務端之間就再也不進行HTTP通訊了,全部信息交互都由websocket接管。前端
從資源佔用的角度上來講,其實websocket比ajax佔用的資源更多,但它真正實現了全雙工通訊這一點仍是很理想的,意味着不管是前端仍是後臺的信息交互程序編寫都會變得更加方便。因爲採用了新的協議,因此咱們也須要適當地改造下先後臺的程序html5
對app進行一些路由設置 對socketio進行一些監聽設置
socketio的監聽設置,這纔是真正關係到先後端websocket通訊過程的。
@socketio.on('request_for_response',namespace='/testnamespace') def give_response(data): value = data.get('param') #進行一些對value的處理或者其餘操做,在此期間能夠隨時會調用emit方法向前臺發送消息 emit('response',{'code':'200','msg':'start to process...'}) time.sleep(5) emit('response',{'code':'200','msg':'processed'})
socketio也用了和app.route相似的裝飾器的形式進行監聽設置。主要參數中有namespace這一項,也就是這項指定了這個監聽的範圍。java
在前端,只有註冊在testnamespace上的socket,emit向request_for_response的消息纔會被這個函數接受並處理。python
處理函數自帶一個參數用來接收前端emit來消息中的那個object,在處理函數中能夠對其解析處理。隨後後端向前端發送了start to process的消息。也使用了emit這個方法,而後指明瞭監聽是response。也就是說前端on在response上的監聽處理函數會處理這個消息(固然仍是在testnamespace的框架內)。發出消息後後端不會被阻塞而是繼續向下執行,在處理了5秒鐘以後發出告終束處理的消息,前端天然隔了五秒以後就獲得了這個消息了。web
socket監聽響應函數自己不須要返回什麼值,只須要在處理過程當中適當的位置emit出消息便可。ajax
關於send和emit方法:編程
網上其餘一些教程中會提到send方法來取代emit方法的位置(不管是前端仍是後端),其實send方法就是把上文中的'request_for_response','response'這兩個標識都默認成'message'。如此在寫的時候就不用寫事件名,直接寫要傳遞的參數便可。反過來看,用emit方法其實是作了一個自定義事件的工做,能夠說更加靈活多變一點json
爲flask應用提供了一個客戶端與服務器之間低延遲的雙向通訊。客戶端應用能夠用Javascript,C++,Java,Swift或者其它任意的編程語言的socketio官方庫的客戶端去和服務端建立一個永久的鏈接。
這個異步的服務的包的依賴能夠有三個選擇:
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協議。
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()風格的初始化也是支持的。
注意網絡服務器的啓動。
函數socketio.run()封裝了網絡服務器的啓動部分,而且代替了flask開發服務器的標準啓動語句app.run()。當應用在debug模式下,Werkzeug開發服務器也是在socketio.run()中被合理地應用和配置。若是可用的話,在生產模式下eventlet網絡服務器也是被應用的,不然,gevent網絡服務器將會被啓用。若是eventlet和gevent都沒有被安裝,那麼將會使用Werkzeug開發網絡服務器。
在flask 0.11中被引入的可點擊命令行界面也是被支持的。這個擴展提供了一個新版的flask run命令,適合啓動一個Socket.IO服務器。用法示例:
FLASK_APP = my_app flask run
這個應用只能爲那種鏈接到客戶端的頁面服務,而且客戶端還需引用Socket.IO庫而且創建一個鏈接:
<script type="text/javascript" scr="//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>
在使用SocketIO的時候,消息將被做爲活動(event)的兩端接收。
在客戶端使用JavaScript回叫信號。
使用Flask-SocketIO服務器,須要爲這些活動註冊處理器(handler),相似於視圖函數怎樣處理路由。
建立了一個服務端的活動處理器(event handler):使用了字符串消息/json。
@socketio.on('message')這些活動的消息數據類型能夠是字符串,字節,整型,或者JSON:
@socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json))
自定義名稱的活動能夠支持多參數:
命名活動是極度複雜的,在其消除了額外的元數據(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')
客戶端要求一個確認回覆,來確認消息的接收。任何一個從處理函數(handler function)中返回的值都會在回調函數中做爲一個參數返回給客戶端。
@socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json))
return('one', 2)
在上面的例子中,客戶端回調函數將會回調兩個參數,one和2。若是處理函數沒有返回值,這個客戶端回調函數將以沒有參數的狀況返回。
以前章節定義的SocketIO活動處理函數,能夠憑藉send()函數和emit()函數來主動鏈接客戶端。
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()默認用在接下來的消息中。不一樣的命名空間能夠被具體化到可選擇的可選擇的命名空間參數上
@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')
爲了實現發送一個多參數的活動,發送一個元組:
使用回調時,JavaScript客戶端使用回調函數在接收到的信息時回調。在客戶端應用啓用回調函數時,服務器會啓用服務端相匹配的函數去響應。若是客戶端沒有回調任何值,這些將會做爲服務端的響應被提供。
也就是說不管客戶端是否接收到,服務端必定會執行回調函數
def ack():
print('message was received!')
@socketio.on('my event')
def handle_my_custom_event(json):
emit('my response', json, callback=ack)
客戶端的應用一樣要求一個來自服務端的確認信息。若是服務端想爲一次響應提供一個參數,它必需要在活動處理函數中被返回。
@socketio.on('my event')
def handle_my_custom_event(json):
# ... handle the event
return 'foo', 'bar', 123 # client callback will receive these 3 arguments
SocketIO另一個很是有用的特性就是廣播消息。Flask-SocketIO中,只要將broadcast = True這個可選參數加到send()和emit()中便可:
@socketio.on('my event')
def handle_my_custom_event(data):
emit('my response', data, broadcast=True)
當一個消息以廣播選項被開啓的狀況下被髮出的時候,鏈接到這個命名空間的全部客戶端都會收到這個消息。注意:廣播的消息將不會被回調。
全部的例子代表,直到這個節點服務器纔回復客戶端發出的這個活動。可是另外的應用中,服務器須要成爲消息的發起者。對於起源於服務器的活動而言,這個有利於發送通知到客戶端,好比在後臺線程中。socketio.send()和socketio.emit()方法能夠用來對全部的鏈接進行廣播。
def some_function():
socketio.emit('some event', {'data': 42})
注意:經過對send()和emit()的上下文的感知,socketio.send()和socketio.emit()不是相同的函數。一樣須要注意的是:以上的用法是沒有客戶端內容,因此假定broadcast=True,而且須要被具體化。