進程間采用pydbus通信并設(shè)置為系統(tǒng)服務(wù)

在做智能家居的產(chǎn)品的時候,設(shè)備上跑的是個linux系統(tǒng),里面有一些C/C++的和Python的程序,互相之間需要來回傳一些數(shù)據(jù),發(fā)一些信號。發(fā)現(xiàn)dbus這個東西可以用,Python有pydbus,但是在使用過程中,發(fā)現(xiàn)官方的文檔確實不是那么詳實,所以記錄一些經(jīng)驗。

python這邊的庫本來用的是dbus-python,結(jié)果后來發(fā)現(xiàn)deprecated了。改成pydbus,發(fā)現(xiàn)簡潔了很多,不過文檔就更簡略了。

prerequisite:

非需要一個 python3-gi, 關(guān)鍵是這個包在pypi里沒有,沒法pip安裝,所以很煩人,比如在虛擬環(huán)境里,就得折騰。如果是python2,那debian系里的包應(yīng)該叫python-gi。

還需要dbus,估計還需要dbus-x11

python 這邊我也推薦使用pydbus。


要建立一個dbus服務(wù),并提供method可以被其他進程調(diào)用,就下面這個例子就行:

https://github.com/LEW21/pydbus/blob/master/examples/clientserver/server.py

interface的名字起成和busname一樣也沒啥事,反正簡單需求。

from gi.repository import GLib

這句就是為啥需要那個python3-gi了。

需要它的loop

loop = GLib.MainLoop()

并且在最后讓loop無限循環(huán)下去:

loop.run()

bus = SessionBus()

bus.publish("net.lew21.pydbus.ClientServerExample", MyDBUSService())

這兩句是聲明一個Session Bus的連接,然后把自己定義的這個服務(wù)發(fā)表出去,名字就是那一串字符。

服務(wù)類的定義很簡單,method主要靠docstring的注釋。里面最主要的無非就是args的類型

arg type='s' name='response' direction='out'

out 是表示這是method的返回值,s就是字符串類型,

arg type='s' name='a' direction='in'

in就是method的調(diào)用參數(shù),s是字符串,參數(shù)名是a,

def EchoString(self, s):

return s

這個method就實現(xiàn)了這個相應(yīng)的聲明。

這個時候你如果能把這個文件運行起來,那么一個發(fā)表在dbus上的服務(wù)就開始工作了。

要調(diào)用這個dbus服務(wù),可以在另外一個進程里,也連到dbus上,然后通過dbus根據(jù)名字獲得相應(yīng)的proxy對象,然后用proxy對象來調(diào)用函數(shù)。

bus = SessionBus()

obj = bus.get('com.pi.mic')

result = obj.EchoString('Hello')


然而。。這是個sessionbus,就是說這個是每個session里的,也只能跟同一個session里的進程通訊。而我的需求是系統(tǒng)里跑的服務(wù),跟用戶登不登錄有沒有界面沒關(guān)系。。所以我要用SystemBus。

當然也很簡單,就是把上面的SessionBus換成SystemBus就行了。

然而。。當你運行的時候,就會發(fā)現(xiàn)報權(quán)限錯誤,無法擁有那個bus名字。。

這是因為策略的問題,所以我們需要一個策略文件

https://github.com/LEW21/pydbus/blob/master/examples/polkit/dbus.conf

allow own="net.lew21.pydbus.PolkitExample"

這一句就是允許擁有這個bus名字。但是例子里這個配置是寫在user="root" 里的,所以只有root能運行。如果其他用戶運行,還是會出現(xiàn)這個錯誤:GLib.Error: g-dbus-error-quark: GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: Connection ":x.xx" is not allowed to own the service "x.x.x" due to security policies in the configuration file

如果要讓其他用戶能運行,可以把這一句放到context="default" 里

后面兩句:

allow send_destination="net.lew21.pydbus.PolkitExample"

allow receive_sender="net.lew21.pydbus.PolkitExample"

就是允許收發(fā)消息了。

這個文件要放到/etc/dbus-1/system.d/ ?目錄下面。

現(xiàn)在,手動執(zhí)行那個python腳本是可以啟動服務(wù)了,但是要想把它變成開機自動啟動的,還需要加systemd的配置。

https://stackoverflow.com/questions/31702465/how-to-define-a-d-bus-activated-systemd-service

按這篇答案里的方法把兩個配置文件寫好,應(yīng)該就可以開機啟動了。

有個問題要注意,調(diào)用的函數(shù)如果執(zhí)行時間長,調(diào)用者會block在那等返回。如果不想這樣,可以用異步函數(shù)調(diào)用方式,加兩個參數(shù)。

下面的問題是,如果是method,如果客戶端要call服務(wù)端的method,那么服務(wù)端就得在call之前運行起來并且在bus上發(fā)布自己,但是如果情況是,你兩個進程之間要相互call,比如A有method ?a, B有method b,A在某些情況下要call b, B在某些情況下要call a,那么就很苦惱了,當然你可以把bus.get 寫在啟動以后具體需要調(diào)用的函數(shù)里。這樣不會一啟動就依賴另一個服務(wù)。或者還有個辦法,就是使用信號signal。其實使用signal的主要場景是事件觸發(fā)。比如A對象會在運行過程中產(chǎn)生一個事件a,他并不想關(guān)心其他進程怎么去處理這個事件,所以只需要發(fā)出一個signal即可。其他所有對這個事件感興趣的進程只需要訂閱這個signal,綁定回調(diào)函數(shù),那么當A發(fā)出這個signal時,其他所有訂閱了這個signal的進程的回調(diào)函數(shù)都會被自動調(diào)用。

signal的定義很簡單,發(fā)送方在docstring里和method的聲明方式一樣,然后

from pydbus.generic import signal

signalname = signal()

這里要注意的是在docstring里聲明的時候,signal的參數(shù)的direction都是out,

然后在需要訂閱signal的進程里,

def hello_signal_handler(hello_string):

? ? print("Received signal and it says: " +hello_string)

bus = SystemBus()

mainloop = GLib.MainLoop()

obj = bus.get('com.pi.mic')

obj.HelloSignal.connect(hello_signal_handler)

mainloop.run()

解釋一下,就是先連上bus,然后get回發(fā)出signal的proxy對象,然后聲明當收到相應(yīng)的signal的時候要調(diào)用哪個回調(diào)函數(shù)。由于要接受信號并回調(diào),所以和publish bus一樣,需要用loop。

這樣信號發(fā)送和接收就都寫完了。

還剩下一個問題,怎么發(fā)出signal呢,只需要在發(fā)送進程里按普通調(diào)用函數(shù)方式,比如在 A 對象里的某個函數(shù)里,要發(fā)送signal,只需要self.HelloSignal('world'),就行了。

但是這個調(diào)用并不能在別的進程里通過proxy對象發(fā),比如我不能在C進程里,obj = bus.get('com.pi.mic'), 然后obj.HelloSignal('world'),這是不行的。

signal還有一個好處就是發(fā)送方不需要等待,發(fā)送完直接返回,至于訂閱的人是怎么執(zhí)行,以及執(zhí)行多久,就不是發(fā)送方要考慮的事情了。


有時候我們可能想監(jiān)聽更加自由的signal,比如不管是哪個obj的同一個名字的信號,可以用bus的subscribe 方法。這個方法有7個參數(shù)。都是可選的。

bus.subscribe(sender=None,iface=None,signal=None,object=None,arg0=None,flags=0,signal_fired=signal_fired)

先定義一個signal_fired 函數(shù),你非要叫callback也行。參數(shù)包括 sender, object, iface, signal, params

def signal_fired(sender, object, iface, signal, params):

? ? ? ? print("receive signal of emit event {0}".format("".join(params)))

這里params就是從發(fā)送信號的對象那發(fā)送的時候給發(fā)送函數(shù)送的參數(shù),是個元組,如果信號沒參數(shù)那就是空元組。

然后訂閱事件:

這里如果你是要監(jiān)聽任何信號,那就全都不傳就行。。。哦,你要想調(diào)用回調(diào)函數(shù),總得傳signal_fired吧。

如果你要監(jiān)聽通過interface為com.pi.event.emit發(fā)的信號名字為event_emit_signal的信號,就傳這兩個,加上signal_fired

bus.subscribe(signal="event_emit_signal", iface='com.pi.event.emit', signal_fired=signal_fired)

這樣不管哪個對象發(fā)出的信號,只要是interface和signal的名字都對的上就能收到,能調(diào)用回調(diào)函數(shù)。

如果你只想關(guān)心信號名字:

bus.subscribe(signal="event_emit_signal", signal_fired=signal_fired)


寫這篇的時候的pydbus版本https://github.com/LEW21/pydbus 還不支持異步函數(shù)調(diào)用。


如果需要c語言版本的dbus server的例子,可以參考 https://github.com/fbuihuu/samples-dbus

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,535評論 19 139
  • 進程間使用D-Bus通信 D-Bus是一種高級的進程間通信機制,它由freedesktop.org項目提供,使用G...
    WB莫遙燚閱讀 22,282評論 0 7
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,603評論 0 6
  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學習操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,317評論 0 23
  • 進程間的通信主要分為本機器進程間的通信和不同機器間進程的通信。本文主要描述本機進程間的通信。 一、傳統(tǒng)Linux的...
    一葉之界閱讀 496評論 0 2

友情鏈接更多精彩內(nèi)容