情境
前陣子 JS 碰久了,自然就好奇 Python 的異步如何開發,所以研究了一下 Twisted。
環境設定
1 | (venv)$ pip install twisted |
如果想要使用 Twisted 的 SSL 功能,需要安裝:1
2
3
4
5
6
7(venv)$ pip install pyopenssl
(venv)$ pip install service_identity
# 測試方式
>>> import OpenSSL
>>> import twisted.internet.ssl
>>> twisted.internet.ssl.SSL
如果沒有安裝 service_identity,應該會看這個警告:1
2>>> import twisted.internet.ssl
:0: UserWarning: You do not have a working installation of the service_identity module: 'No module named service_identity'. Please install it from <https://pypi.python.org/pypi/service_identity> and make sure all of its dependencies are satisfied. Without the service_identity module, Twisted can perform only rudimentary TLS client hostname verification. Many valid certificate/hostname mappings may be rejected.
如果想要使用 Twisted 的 SSH 功能,需要安裝:1
2
3
4
5
6(venv)$ pip install pycrypto
# 測試方式
>>> import Crypto
>>> import twisted.conch.ssh.transport
>>> twisted.conch.ssh.transport.md5
開發資源
直接從官方網站的文件下手應該是最好的。The complete developer guide 這份文件又依照各功能分門別類,還提供範例程式,是熟悉上手的好選擇。
echo_server & echo_client
echo_server.py
1 | #!/usr/bin/env python |
echo_client.py
1 | #!/usr/bin/env python |
從 echo_server 和 echo_client 的例子,可以開始最簡單的異步程式。要小心繼承來的方法名稱不要打錯字,否則什麼事情都不會發生,而且還可能不會報錯。我就不小心把 echo_client 的 connectionMade 打錯了,中間沒有任何事情發生,包括錯誤,就只能靠經驗除錯。從這點可以很清楚看到 Python 在繼承上的不足,JAVA 早期也是有這樣的問題,後來透過 annotation 解決了。查了資料,有些人會自製 decorator 來避免這樣的問題。
Twisted 基本觀念
Reactor
Reactor 是 Twisted 的核心,負責與底層實作的中間溝通部分,讓上層呼叫只要透過統一的介面即可使用。他等待並且解析 network、file system、timer 事件,將這些事件分配到對應的事件處理器。
基本上做了這樣的事情:
1 | while True: |
Reactor 的 listenTCP 和 connectTCP 是用來註冊要在 TCP Socket 可以讀取資料時,用來接獲通知的 callback 。reactor.run
用來啟動事件迴圈,除非呼叫 react.stop
,否則事件迴圈就會一直處理下去。
Transports
Transport 代表的是透過網路溝通兩端點之間的連線。Transport 代表的是 TCP、UDP、Unix sockets、serial ports。Transport 實作 ITransport ,有這些方法:
- write: 使用 nonblocking 方法將資料寫至實體連線。
- writeSequence: 在 line-oriented protocols 時,可以一次寫多筆的字串到實體連線。
- loseConnection: 把所有資料寫出,然後關閉連線。
- getPeer: 取得遠端端點的位址。
- getHost: 取得本機端點的位址。
Protocols
Protocol 決定如何異步處理網路事件。Protocol 實作了 IProtocol:
- makeConnection: 用來建立連線。
- connectionMade: 連線建立時,會被呼叫。
- dataReceived: 收到資料時,會被呼叫。
- connectionLost: 斷線時會被呼叫。
Protocol Factories
Protocol 實體在每條 connection 建立時初始化,每條 connection 斷線時就跟著結束。這代表用來連線的永久(persistent)設定資訊,不會存放在 Protocol 實體中。所以有一個 Factory 這個角色負責維護超越多個 Protocol 實體的設定資訊是必然的。每個 Factory 的 buildProtocol 用來為每條 connection 創建新的 Protocol,之後就將這個 Protocol 丟到 Reactor 去註冊 callback。Server/Client 分別使用 protocol.Factory/protocol.ClientFactory 的子類。這邊使用了 Factory Method Pattern ,算是一定規模以上的專案常見的手法。