Write a PHP DBus client
前兩天,我在觀看 PHP Manual 中新增的 Gearman 和 mqseries 兩個擴充元件的內容。我一邊想著 PHP 連 IBM WebSphere MQ Series 這種 Message bus 都支援了,真是不錯;另一邊又想起好一陣子沒留意 PHP 的 D-Bus 相關消息了。於是搜尋了一下,這次找到了兩個 php-dbus 擴充元件,版本都還在 0.1.x ,文件也非常匱乏。不過我還是看了它們的源碼文件,安裝並撰寫一些 D-Bus 應用。就結果而言,現在已經可以使用 PHP 撰寫基本的 D-Bus 客戶端應用工具了。
目前有兩套 php-dbus 擴充元件:
- GREE Labs's php-dbus
GREE 這套的實作內容有限,你無法用它補捉 D-Bus signal 。也無法用它撰寫 D-Bus 服務。 - Derick's php-dbus
Derick 這套已經被列入 PECL 之中,而且大部分的 D-Bus 內容皆已實作,可用度最高。但是文件相當匱乏,我實際上是看著它的源碼中的範例程式碼摸索它的使用方式。
這兩套我都測試過了,以 Derick's php-dbus 完成度較高。儘管 Derick's php-dbus 目前版本 (ver 0.1.0) 在撰寫 D-Bus 服務與客戶端程式時,仍然要避開某些情況才不會觸發記憶體錯誤。但它功能齊全,假以時日,等它修正錯誤後,相信它就會是 PHP 官方的 php-dbus 擴充元件。
本文接下來就會採用 Derick's php-dbus 示範如何用 PHP 撰寫一個簡單的 D-Bus 客戶端工具。
Install Derick's php-dbus
因為 Windows 平台基本上沒有 D-Bus 服務,所以以下內容,都假設你是在 Linux/FreeBSD 平台下操作。
首先前往 PECL 的 DBus 網頁下載源碼壓縮檔。因為這是源碼文件,必須經過編譯才能安裝。所以接著準備 D-Bus library, header files ,以及 PHP header files, phpize (PECL tool)。或是使用我修改過的版本: php dbus extension with ByteArray。
你下載回來的 php-dbus 是一套符合 PHP 標準擴充元件撰寫規範的源碼,故而解開壓縮檔後,僅需要執行四個標準動作,就可以完成編譯與安裝動作。假設我將 php-dbus 源碼壓縮檔的內容解開於 php-dbus 的目錄內,我只需要執行下列動作即可完成安裝。
$ cd php-dbus
$ phpize #PHP標準擴充元件源碼組態工具
$ ./configure
$ make #執行成功後就會得到 dbus.so
$ sudo make install
最後的 make install 只是將 dbus.so 複製到 PHP 搜尋擴充元件的目錄內。我們仍然要編輯 php.ini 的 extensions 區段,加入 extension=dbus.so
的設定,才能啟用 php-dbus 擴充元件。
DBus class
Derick's php-dbus 目前缺乏說明文件,我是參考源碼中的 Examples 了解它的用法。
php-dbus 實作了一個 DBus 類別。我們通常用到以下幾個方法:
- new DBus($type)
type: BUS_SESSION, BUS_SYSTEM - DBus::createProxy($service, $object, $interface)
配置一個 proxy 個體指向我們想要使用的 D-Bus 服務實體。 - DBus::addWatch($interface)
傾聽指定介面的 D-Bus signal (訊號)。屬於這個介面的訊號(signal) 都會回報給我們的 PHP 程式。我們要自己過濾目前的訊號事件是哪一個訊號。 - DBus::waitLoop($timeout)
配合DBus::addWatch()
,用於等待訊號事件。若有訊號事件發生,則它會回傳一個 DBusSignal 實體,否則回傳 NULL。 - DBusSignal::matches($interface, $signal)
配合DBus::waitLoop()
使用。用於過濾目前的訊號事件為何? - DBusSignal::getData()
取得此訊號事件的回應資料。
顯示 Rhythmbox 目前播放曲目
我將以 Rhythmbox 這套 Ubuntu 預設安裝的媒體播放工具為例,以 PHP 透過 D-Bus 服務取得 Rhythmbox 目前播放的音樂檔名。
watch_rhythmbox_playing.php
我只用 Rhythmbox D-Bus 服務的 org.gnome.Rhythmbox.Player.getPlayingUri() 方法,以及傾聽 org.gnome.Rhythmbox.Player.playingUriChanged() 訊號。
<?php
/*
There are two php-dbus bindings.
1. GREE Labs's
http://labs.gree.jp/Top/OpenSource/DBus-en.html
GREE's php-dbus binding could not handle D-Bus signal.
Also you could not write a D-Bus service by GREE's binding.
2. Derick's
http://pecl.php.net/package/DBus
required: dbus library and header files, phpize (PECL tools).
Ubuntu packages: libdbus-1-dev, php5-dev
Derick's is most useful, but lacks of documents.
You need to see it's examples.
http://svn.php.net/viewvc/pecl/dbus/trunk/examples/
*/
$rhythmboxService = 'org.gnome.Rhythmbox';
$rhythmboxPlayer = '/org/gnome/Rhythmbox/Player';
$rhythmboxPlayerInterface = 'org.gnome.Rhythmbox.Player';
/*
dbus-send --session --print-reply \
--dest=org.gnome.Rhythmbox \
/org/gnome/Rhythmbox/Player \
org.freedesktop.DBus.Introspectable.Introspect
*/
$d = new Dbus(Dbus::BUS_SESSION);
$playerProxy = $d->createProxy(
$rhythmboxService, $rhythmboxPlayer, $rhythmboxPlayerInterface);
$uri = $playerProxy->getPlayingUri();
//透過 proxy 實體,呼叫 RhythmboxPlayer->getPlayingUri()。
//取得它目前正在播放的音樂檔名。
echo "Rhythmbox is playing: ", urldecode($uri), "\n";
$d->addWatch($rhythmboxPlayerInterface);
//傾聽 RhythmboxPlayer 介面的訊號。
while (true) {
if ( !($s = $d->waitLoop(1000)) )
continue;
if ($s->matches($rhythmboxPlayerInterface, 'playingUriChanged')) {
//過濾傾聽 Rhythmbox 改變曲目的訊號。
$uri = $s->getData();
echo "Rhythmbox changes to play: ", urldecode($uri), "\n";
}
}
?>
DBus2 class
我覺得目前 php-dbus 過濾訊號的模式很難看,所以我參考了 Python dbus 模組,另外定義了一個 DBus2 類別,它實作了一個新方法 addSignalReceiver()
,以及內含迴圈的 mainLoop()
。並覆寫了
waitLoop()
的行為方式
new_watch_rhythmbox_playing.php
<?php
/**
* DBus2 class
*
* @license: LGPL
* @author: rock
*/
class Dbus2 extends Dbus {
function __construct($type) {
parent::__construct($type);
$this->signalReceivers = array();
}
//self.bus.add_signal_receiver(self.handler, dbus_interface=Hello.interface, signal_name = "SignalSay")
function addSignalReceiver($callback, $interface, $signal) {
if (!isset($this->signalReceivers[$interface]))
$this->signalReceivers[$interface] = array();
$this->signalReceivers[$interface][$signal] = $callback;
$this->addWatch($interface);
//var_dump($this->signalReceivers);
}
function mainLoop($time) {
while (true) {
$s = parent::waitLoop($time);
$called = false;
if ($s) {
foreach ($this->signalReceivers as $interface => $members) {
foreach ($members as $signal => $callback) {
if ($s->matches($interface, $signal)) {
//echo "matches\n";
$reply = $s->getData();
if (is_array($reply))
call_user_func_array($callback, $reply);
else
call_user_func($callback, $reply);
$called = true;
break; //foreach members
}
}
if ($called)
break; //foreach receivers
}
}
}
return $s;
}
}
$rhythmboxService = 'org.gnome.Rhythmbox';
$rhythmboxPlayer = '/org/gnome/Rhythmbox/Player';
$rhythmboxPlayerInterface = 'org.gnome.Rhythmbox.Player';
$d = new Dbus2(Dbus::BUS_SESSION); // use DBus2, not DBus
$playerProxy = $d->createProxy(
$rhythmboxService, $rhythmboxPlayer, $rhythmboxPlayerInterface);
$uri = $playerProxy->getPlayingUri();
echo "Rhythmbox is playing: ", urldecode($uri), "\n";
function my_callback($uri) {
echo "Rhythmbox changes to play: ", urldecode($uri), "\n";
}
$d->addSignalReceiver('my_callback', $rhythmboxPlayerInterface, 'playingUriChanged');
$d->mainLoop(1000);
?>
我看過 php-dbus 的源碼後,發覺它目前實作的方法有點少,所以我們才需要特別處理 waitLoop()
的模式。如果我們使用 dbus library 中的 dbus_message_get_interface()
和 dbus_message_get_member()
方法,就可以提高過濾訊號的效率。
等我改天找個空閒時間,跟作者聯絡一下,把我這 DBus2 class 的內容,用 C 語言寫進 php-dbus。
相關文章
- D-Bus 用途說明
- PHP D-Bus 與 Gearman 之比較
- php dbus 0.1.0 撰寫 DBus service 的使用經驗
- php dbus unboxing
- php dbus extension with ByteArray support