利用XML-RPC實現簡單的遠端過程調用

我們希望能有一種簡單的方法可以在遠端機器上運行的Python程序中執行函數或者方法。對於這種情況而言,最簡單的方法就是使用XML-RPC了。比如下面這個例子就是給出了一個簡單的伺服器,其中實現了鍵-值對的存儲:

from xmlrpc.server import SimpleXMLRPCServer

class KeyValueServer:
_rpc_methods_ = [get, set, delete, exists, keys]
def __init__(self, address):
self._data = {}
self._serv = SimpleXMLRPCServer(address, allow_none=True)
for name in self._rpc_methods_:
self._serv.register_function(getattr(self, name))

def get(self, name):
return self._data[name]

def set(self, name, value):
self._data[name] = value

def delete(self, name):
del self._data[name]

def exists(self, name):
return name in self._data

def keys(self):
return list(self._data)

def serve_forever(self):
self._serv.serve_forever()

# Example
if __name__ == __main__:
kvserv = KeyValueServer((, 15000))
kvserv.serve_forever()

下面我們從一個客戶端機器上面來訪問伺服器:

>>> from xmlrpc.client import ServerProxy
>>> s = ServerProxy(http://localhost:15000, allow_none=True)
>>> s.set(foo, bar)
>>> s.set(spam, [1, 2, 3])
>>> s.keys()
[spam, foo]
>>> s.get(foo)
bar
>>> s.get(spam)
[1, 2, 3]
>>> s.delete(spam)
>>> s.exists(spam)
False
>>>

XML-RPC 可以讓我們很容易的構造一個簡單的遠程調用服務。你所需要做的僅僅是創建一個伺服器實例, 通過它的方法register_function()來註冊函數,然後使用方法serve_forever()啟動它。 在上面我們將這些步驟放在一起寫到一個類中,不夠這並不是必須的。比如你還可以像下面這樣創建一個伺服器:

from xmlrpc.server import SimpleXMLRPCServer
def add(x,y):
return x+y

serv = SimpleXMLRPCServer((, 15000))
serv.register_function(add)
serv.serve_forever()

XML-RPC暴露出來的函數只能適用於部分數據類型,比如字元串、整形、列表和字典。 對於其他類型就得需要做些額外的功課了。 例如,如果你想通過 XML-RPC 傳遞一個對象實例,實際上只有他的實例字典被處理:

>>> class Point:
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> p = Point(2, 3)
>>> s.set(foo, p)
>>> s.get(foo)
{x: 2, y: 3}
>>>

一般來講,你不應該將 XML-RPC 服務以公共API的方式暴露出來。 對於這種情況,通常分散式應用程序會是一個更好的選擇。

XML-RPC的一個缺點是它的性能。SimpleXMLRPCServer 的實現是單線程的, 所以它不適合於大型程序,儘管我們在11.2小節中演示過它是可以通過多線程來執行的。 另外,由於 XML-RPC 將所有數據都序列化為XML格式,所以它會比其他的方式運行的慢一些。 但是它也有優點,這種方式的編碼可以被絕大部分其他編程語言支持。 通過使用這種方式,其他語言的客戶端程序都能訪問你的服務。

雖然XML-RPC有很多缺點,但是如果你需要快速構建一個簡單遠程過程調用系統的話,它仍然值得去學習的。 有時候,簡單的方案就已經足夠了。

在不同的解釋器之間進行通信

我們正在運行多個Python解釋器的示例,有可能還是在不同的機器上,我們想通過消息在不同的解釋器之間交換數據

如果使用mutiprocessing.connection模塊,那麼在不同的解釋器之間實現通信就很簡單了。下面是一個實現了echo服務的簡單示例:

from multiprocessing.connection import Listener
import traceback

def echo_client(conn):
try:
while True:
msg = conn.recv()
conn.send(msg)
except EOFError:
print(Connection closed)

def echo_server(address, authkey):
serv = Listener(address, authkey=authkey)
while True:
try:
client = serv.accept()

echo_client(client)
except Exception:
traceback.print_exc()

echo_server((, 25000), authkey=bpeekaboo)

跟底層socket不同的是,每個消息會完整保存(每一個通過send()發送的對象能通過recv()來完整接受)。 另外,所有對象會通過pickle序列化。因此,任何兼容pickle的對象都能在此連接上面被發送和接受。

目前有很多用來實現各種消息傳輸的包和函數庫,比如ZeroMQ、Celery等。 你還有另外一種選擇就是自己在底層socket基礎之上來實現一個消息傳輸層。 但是你想要簡單一點的方案,那麼這時候 multiprocessing.connection 就派上用場了。 僅僅使用一些簡單的語句即可實現多個解釋器之間的消息通信。

如果你的解釋器運行在同一臺機器上面,那麼你可以使用另外的通信機制,比如Unix域套接字或者是Windows命名管道。 要想使用UNIX域套接字來創建一個連接,只需簡單的將地址改寫一個文件名即可:

s = Listener(/tmp/myconn, authkey=bpeekaboo)

一個通用準則是,你不要使用 multiprocessing 來實現一個對外的公共服務。 Client() 和 Listener() 中的 authkey 參數用來認證發起連接的終端用戶。 如果密鑰不對會產生一個異常。此外,該模塊最適合用來建立長連接(而不是大量的短連接), 例如,兩個解釋器之間啟動後就開始建立連接並在處理某個問題過程中會一直保持連接狀態。

如果你需要對底層連接做更多的控制,比如需要支持超時、非阻塞I/O或其他類似的特性, 你最好使用另外的庫或者是在高層socket上來實現這些特性。

參考書目

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Github地址:

yidao620c/python3-cookbook?

github.com圖標
推薦閱讀:

相關文章