主要是利用了redis的pub/sub功能,這種方案也沒有什麼問題,但是整體的效能瓶頸受redis的影響。
最近接觸到socketio,發現這種需求可以使用它來實現,但是網上查找了一些資料,在python的使用中,主要還是flask-socketio與原生的應用上,由於目前專案使用Tornado來構建,所以用了幾天時間將socketio與Tornado的融合使用。
本教程會分幾篇來介紹,主要以下幾個章節
socketio介紹與腳手架的搭建定義訊息處理事件名稱空間的使用訊息的釋出與響應room的使用前端vue中使用socketio與後端通訊一、socketio 簡介
Socket.IO 支援實時、雙向和基於事件的通訊。它能夠在任何平臺、瀏覽器或裝置上執行,可靠性和速度同樣出色。它相容websocket,在不支援websocket的裝置上,會使用更加低層的長連結協議
Socket.io是一個WebSocket庫,包括了客戶端的js和伺服器端的nodejs,它的目標是構建可以在不同瀏覽器和移動裝置上使用的實時應用。它會自動根據瀏覽器從WebSocket、AJAX長輪詢、Iframe流等等各種方式中選擇最佳的方式來實現網路實時應用,非常方便和人性化,而且支援的瀏覽器最低達IE5.5
socket.io特點
實時分析:將資料推送到客戶端,這些客戶端會被表示為實時計數器,圖表或日誌客戶。實時通訊和聊天:只需幾行程式碼便可寫成一個Socket.IO的”Hello,World”聊天應用。二進位制流傳輸:從1.0版本開始,Socket.IO支援任何形式的二進位制檔案傳輸,例如:圖片,影片,音訊等。文件合併:允許多個使用者同時編輯一個文件,並且能夠看到每個使用者做出的修改。我這個專案主要利用其二進位制流的傳輸。
二、python-socketio 簡介
最初socketio的後臺使用nodejs,後來又有了java,c++,python等後端的應用。
python的後端庫地址 https://github.com/miguelgrinberg/python-socketio
但是注意,不同的版本並不相容,參考下表
JavaScript Socket.IO version |
Socket.IO protocol revision |
Engine.IO protocol revision |
python-socketio version |
0.9.x |
1, 2 |
1, 2 |
Not supported |
1.x and 2.x |
3, 4 |
3 |
4.x |
3.x and 4.x |
5 |
4 |
5.x |
比如python-socketio用的是5.x的,那前端應該使用3.x或者4.x的socket.io 庫,不同的版本不相容。
另外,我演示時使用python版本是3.7.8的,我試過在3.5上的python中安裝python-socketio時會安裝失敗,在安裝一個依賴庫didict時會失敗
我的環境如下
python:3.7.8
python-socketio: 5.3.0
tornado: 6.0.4
windows x64
三、Tornado 的搭建
首先先使用Tornado構建一個簡單的基礎web應用
#-*- coding:utf-8 -*-# author:Yang# datetime:2021/5/19 23:26# software: PyCharmimport osimport tornado.httpserverimport tornado.webimport tornado.genimport tornado.concurrentimport tornado.autoreloadfrom tornado.platform.asyncio import AsyncIOMainLoopimport asyncioimport tracebackclass Index(tornado.web.RequestHandler): def get(self): self.write("hello world")class NotFount(tornado.web.RequestHandler): def get(self): self.write("404 not found")def make_app(**kwargs): settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), debug=True, ) return tornado.web.Application([ (r'/', Index), (r'.*', NotFount), ], **settings, **kwargs)if __name__ == '__main__': try: AsyncIOMainLoop().install() app = make_app() app.listen(8080) loop = asyncio.get_event_loop() processPoolNum = 2 try: loop.run_forever() except KeyboardInterrupt: for task in asyncio.Task.all_tasks(): task.cancel() loop.stop() loop.run_forever() finally: loop.close() except: print(traceback.print_exc()) finally: pass
上面的程式碼即可以搭建一個基礎的Tornado web服務, 之後的程式碼就在這個基礎上做新增。
四、新增python-socketio
python-socketio 分為server端與client端,在server端安裝命令為
pip install python-socketio
先初始化socketio.AsyncServer 類的例項
import socketiosio = socketio.AsyncServer(async_mode='tornado')
以下將python-socketio 簡稱為socketio
socketio server 有兩個版本,一個同步的Server(),一個非同步的AsyncServer() ,功能是樣的,只是非同步的server可以構建在asyncio環境中,由於我的Tornado應用也是使用asyncio,所以這裡我使用了非同步的server。
之後建立一個路由, 修改make_app函式
def make_app(**kwargs): settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), debug=True, ) return tornado.web.Application([ (r'/', Index), (r"/socket.io/", socketio.get_tornado_handler(sio)), (r'.*', NotFount), ], **settings, **kwargs)
注意這裡只能新增/socket.io/ 的路由,這裡先這麼寫,之後在介紹client端面時再說明為什麼。
還有一點要注意的,這個/socket.io/ 要定義在.* 路由之前,否則也會命中NotFount。
五、定義事件處理函式
當我們使用websocket時,我們會定義幾個常用的方法,如connect 為成功連線上以後呼叫,disconnect 為斷開連線時的操作。
對於服務端,同樣我們也可以定義這些函式,我們稱為事件(event), 定義事件有兩種方式
1、使用sio.event裝飾器@sio.event
def my_event(sid, data): print("my_event get an message {} from {}".format(data, sid))
2、使用sio.on方法@sio.on("my event")
def event_special(sid, data): print('special event get an message {} from {}'.format(data, sid))
兩種方法定義的事件相同,使用第一個種方法,函式名即為事件名,這裡就為my_event, 由於函式名不能有特殊的字元有空格,但是第二種方法,可以將事件名定義在裝飾器引數中,如這裡的my event 事件, 它中間有個空格,所以看需求,需要定義事件的名字中有特殊字元的需要使用on方法。
connect 和disconnect 事件是特殊的事件,在客戶端進行連線和斷開連線時自動呼叫
@sio.eventdef connect(sid, environ, auth): print('connect ', sid)@sio.eventdef disconnect(sid): print('disconnect ', sid)
注意在自定義事件時,需要兩個引數,一個是sid, 這個是客戶端標識,一個是data, 這個是客戶端傳送過來的資料。
六、使用客戶端進行連線
客戶端的安裝與服務端有所不同,使用pip install "python-socketio[client]" 安裝同步版本
使用pip install "python-socketio[asyncio_client]" 安裝非同步版本,我這裡使用同步版本的客戶端,如果你需要在asyncio中使用則需要安裝非同步版本
import socketiosio = socketio.Client()sio.connect('http://localhost:8080')
三行程式碼即可進行連線web服務,並與之建立長連線,這裡有一點要注意,我們在server端是建立了一個路由,
(r"/socket.io/", socketio.get_tornado_handler(sio))
但是這裡連線的時候卻不能將/socket.io/ 新增到connect的url中,只能寫到根url
檢視connect函式定義
def connect(self, url, headers={}, auth=None, transports=None, namespaces=None, socketio_path='socket.io', wait=True, wait_timeout=1):
這裡有一個socketio_path 引數,也正是這個引數,所以在定義server端的時候,要加上那麼一條路由,當然這個引數也可以自己定義,這裡為了簡單就不自定義了,只要知道這裡是可以自己定義的。
呼叫client,觀察server端的輸出,當有客戶端連線到服務端以後,服務端會自動觸發connect事件,這裡就執行了自定義的函式
@sio.eventdef connect(sid, environ, auth): print('connect ', sid)
列印輸出
connect VUke1-fDf8dYFAyEAAAB, 其中後面的為客戶端的sid, 當關閉客戶端時,又會觸發disconnect事件,輸出disconnect VUke1-fDf8dYFAyEAAAB。
七、定義客戶端事件
其實對於這種長連線的方式,客戶端與服務端的界線已經有些模糊了,服務端也可以向客戶端傳送資料請求,這裡的客戶端也就相關於服務端。
我們在客戶端定義一個connect事件, 用於連線上服務端以後執行的函式
@sio.eventdef connect(): print("I'm connected!")
這裡再次連線服務端,當連線成功以後,就會打印出 I'm connected!
八、客戶端傳送自定義事件
我們在服務端定義了兩個事件 my_event 和my event, 那麼客戶端如何傳送這兩個事件呢?
客戶端可以使用emit 函式
sio.connect('http://localhost:8080')sio.emit("my event", {"data": "hello server"})
這行程式碼即可向服務端傳送my event 事件,再將觀察服務端的輸出,
觸發了my event 函式
@sio.on("my event")def event_special(sid, data): print('special event get an message {} from {}'.format(data, sid))
得到輸出:
special event get an message {'data': 'hello server'} from kh-Ui9gCLG9b5jD4AAAF
九、服務端向客戶端傳送事件
客戶端可以向服務端傳送事件,服務端也可以向客戶端傳送事件,我們先在客戶端定義一個事件
@sio.on("client event")def client_event(data): print("get server message:{}".format(data))
修改服務端my event事件程式碼
@sio.on("my event")async def event_special(sid, data): print('special event get an message {} from {}'.format(data, sid)) await sio.emit("client event", "hello {}".format(sid))
當服務端接收到my event事件以後,再向客戶端傳送一個client event事件,注意這裡由於服務端使用的是非同步的,所以這裡要將函式改為async def, emit函式也需要使用await 。
再將呼叫客戶端進行連線,這時客戶端會得到如下輸出
I'm connected!get server message:hello tNp1Yb7OONZn2FwxAAAB
I'm connected! 是觸發了connect事件,get server message:hello tNp1Yb7OONZn2FwxAAAB 是觸發了client event 事件。
十、自動重連
當我們把服務端關掉以後,此時客戶端的指令碼並沒有退出,當再將啟動服務端的時候,客戶端可以自動連線上,這個也是該庫的方便之處,如果要自己寫長連線的話,還要考慮重連問題。
到此,已經掌握了socketio與Tornado的最基本的使用,可以相互發送訊息,之後的文章裡會介紹更加詳細名稱空間,與web前端的互動操作。
參考
python-socketio官方文件