最近更新: 2010-04-16

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 擴充元件:

  1. GREE Labs's php-dbus
    GREE 這套的實作內容有限,你無法用它補捉 D-Bus signal 。也無法用它撰寫 D-Bus 服務。
  2. 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

Ubuntu requied packages: libdbus-1-dev, libxml2-dev, php5-dev (include phpize)

你下載回來的 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。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/12193781.html