服務端代碼
代碼clone到本地,搭好相應環境(怎么搭的這里就不介紹了,很好搭的哈)
一般庫首先查看main.py文件,debug模式開始運行
一開始就是沒接觸過的tornado.ioloop,有點偏底層,頭疼,還是加油干吧
為了理解的更深入些,先了解下ioloop的一些知識吧,在這之前先了解點預備知識
一、epoll
ioloop 的實現基於epoll,那么什么是epoll?epoll是Linux內核為處理大批量文件描述
符而作了改進的 poll。那什么是poll呢?首先,我們了解一下, socket 通信時的服務端,當它接受( accept )一個連接並建立通信后( connection )就進行通信,而此時我們並不知道連接的客戶端有沒有信息發完。 這時候有兩種選擇:
- 一直在這里等着直到收發數據結束;
-
每隔一定時間來看看這里有沒有數據;
第二種辦法要比第一種好一些,多個連接可以統一在一定時間內輪流看一遍里面有沒
有數據要讀寫,看上去可以處理多個連接了,這個方式就是 poll / select 的解決方案。 看起來似乎解決了問題,但實際上,隨着連接越來越多,輪詢所花費的
時間將越來越長,而服務器連接的 socket大多不是活躍的,所以輪詢所花費的大部分
時間將是無用的。為了解決這個問題, epoll 被創造出來,它的概念和 poll 類似,不過每次輪詢時,他只會把有數據活躍的socket挑出來輪詢,這樣在有大量連接時輪詢就節省了大量時間。
而對於epoll的操作,其實也很簡單,只要 4 個 API 就可以完全操作它。
epoll_create
用來創建一個 epoll 描述符( 就是創建了一個 epoll )
epoll_ctl
操作 epoll 中的 event;可用參數有:
參數 | 含義 |
---|---|
EPOLL_CTL_ADD | 添加一個新的epoll事件 |
EPOLL_CTL_DEL | 刪除一個epoll事件 |
EPOLL_CTL_MOD | 改變一個事件的監聽方式 |
而事件的監聽方式有七種,而我們只需要關心其中的三種:
宏定義 | 含義 |
---|---|
EPOLLIN | 緩沖區滿,有數據可讀 |
EPOLLOUT | 緩沖區空,可寫數據 |
EPOLLERR | 發生錯誤 |
epoll_wait
就是讓 epoll 開始工作,里面有個參數 timeout,當設置為非 0 正整數時,會監聽(阻塞) timeout 秒;設置為 0 時立即返回,設置為 -1 時一直監聽。
在監聽時有數據活躍的連接時其返回活躍的文件句柄列表(此處為 socket 文件句柄)
close
關閉 epoll
現在簡單了解了 epoll 后,我們就可以來了解ioloop代碼
二 ioloop.py文件
ioloop是tornado的關鍵,是他的最底層。
ioloop就是對I/O多路復用的封裝,它實現了一個單例,將這個單例保存在IOLoop._instance中
主要有兩個要點。一個是configurable機制,一個就是epoll循環
2.1 創建IOLoop實例
來看IOLoop,它的父類是Configurable類,也就是說:IOLoop是一個直屬配置子類
class IOLoop(Configurable):
......
這里就要結合Configurable類進行講解:
Configurable中的new方法
1.首先實例化一個該直屬配置子類的'執行類對象',也就是調用該類的configurable_default方法並返回賦值給impl:
2.2 epoll
從start()開始,啥都不說,上代碼
asyncio.get_event_loop()方法,在一個coroutine內部獲取loop他不需要將loop作為參數傳遞給協程函數
注意get_event_loop()方法僅在同樣的線程中生效,如果在一個新線程中,應該用new_event_loop()來獲取新的loop,並通過set_event_loop(loop)來將其設為該線程下的loop。
(暫時深入到這里吧,跑偏題了,尷尬)
繼續
main.py文件中運行開始 IOLoop.current().run_sync(async_main)是干什么的
IOLoop中的run_sync方法中調用的函數添加參數,這個好理解。
(這里暫時不在深入,怕又跑偏了,記錄yi有時間的話繼續)
async(協程的語法)
作者很給力,不斷的使用新的東西,這是python3.5后為asyncio提供了async和await的新語法
想要了解協程的可以看下這篇,通過實例來了解更好些https://zhuanlan.zhihu.com/p/25228075
注意:通過async關鍵字定義一個協程(coroutine),協程也是一種對象。協程不能直接運行,需要把協程加入到事件循環(loop),由后者在適當的時候調用協程。
定義異步函數async def async_main()
三 繼續看代碼吧
# 定義異步函數
async def async_main(): # 建立解析對象ArgumentParser formatter_class: 重置 help 信息輸出的格式 parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) # 為應用程序添加參數選項 帶-的為可選參數,default默認參數值 parser.add_argument( '-s', '--server', default='localhost:4000', help='server address') # action參數的處理方法 parser.add_argument( "--allow-remote", action="store_true", help="allow remote connect device") parser.add_argument( '-t', '--test', action="store_true", help="run test code") # type參數的數據類型 parser.add_argument( '-p', '--port', type=int, default=3500, help='listen port') parser.add_argument("--owner", type=str, help="provider owner email") parser.add_argument( "--owner-file", type=argparse.FileType("r"), help="provider owner email from file")
3.1調用parse_args()
來解析ArgumentParser對象中保存的命令行參數:將命令行參數解析成相應的數據類型並采取相應的動作,它返回一個Namespace對象。
args = parser.parse_args()
3.2 heartbeat_connect 等待連接
這里直接運行會出現
什么原因,應該很容易看到,需要連接localhost:4000,沒有起服務,好吧,先運行rethinkdb數據庫,然后啟動atx-server2

好了,然后運行
3.3 進入make_app()
使用tornado.web.Application進行路由控制
3.4 heartbeat_connect去連接設備,這里可以看到過程中基本都是異步方法aysc,運行到_connect這個方法時:
async def _connect(self): ws = await websocket.websocket_connect(self._server_ws_url) ws.__class__ = SafeWebSocket await ws.write_message({ "command": "handshake", "name": self._name, "owner": self._owner, "secret": self._secret, "url": self._provider_url, "priority": self._priority, # the large the importanter })
發現通過websockt 服務來連接
3.5 hbc.open()
IOLoop.current().spawn_callback(coroutine_visit) #開始調用協程
3.6 device_watch()
開始使用adb進行監控 127.0.0.1:5037 這里就不再說name多了
_init_binaries() 這里剛開始獲取了設備信息] [設備名] [sdk: 28, abi: arm64-v8a, abis: ['arm64-v8a', 'armeabi-v7a', 'armeabi']
根據設備選擇要使用的atx 代理use atx-agent: atx-agent-armv7
3.7 _install_apk()安裝whatsInput 和ATX這里只看這兩個,另一個屬於測試包
這里手機初始化准備好后會向前端頁面發送
websocket send: {'udid': '2d869e6', 'platform': 'android', 'colding': False, 'provider': {'atxAgentAddress': '127.0.0.1:20001', 'remoteConnectAddress': 'Ip:20004',
'whatsInputAddress': '127.0.0.1:20003'}, 'properties': {'serial': '設備名', 'brand': 'Xiaomi', 'version': '9', 'model': 'MI 9', 'name': 'MI 9'}, 'command': 'update'}
暫時寫到這里吧,明天有時間看下atx-server2的部分代碼,覺得結合着看更方便去了解
目前還在學習中,希望會對大家有所幫助,覺得不錯,就點贊支持一下。 另外,有什么錯誤的地方需要大家指正。謝謝!