自駕白石山、滿城漢墓

| Comments

行程

D1:北京到白石山,爬山,到滿城住宿。
D2:看滿城漢墓,回京。

D1 白石山

原計劃早上6點出發,該死的拖延症,收拾東西花了點時間,七點多才走。

途徑南六環、京石高速、廊涿高速、張石高速。易縣以後進入山區,隧道又多又長,本着不超速、不随意變道、做中國好司機的原則,走得比較慢,十點多才到涞源。涞源出口很窄,堵了二十分鍾。出高速到景區已經不遠了,在東門停車場停好車。

來前天氣預報說有小雨,心說很好啊,我就喜歡下着小雨爬山,所以沒帶速幹帽,結果是個悶熱天兒,我去。

十點四十,出發。我的藝名叫不逃票不舒服斯基,因爲走正門太low了。不過逃票要早上四五點走野路,時間緊,任務重,還是low吧。頭一天同程訂的門票,比現買便宜十塊,據說發表評價再退十塊。坐擺渡車到祥雲門,也可以坐索道。然後順時針走環線。

山裏大部分路口都是局部路标,帶張完整的路線圖還是有必要的。

我對所有以“傳說”開頭介紹的東西不感冒,總之這個塑像是環線的起點和終點。

一開始就走下坡,沿着峽壁的棧道下到飛狐峽,這一線主要是峽谷半山的棧道,是全環線最好的一段,我喜歡峽谷。

飛狐峽走的人少,多數時間太陽不直曬,風吹着,涼快。

我喜歡峽谷。

每個山谷都應該有一條河,可惜。

逆風如解意,容易莫摧殘。

仙人曬靴。不喜歡這種附會的東西。

太行之神。這貨跟神有一毛錢關系麽。

好吧,這才是重點,玻璃棧道。一點兒都不吓銀,而且很短,而且收費,看我的斯卡帕美不美。

回望走過的棧道,敢問路在何方。

過了玻璃棧道就是雙雄石,好内涵的名字,剩下的路已經不多了,而且都是平路。

豬八戒背媳婦兒。雖然附會,确實比較像,就是這媳婦兒砢碜了點兒。

走一圈環線用了4個小時。剛下山,到停車場,毫無征兆的一場暴雨,這酸爽,山裏的天氣真是孩子臉。

驅車奔滿城,走高速要繞很遠,于是奔S332,看地圖隻有一百多公裏。結果又幹了三個多小時,天落黑才到酒店。

剛開始的一段路還可以。小公路在太行山裏蜿蜒,重山夾路,車少,路況也不錯,偶爾掠過幾個石築的村落,甚至有牧人和羊群鑲嵌在陡峭的青山上,恍如隔世。

中間一段路況很差,再往下走,人煙逐漸增多,更多的是成群結隊的重卡,很耽誤時間,有時候走着走着停了,還以爲前面堵車了,哪知道這孫子站路中間噓噓呢,知道燕趙民風骠悍,沒想到這麽骠悍。省道真練技術。

D2 滿城漢墓

我喜歡破破爛爛的地方,滿城漢墓早在我的怨念單裏。漢墓十墓九空,唯獨這個是極少沒被盜過的之一,光這個墓的陪葬,撐起了整個河北省博物館。另外墓主人很有名,中山靖王,漢景帝庶子,漢武帝異母兄,劉備号稱的祖宗。平生三大愛好,喝酒、吃肉、叮叮當當造小人兒。《史記》載其有子百二十人,按照正态分布,女兒也得有百二十人,劉營長辛苦。

景區大門,我喜歡人少的地方。頭一天在同程上訂好的園區門票、窦绾墓、劉勝墓的聯票,比現買便宜十塊,一個景點分段收費,旅遊業非常黑。

石像生和翁仲應該不是漢墓的原物。

有索道,不過山不高,沒必要。

夫人窦绾墓在靖王墓北一百多米處,容積比靖王墓大,大概是靖王死在先,窦绾墓多施工了幾年。

墓門用磚砌牆,然後注入熔鐵封死。倒這樣的鬥确實不容易。

窦绾墓平面圖。仿宮殿式布局,兩個耳室一存車馬,一存飲食。中室是客廳。側室是帶浴室的主卧。

墓道裏非常涼快,和外面完全兩個世界。兩千年前的人,先用火灼燒石壁,再用水激,最後用鐵器鑿掉碎石,這樣的工藝也算是巧奪天工了。

南耳室是車庫。

北耳室存放飲食。

中室灰常大。

滲水井和室内排水溝相連,組成很完善的排水系統。

金縷玉衣,仿品。

側室,窦绾的金縷玉衣和傳說中的長信宮燈原本停放在這裏。

靖王墓的容積雖然比窦绾墓小,不過規制似乎更大。中室有木瓦結構的建築,卧室在中室正後方,卧室後甚至有回廊。

中室内的木瓦建築,象征宴會廳。

南耳室,車庫。

北耳室,倉庫。

中室,宴會廳。

後室回廊。

後室,劉勝的金縷玉衣和傳說中的錯金博山爐原本停放在這裏。

費用

北京到白石山高速過路費:95¥
白石山門票:140¥
白石山停車費:20¥
白石山擺渡車票:20¥×2
滿城漢墓門票:65¥
滿城漢墓停車費:10¥
加油:233¥
保定到北京高速過路費:75¥

Swoole與PHP-FPM性能對比

| Comments

測試環境:

  • CPU: Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz
  • MEM: 4G
  • OS: Archlinux 64bit

測試命令:

1
ab -c 200 -n 200000 -k http://127.0.0.1/test

NGINX + PHP-FPM

Requests per second: 16240.50 [#/sec] (mean)
Time per request: 12.315 [ms] (mean)

NGINX + Swoole

Requests per second: 31284.57 [#/sec] (mean)
Time per request: 6.393 [ms] (mean)

Swoole

Requests per second: 99926.55 [#/sec] (mean)
Time per request: 2.001 [ms] (mean)

結論

對一個最簡單的PHP腳本做測試,排除業務邏輯的消耗的影響。Swoole威武。

變形金剛:KO電鍍嘴炮OP

| Comments

嘴炮OP這個模具應該是電影系列十年來最好的模具,經典到一再複刻和改模,和原色雙刀、DA28一起都屬同模或改模版本。相比之下,這款模具在塗裝上勝過同模其它版本,電鍍件和銀柒使人形、車形在色彩上對比鮮明,和銀色飛翼搭配很自然。

人形正面比例協調,L級的大小更突顯霸氣。

受G1動畫的影響,我覺得樸實無華的平頭卡車更能顯現OP的威嚴,電影版的狗頭卡車多少有點浮誇。不過從還原電影的角度來說,這款模具的車形還是很不錯的。尤其是兩部分電鍍件,遮光闆和進氣面闆,非常閃亮。白色描邊的火焰紋的刻畫也很到位。

這款模具之所以叫嘴炮OP,就是因為有嘴有炮。一般OP的模具的頭雕都是帶口罩的,而這款是露嘴的。其實我更喜歡帶口罩的形象,機器人要嘴沒什麼用,尤其像大黃蜂那種嗪奶頭的嘴,見一次吐一次。

L級的頭雕還是很精細的,細節的刻畫和塗裝都很到位,隻是這長了腮的OP是要配合天火馬甲搞個海陸空三棲麼。

炮是由兩個油箱組合變形而成,雖然炮形比較奇葩,但是構思確實很新穎。

背包不算大,不過還是過於突兀,尤其是這交叉的造型個人覺得很難看。

模具本身的缺陷很少,除了背包問題,吹毛求疵地有以下幾點:

  • 腰部的兩個輪子耷拉着太難看,完全可以做成翻折上去。
  • 車形狀态下的進氣面闆,作爲最大的電鍍件,變成人形後居然成了鞋底,很容易刮傷。
  • 手臂上的兩塊折闆太礙事。
  • 手是不可動手。
  • KO版的普遍問題:左遮光闆卡不緊,胸甲滑動不流暢。

L級模具的噱頭一般是聲光效果。這款模具有三個,一是人形狀态下,扳下腹部機關,胸甲聳起的同時,胸部和眼睛會亮,OP喊:“I am Optimus Prime !”(其實不如喊那句最經典的:“Autobots, transform and roll out !”,向G1緻敬會讓很多人喪失抵抗力……);第二個是人形變車形時,上半身和下半身分離時,發出叽叽咔叽的變形音效;第三個是在車形狀态下,扳動車頭背部的開關,發出機槍掃射的聲音。平心而論,這款模具的聲光效果設計得還是不錯的,人形、變形、車形三種狀态都有照顧,不像變3的L級大黃蜂,所有聲光效果都在那個巨大無比的雞肋背包上。

總之,這款KO雙刀電鍍嘴炮OP在模具、塗裝、做工、用料等各方面的表現都不錯,非神物而神物,壓箱底還是可以勝任的。

PHP擴展框架的創建

| Comments

創建項目

在PHP源碼目錄下的ext目錄下執行:

1
./ext_skel --extname=foobar

修改foobar/config.m4,移除以下三行前的dnl:

dnl PHP_ARG_WITH(foobar, for foobar support,
dnl Make sure that the comment is aligned:
dnl [ –with-foobar Include foobar support])

編譯與安裝

在foobar目錄下執行以下命令,生成configure腳本:

1
/usr/bin/phpize

執行configure:

1
./configure --with-php-config=/usr/bin/php-config

編譯安裝:

1
sudo make install

修改php.ini,啟用擴展:

1
extension=foobar.so

PHP流的上下文和過濾器的實現

| Comments

上下文的實現和應用

上下文包含流的選項和流的參數兩部分內容。

1
php_stream_context *php_stream_context_alloc(void);

流的選項是一系列鍵值對。

1
2
3
int php_stream_context_set_option(php_stream_context *context, const char *wrappername, const char *optionname, zval *optionvalue);

int php_stream_context_get_option(php_stream_context *context, const char *wrappername, const char *optionname, zval ***optionvalue);

流的參數目前只實現對流的事件的回調,php_stream_context->notifier存儲如下結構:

1
2
3
4
5
6
7
typedef struct {
    php_stream_notification_func func;
    void (*dtor)(php_stream_notifier *notifier);
    void *ptr;
    int mask;
    size_t progress, progress_max;
} php_stream_notifier;

回調函數的原型為:

1
2
3
4
5
typedef void (*php_stream_notification_func)(php_stream_context *context,
      int notifycode, int severity,
      char *xmsg, int xcode,
      size_t bytes_sofar, size_t bytes_max,
      void * ptr TSRMLS_DC);

notifycode包含如下:

  • PHP_STREAM_NOTIFY_RESOLVE:主機名解析完成
  • PHP_STREAM_NOTIFY_CONNECT:socket連接建立
  • PHP_STREAM_NOTIFY_AUTH_REQUIRED:需要驗證
  • PHP_STREAM_NOTIFY_MIME_TYPE_IS:遠程資源的MIME-Type可用
  • PHP_STREAM_NOTIFY_FILE_SIZE_IS:遠程資源的大小可用
  • PHP_STREAM_NOTIFY_REDIRECTED:原始地址被跳轉
  • PHP_STREAM_NOTIFY_PROGRESS:php_stream_notifier->progress和progress_max(可能的)已更新
  • PHP_STREAM_NOTIFY_COMPLETED:流中已無可用數據
  • PHP_STREAM_NOTIFY_FAILURE:請求失敗
  • PHP_STREAM_NOTIFY_AUTH_RESULT:遠程驗證已完成,並且可能是成功的

severity包含如下:

  • PHP_STREAM_NOTIFY_SEVERITY_INFO
  • PHP_STREAM_NOTIFY_SEVERITY_WARN
  • PHP_STREAM_NOTIFY_SEVERITY_ERR

php_stream_notifier->ptr用於存儲附加數據,如果該數據需要手工回收,需指定php_stream_notifier->dtor。

php_stream_notifier->mask如果被賦值severity,其它severity的事件將不會觸發回調函數。

過濾器的實現和應用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "ext/standard/php_string.h"

typedef struct {
  char is_persistent;
  char *from_chars;
  char *to_chars;
  int tr_len;
} php_donie_filter_data;

static php_stream_filter_status_t php_donie_stream_filter(
  php_stream *stream, php_stream_filter *thisfilter,
  php_stream_bucket_brigade *buckets_in,
  php_stream_bucket_brigade *buckets_out,
  size_t *bytes_consumed, int flags TSRMLS_DC
) {
  php_donie_filter_data *data = thisfilter->abstract;
  php_stream_bucket *bucket;
  size_t consumed = 0;

  while(buckets_in->head) {
      bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
      php_strtr(bucket->buf, bucket->buflen, data->from_chars, data->to_chars, data->tr_len);
      consumed += bucket->buflen;
      php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
  }

  if (bytes_consumed) {
      *bytes_consumed = consumed;
  }

  return PSFS_PASS_ON;
}

static void php_donie_stream_filter_dtor(
  php_stream_filter *thisfilter TSRMLS_DC
) {
  php_donie_filter_data *data = thisfilter->abstract;
  pefree(data, data->is_persistent);
}

static php_stream_filter_ops php_donie_stream_filter_ops = {
  php_donie_stream_filter,
  php_donie_stream_filter_dtor,
  "donie.to_upper_case"
};

static php_stream_filter *php_donie_stream_filter_create(
  const char *name, zval *param, int persistent TSRMLS_DC
) {
  php_donie_filter_data *data;

  data = pemalloc(sizeof(php_donie_filter_data), persistent);
  if (!data) {
      return NULL;
  }
  data->is_persistent = persistent;
  data->from_chars = "abcdefghijklmnopqrstuvwxyz";
  data->to_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  data->tr_len = strlen(data->from_chars);

  return php_stream_filter_alloc(&php_donie_stream_filter_ops, data, persistent);
}

static php_stream_filter_factory php_donie_stream_uppercase_factory = {
  php_donie_stream_filter_create
};

PHP_MINIT_FUNCTION(donie)
{
  /* register a filter */
  php_stream_filter_register_factory("donie.to_upper_case", &php_donie_stream_uppercase_factory TSRMLS_CC);

  return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(donie)
{
  /* unregister the filter */
  php_stream_filter_unregister_factory("donie.to_upper_case" TSRMLS_CC);

  return SUCCESS;
}

註冊和註銷

分別在MINIT和MSHUTDOWN函數中調用php_stream_filter_register_factory()和php_stream_filter_unregister_factory()註冊和註銷過濾器。

過濾器的執行過程

當過濾器被調用時,調用方將使用php_donie_stream_filter_create()函數創建過濾器的實例。該函數在被執行時初始化過濾器的相關數據,並指定過濾器的相關操作。

調用方然後將過濾器實例添加到對應的流的讀過濾器鏈或寫過濾器鏈中,當流中發生讀或寫的操作時,過濾器鏈將數據放到一或多個php_stream_bucket結構中,並傳遞給過濾器處理。

業務邏輯

結構體php_donie_stream_filter_ops指定了過濾器的名稱和相關業務邏輯。php_donie_stream_filter_ops->php_donie_stream_filter_dtor是過濾器的析構函數。php_donie_stream_filter_ops->php_donie_stream_filter是過濾器的主要業務邏輯。

在php_donie_stream_filter()中,函數接收一個php_stream_bucket鏈表buckets_in,並將處理後的php_stream_bucket追加到鏈表buckets_out中輸出。

php_stream_bucket_make_writeable()將一個bucket從鏈表中移除,如果必要,複製其內部緩衝數據,以獲得對內容的寫權限。此外,對bucket的相關操作還有:

1
2
3
4
5
6
7
8
9
10
11
php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, int own_buf, int buf_persistent TSRMLS_DC);

int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length TSRMLS_DC);

void php_stream_bucket_delref(php_stream_bucket *bucket TSRMLS_DC);
void php_stream_bucket_addref(php_stream_bucket *bucket);

void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC);
void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket TSRMLS_DC);

void php_stream_bucket_unlink(php_stream_bucket *bucket TSRMLS_DC);

若過濾器已準備好輸出的數據,返回PSFS_PASS_ON;若還需要更多數據才能結束過濾操作,返回PSFS_FEED_ME;若遇到嚴重問題,返回PSFS_ERR_FATAL。

使用gn操作增強Vim的搜索

| Comments

gn是Vim 7.4新增的一個操作(motion),作用是跳到並選中下一個搜索匹配項。

具體說,Vim裡執行搜索後,執行n操作只會跳轉到下一個匹配項,而不選中它。但是我們往往需要對匹配項執行一些修改操作,例如替換、刪除或修改大小寫等,如果先跳轉再執行對應操作,會比較繁瑣。gn可以和相應的操作結合,簡化這些過程。

舉個栗子。如果要把所有的win、linux替換成大寫,可以先用正則搜索“\(win\|linux\)\C”,然後執行“gUgn”,此時下一個匹配的結果就會被替換成大寫,然後直接執行“.”重複上次操作即可。

此外,常用的組合有:

  • cgn: 刪除下一個匹配項,並進入插入模式。
  • dgn: 刪除下一個匹配項,並保持常規模式。

和操作“N”相同,執行“gN”是逆向操作。

在i3wm狀態欄顯示股票信息

| Comments

效果如圖:

創建腳本,並賦可執行權限:

~/.i3/myi3status.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh
# shell script to prepend i3status with more stuff

STOCK_SCRIPT=`realpath "$0"|xargs dirname`/stock.php

i3status | while :
do
    stock_info=""
    if [[ -x "$STOCK_SCRIPT" ]]; then
        stock_info=`$STOCK_SCRIPT`
    fi

    read line

    # if output_format = i3bar in i3status.conf
    stock_info="[{ \"full_text\": \"${stock_info}\" },"
    echo "${line/[/$stock_info}" || exit 1

    # # if not output_format = i3bar in i3status.conf
    # echo "$stock_info | $line" || exit 1
done

如果i3status.conf中啟用了JSON格式輸出(支持顏色),應啟用上面腳本中第一塊的代碼,否則使用後面的。啟動JSON格式輸出的內容具體如下:

~/.i3status.conf
1
2
3
4
general {
    colors = true
    output_format = i3bar
}

在i3wm的配置文件中用以上腳本替換i3status:

~/.i3/config
1
2
3
4
5
6
7
8
9
10
bar {

    # ...

    # status_command i3status
    status_command ~/.i3/myi3status.sh

    # ...

}

在和上面腳本同路徑下創建腳本:

~/.i3/stock.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/bin/env php
<?php
class PinYin
{
    public static function utf8To($ss, $isfirst = false)
    {
        return self::convert(self::utf8ToGB2312($ss), $isfirst);
    }

    public static function utf8ToGB2312($ss)
    {
        return iconv('UTF-8', 'GB2312//IGNORE', $ss);
    }

    // 字符串必须为GB2312编码
    public static function convert($ss, $isfirst = false)
    {
        $res = '';
        $len = strlen($ss);
        $pinyinArr = self::get_pinyin_array();
        for ($i=0; $i<$len; $i++) {
            $ascii = ord($ss[$i]);
            if ($ascii > 0x80) {
                $asciiB = ord($ss[++$i]);
                $ascii = $ascii * 256 + $asciiB - 65536;
            }

            if ($ascii < 255 && $ascii > 0) {
                if (($ascii >= 48 && $ascii <= 57) || ($ascii >= 97 && $ascii <= 122)) {
                    $res .= $ss[$i]; // 0-9 a-z
                } elseif ($ascii >= 65 && $ascii <= 90) {
                    $res .= strtolower($ss[$i]); // A-Z
                } else {
                    $res .= '_';
                }
            } elseif ($ascii < -20319 || $ascii > -10247) {
                $res .= '_';
            } else {
                foreach ($pinyinArr as $py=>$asc) {
                    if ($asc <= $ascii) {
                        $res .= $isfirst ? $py[0] : $py;
                        break;
                    }
                }
            }
        }
        return $res;
    }

    public static function toFirst($ss)
    {
        $ascii = ord($ss[0]);
        if ($ascii > 0xE0) {
            $ss = self::utf8ToGB2312($ss[0].$ss[1].$ss[2]);
        } elseif ($ascii < 0x80) {
            if ($ascii >= 65 && $ascii <= 90) {
                return strtolower($ss[0]);
            } elseif ($ascii >= 97 && $ascii <= 122) {
                return $ss[0];
            } else {
                return false;
            }
        }

        if (strlen($ss) < 2) {
            return false;
        }

        $asc = ord($ss[0]) * 256 + ord($ss[1]) - 65536;

        if($asc>=-20319 && $asc<=-20284) return 'a';
        if($asc>=-20283 && $asc<=-19776) return 'b';
        if($asc>=-19775 && $asc<=-19219) return 'c';
        if($asc>=-19218 && $asc<=-18711) return 'd';
        if($asc>=-18710 && $asc<=-18527) return 'e';
        if($asc>=-18526 && $asc<=-18240) return 'f';
        if($asc>=-18239 && $asc<=-17923) return 'g';
        if($asc>=-17922 && $asc<=-17418) return 'h';
        if($asc>=-17417 && $asc<=-16475) return 'j';
        if($asc>=-16474 && $asc<=-16213) return 'k';
        if($asc>=-16212 && $asc<=-15641) return 'l';
        if($asc>=-15640 && $asc<=-15166) return 'm';
        if($asc>=-15165 && $asc<=-14923) return 'n';
        if($asc>=-14922 && $asc<=-14915) return 'o';
        if($asc>=-14914 && $asc<=-14631) return 'p';
        if($asc>=-14630 && $asc<=-14150) return 'q';
        if($asc>=-14149 && $asc<=-14091) return 'r';
        if($asc>=-14090 && $asc<=-13319) return 's';
        if($asc>=-13318 && $asc<=-12839) return 't';
        if($asc>=-12838 && $asc<=-12557) return 'w';
        if($asc>=-12556 && $asc<=-11848) return 'x';
        if($asc>=-11847 && $asc<=-11056) return 'y';
        if($asc>=-11055 && $asc<=-10247) return 'z';
        return false;
    }

    public static function get_pinyin_array()
    {
        static $pyArr;
        if(isset($pyArr)) return $pyArr;

        $kk = 'a|ai|an|ang|ao|ba|bai|ban|bang|bao|bei|ben|beng|bi|bian|biao|bie|bin|bing|bo|bu|ca|cai|can|cang|cao|ce|ceng|cha|chai|chan|chang|chao|che|chen|cheng|chi|chong|chou|chu|chuai|chuan|chuang|chui|chun|chuo|ci|cong|cou|cu|cuan|cui|cun|cuo|da|dai|dan|dang|dao|de|deng|di|dian|diao|die|ding|diu|dong|dou|du|duan|dui|dun|duo|e|en|er|fa|fan|fang|fei|fen|feng|fo|fou|fu|ga|gai|gan|gang|gao|ge|gei|gen|geng|gong|gou|gu|gua|guai|guan|guang|gui|gun|guo|ha|hai|han|hang|hao|he|hei|hen|heng|hong|hou|hu|hua|huai|huan|huang|hui|hun|huo|ji|jia|jian|jiang|jiao|jie|jin|jing|jiong|jiu|ju|juan|jue|jun|ka|kai|kan|kang|kao|ke|ken|keng|kong|kou|ku|kua|kuai|kuan|kuang|kui|kun|kuo|la|lai|lan|lang|lao|le|lei|leng|li|lia|lian|liang|liao|lie|lin|ling|liu|long|lou|lu|lv|luan|lue|lun|luo|ma|mai|man|mang|mao|me|mei|men|meng|mi|mian|miao|mie|min|ming|miu|mo|mou|mu|na|nai|nan|nang|nao|ne|nei|nen|neng|ni|nian|niang|niao|nie|nin|ning|niu|nong|nu|nv|nuan|nue|nuo|o|ou|pa|pai|pan|pang|pao|pei|pen|peng|pi|pian|piao|pie|pin|ping|po|pu|qi|qia|qian|qiang|qiao|qie|qin|qing|qiong|qiu|qu|quan|que|qun|ran|rang|rao|re|ren|reng|ri|rong|rou|ru|ruan|rui|run|ruo|sa|sai|san|sang|sao|se|sen|seng|sha|shai|shan|shang|shao|she|shen|sheng|shi|shou|shu|shua|shuai|shuan|shuang|shui|shun|shuo|si|song|sou|su|suan|sui|sun|suo|ta|tai|tan|tang|tao|te|teng|ti|tian|tiao|tie|ting|tong|tou|tu|tuan|tui|tun|tuo|wa|wai|wan|wang|wei|wen|weng|wo|wu|xi|xia|xian|xiang|xiao|xie|xin|xing|xiong|xiu|xu|xuan|xue|xun|ya|yan|yang|yao|ye|yi|yin|ying|yo|yong|you|yu|yuan|yue|yun|za|zai|zan|zang|zao|ze|zei|zen|zeng|zha|zhai|zhan|zhang|zhao|zhe|zhen|zheng|zhi|zhong|zhou|zhu|zhua|zhuai|zhuan|zhuang|zhui|zhun|zhuo|zi|zong|zou|zu|zuan|zui|zun|zuo';
        $vv = '-20319|-20317|-20304|-20295|-20292|-20283|-20265|-20257|-20242|-20230|-20051|-20036|-20032|-20026|-20002|-19990|-19986|-19982|-19976|-19805|-19784|-19775|-19774|-19763|-19756|-19751|-19746|-19741|-19739|-19728|-19725|-19715|-19540|-19531|-19525|-19515|-19500|-19484|-19479|-19467|-19289|-19288|-19281|-19275|-19270|-19263|-19261|-19249|-19243|-19242|-19238|-19235|-19227|-19224|-19218|-19212|-19038|-19023|-19018|-19006|-19003|-18996|-18977|-18961|-18952|-18783|-18774|-18773|-18763|-18756|-18741|-18735|-18731|-18722|-18710|-18697|-18696|-18526|-18518|-18501|-18490|-18478|-18463|-18448|-18447|-18446|-18239|-18237|-18231|-18220|-18211|-18201|-18184|-18183|-18181|-18012|-17997|-17988|-17970|-17964|-17961|-17950|-17947|-17931|-17928|-17922|-17759|-17752|-17733|-17730|-17721|-17703|-17701|-17697|-17692|-17683|-17676|-17496|-17487|-17482|-17468|-17454|-17433|-17427|-17417|-17202|-17185|-16983|-16970|-16942|-16915|-16733|-16708|-16706|-16689|-16664|-16657|-16647|-16474|-16470|-16465|-16459|-16452|-16448|-16433|-16429|-16427|-16423|-16419|-16412|-16407|-16403|-16401|-16393|-16220|-16216|-16212|-16205|-16202|-16187|-16180|-16171|-16169|-16158|-16155|-15959|-15958|-15944|-15933|-15920|-15915|-15903|-15889|-15878|-15707|-15701|-15681|-15667|-15661|-15659|-15652|-15640|-15631|-15625|-15454|-15448|-15436|-15435|-15419|-15416|-15408|-15394|-15385|-15377|-15375|-15369|-15363|-15362|-15183|-15180|-15165|-15158|-15153|-15150|-15149|-15144|-15143|-15141|-15140|-15139|-15128|-15121|-15119|-15117|-15110|-15109|-14941|-14937|-14933|-14930|-14929|-14928|-14926|-14922|-14921|-14914|-14908|-14902|-14894|-14889|-14882|-14873|-14871|-14857|-14678|-14674|-14670|-14668|-14663|-14654|-14645|-14630|-14594|-14429|-14407|-14399|-14384|-14379|-14368|-14355|-14353|-14345|-14170|-14159|-14151|-14149|-14145|-14140|-14137|-14135|-14125|-14123|-14122|-14112|-14109|-14099|-14097|-14094|-14092|-14090|-14087|-14083|-13917|-13914|-13910|-13907|-13906|-13905|-13896|-13894|-13878|-13870|-13859|-13847|-13831|-13658|-13611|-13601|-13406|-13404|-13400|-13398|-13395|-13391|-13387|-13383|-13367|-13359|-13356|-13343|-13340|-13329|-13326|-13318|-13147|-13138|-13120|-13107|-13096|-13095|-13091|-13076|-13068|-13063|-13060|-12888|-12875|-12871|-12860|-12858|-12852|-12849|-12838|-12831|-12829|-12812|-12802|-12607|-12597|-12594|-12585|-12556|-12359|-12346|-12320|-12300|-12120|-12099|-12089|-12074|-12067|-12058|-12039|-11867|-11861|-11847|-11831|-11798|-11781|-11604|-11589|-11536|-11358|-11340|-11339|-11324|-11303|-11097|-11077|-11067|-11055|-11052|-11045|-11041|-11038|-11024|-11020|-11019|-11018|-11014|-10838|-10832|-10815|-10800|-10790|-10780|-10764|-10587|-10544|-10533|-10519|-10331|-10329|-10328|-10322|-10315|-10309|-10307|-10296|-10281|-10274|-10270|-10262|-10260|-10256|-10254';
        $key = explode('|', $kk);
        $val = explode('|', $vv);
        $pyArr = array_combine($key, $val);
        arsort($pyArr);

        return $pyArr;
    }
}

define('STOCKS_FILE', $_SERVER['HOME'].'/.stocks');

if (!file_exists(STOCKS_FILE)) {
    exit('File not found: '.STOCKS_FILE);
}
$stockCodeArr = explode("\n", trim(file_get_contents(STOCKS_FILE)));
if (empty($stockCodeArr)) {
    exit('No stock code found.');
}

$context = stream_context_create(
    [
        'http'=>[
            'method'=>"GET",
            'timeout' => 3
        ]
    ]
);
$response = file_get_contents("http://hq.sinajs.cn/list=".implode(',', $stockCodeArr), false, $context);
if (empty($response)) {
    exit('Failed fetching stock info with API.');
}

$result = [];
$lines = explode("\n", trim(iconv('gbk', 'utf-8', $response)));
foreach ($lines as $line) {
    $matches = [];
    preg_match('/".*"/', $line, $matches);
    if (!empty($matches)) {
        $stock = explode(',', trim($matches[0], '"'));
        if (!empty($stock)) {
            $result[] = strtoupper(PinYin::utf8To($stock[0], true)).": {$stock[3]}, {$stock[4]}, {$stock[5]}";
        }
    }
}
echo implode('; ', $result);

腳本從~/.stocks中讀取股票代碼:

~/.stocks
1
2
sh601985
sz002024

PHP流的操作

| Comments

實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* reimplement fopen using stream */
ZEND_FUNCTION(donie_stream_fopen)
{
  php_stream *stream;
  char *path, *mode;
  int path_len, mode_len;
  int options = ENFORCE_SAFE_MODE|REPORT_ERRORS;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path, &path_len, &mode, &mode_len) == FAILURE)
  {
      return;
  }

  stream = php_stream_open_wrapper(path, mode, options, NULL);
  if (!stream)
  {
      RETURN_FALSE;
  }

  php_stream_to_zval(stream, return_value);
}

php_stream_open_wrapper()是對文件類型資源創建流的方法,此外還有基於socket的流、目錄流和特殊流三種。php_stream_to_zval()用於把流實例轉換成zval結構。

創建文件類型的流

1
2
#define php_stream_open_wrapper(path, mode, options, opened)   _php_stream_open_wrapper_ex((path), (mode), (options), (opened), NULL STREAMS_CC TSRMLS_CC)
#define php_stream_open_wrapper_ex(path, mode, options, opened, context) _php_stream_open_wrapper_ex((path), (mode), (options), (opened), (context) STREAMS_CC TSRMLS_CC)

參數path是文件名或URL,mode是模式字符串,options是選項組合。php_stream_open_wrapper_ex()允許指定一個流的上下文。

options包含以下選項:

  • USE_PATH:應用ini中的include_path到相對路徑。內建的fopen()的第三個參數置True時使用此選項。
  • STREAM_USE_URL:只有遠程URL才允許打開,%%file://, php://, compress.zlib://%%這樣的本地URL會報錯。
  • ENFORCE_SAFE_MODE:只有設置了此選項且ini中的safe_mode開啟時,才會使safe_mode生效,不設置此選項,則不論ini中是否開啟都不會生效。
  • REPORT_ERRORS:若開啟流出錯,生成錯誤信息。
  • STREAM_MUST_SEEK:不是所有流都允許seek,若置此選項,且流不允許seek,則包裝器不會開啟流。
  • STREAM_WILL_CAST:置此參數將要求流可被轉換成posix或stdio類型的文件描述符,若流不可轉換,可在IO開始前失敗。
  • STREAM_ONLY_GET_HEADERS:http包裝器使用此參數,只獲取資源的元數據,不獲取內容。
  • STREAM_DISABLE_OPEN_BASEDIR:當ini中的open_basedir開啟時,置此參數跳過open_basedir檢查。
  • STREAM_OPEN_PERSISTENT:要求流和相關資源都創建為持久數據。
  • IGNORE_PATH:不從include_path中搜索。
  • IGNORE_URL:只有本地文件才可以被打開。

創建傳輸類型的流

1
php_stream *_php_stream_xport_create(const char *name, size_t namelen, int options, int flags, const char *persistent_id, struct timeval *timeout, php_stream_context *context, char **error_string, int *error_code)

參數:

  • name:URL。
  • options:參數,與php_stream_open_wrapper()的相同。
  • flags:STREAM_XPORT_CLIENT或STREAM_XPORT_SERVER與其它STREAM_XPORT_*常量的組合。
  • persistent_id:鍵值,置此參數將使流在多次請求間持久存在。
  • timeout:置NULL將使用ini中設置的值。
  • errstr:用於向外傳遞錯誤信息,初始應置為NULL,若有錯誤信息傳出,調用方有責任釋放錯誤信息佔用的內存。
  • errcode:錯誤碼。

flags:

  • STREAM_XPORT_CLIENT:工作為客戶端,向遠程發起連接。
  • STREAM_XPORT_SERVER:工作為服務器,接受連接。
  • STREAM_XPORT_CONNECT:傳輸建立的同時發起對遠程的連接,否則,需手動調用php_stream_xport_connect()。
  • STREAM_XPORT_CONNECT_ASYNC:發起異步遠程連接。
  • STREAM_XPORT_BIND:将传输流绑定到本地资源. 用在服务端传输流时,这将使得accept连接的传输流准备端口, 路径或特定的端点标识符等信息。
  • STREAM_XPORT_LISTEN:%%Listen for inbound connections on the bound transport endpoint. This is typically used with stream-based transports such as tcp://, ssl://, and unix://%%.

創建目錄類型的流

1
php_stream php_stream_opendir(const char *path, int options, php_stream_context *context)

創建特殊類型的流

1
2
3
4
5
php_stream *php_stream_fopen_tmpfile(void);
php_stream *php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path);
php_stream *php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id);
php_stream *php_stream_fopen_from_file(FILE *file, const char *mode);
php_stream *php_stream_fopen_from_pipe(FILE *file, const char *mode);

讀流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 讀一個字符
int php_stream_getc(php_stream *stream);

// 讀取指定數量的字符
size_t php_stream_read(php_stream *stream, char *buf, size_t count);

// 讀取直到行末、或流末、或最多maxlen個字符
char *php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, size_t *returned_len);
char *php_stream_gets(php_stream *stream, char *buf, size_t maxlen);

// 與php_stream_get_line相同,可指定截止標記
char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC);

// 讀取一個目錄項
php_stream_dirent *php_stream_readdir(php_stream *dirstream, php_stream_dirent *entry);

寫流

1
2
3
4
5
6
7
8
9
// 寫非阻塞流可能寫入的數據比傳入的短;_string要求傳入的字符串以NULL結尾
size_t php_stream_write(php_stream *stream, char *buf, size_t count);
size_t php_stream_write_string(php_stream *stream, char *stf);

int php_stream_putc(php_stream *stream, int c);
// 與_string不同的是會自動追加一個換行符到字符串末尾
int php_stream_puts(php_string *stream, char *buf);

size_t php_stream_printf(php_stream *stream TSRMLS_DC, const char *format, ...);
1
int php_stream_flush(php_stream *stream);

在關閉流的時候,flush會被自動調用,並且大部分無過濾的流因無內部緩沖而不需flush,所以單獨flush一般是不需要的。

尋址

1
2
3
4
int php_stream_seek(php_stream *stream, off_t offset, int whence);
int php_stream_rewind(php_stream *stream);
int php_stream_rewinddir(php_stream *dirstream);
off_t php_stream_tell(php_stream *stream);

offset是相對於whence的位移量,whence包含:

  • SEEK_SET:文件開頭。置offet為負值被認為是個錯誤並導致不可預料的行為。offset超出文件範圍會導致一個錯誤,或文件被增大。
  • SEEK_CUR:當前位置。
  • SEEK_END:文件末尾。offset一般為負,正值的行為因流的實現而異。

獲取額外信息

1
int php_stream_stat(php_stream *stream, php_stream_statbuf *ssb);

關閉流

1
2
#define php_stream_close(stream) php_stream_free((stream), PHP_STREAM_FREE_CLOSE)
#define php_stream_pclose(stream) php_stream_free((stream), PHP_STREAM_FREE_CLOSE_PERSISTENT)

包含以下選項:

  • PHP_STREAM_FREE_CALL_DTOR:銷毀流時調用php_stream->ops->close
  • PHP_STREAM_FREE_RELEASE_STREAM:銷毀流時調用php_stream_wrapper->ops->stream_close
  • PHP_STREAM_FREE_PRESERVE_HANDLE:php_stream->ops->close不銷毀句柄
  • PHP_STREAM_FREE_RSRC_DTOR:用於流內部資源列表垃圾回收
  • PHP_STREAM_FREE_PERSISTENT:用於持久流,所有操作的結果在多次請求間持久有效
  • PHP_STREAM_FREE_CLOSE:CALL_DTOR和RELEASE_STREAM的組合,用於非持久流的常規選項
  • PHP_STREAM_FREE_CLOSE_CASTED:CLOSE和PRESERVE_HANDLE的組合
  • PHP_STREAM_FREE_CLOSE_PERSISTENT:CLOSE和PERSISTENT的組合,用於持久流的常規選項

變形金剛:祖國合金放大版探長

| Comments

探長可以和平頭OP並列變四最好的模具,人形和車形俱佳。但是官版的V級神物實在雞肋,所以祖國版准L級模具實在是拯救巨大控的好物。除了放大,探長的整個車頭都是合金件,既有質感又有份量。涂裝較官版也有改進,胸甲和輪軲上了銀柒,槍管上了金柒,車形後部增加了火焰紋,不像官版那麼素。最重要的是祖國版的做工很好,關節鬆緊適度,車形閉合得很好。

祖國版的探長目前有三個版本,普通版、叼煙版和藍色版。普通版和官版是一致的。叼煙版的頭雕是叼著煙的。藍色版的外形和普通版一樣,但是涂成了深藍色,而且附送兩個塑料小人。我最喜歡的叼煙版,不知道帝都禁煙後還能不能買到……

雖然祖國版整體而言是很好的東東,不過廠家的品控做得很不好,我玩兒過幾個實體才挑到合適的,有的車頭背部塑料板開盒就是爛的,有的輪軲個別沒有涂銀柒,比較普遍的現象是大槍插不進背部凹槽,還有個別關節比較松。所以買祖國版還是蠻拼人品的。

一般比較用心的模具都會在可玩性上加一個噱頭,比如平頭OP的槍是可以發射子彈的。探長的噱頭就是組合武器,拼裝後的大槍還是很霸氣的,只是一只手舉不起來,必須雙手托槍:

拆開掛滿身,不愧是個武器庫:

車形的原型是奧什科什防務軍用戰術裝甲車,也非常帥:

PHP流的實現

| Comments

流的概念

流是一系列概念的集合,包括流包裝器、流資源、流操作、上下文等內容。流是對不同資源進行操作的抽象,允許線性地從指定位置讀取或寫入數據,通過一套統一的API簡化對資源操作的實現。

流由scheme://target指代,scheme是包裝器(Wrapper)的名字,target是流的目標地址。

PHP的流的實現較Java簡單,後者可以通過嵌套實現更靈活的應用。

流的實現

存儲結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct _php_stream  {
  php_stream_ops *ops;
  void *abstract;            /* convenience pointer for abstraction */
  php_stream_filter_chain readfilters, writefilters;
  php_stream_wrapper *wrapper; /* which wrapper was used to open the stream */
  void *wrapperthis;     /* convenience pointer for a instance of a wrapper */
  zval *wrapperdata;      /* fgetwrapperdata retrieves this */
  int fgetss_state;     /* for fgetss to handle multiline tags */
  int is_persistent;
  char mode[16];         /* "rwb" etc. ala stdio */
  int rsrc_id;          /* used for auto-cleanup */
  int in_free;          /* to prevent recursion during free */
  /* so we know how to clean it up correctly.  This should be set to
  * PHP_STREAM_FCLOSE_XXX as appropriate */
  int fclose_stdiocast;
  FILE *stdiocast;    /* cache this, otherwise we might leak! */
#if ZEND_DEBUG
  int __exposed;    /* non-zero if exposed as a zval somewhere */
#endif
  char *orig_path;
  php_stream_context *context;
  int flags;    /* PHP_STREAM_FLAG_XXX */
  /* buffer */
  off_t position; /* of underlying stream */
  unsigned char *readbuf;
  size_t readbuflen;
  off_t readpos;
  off_t writepos;
  /* how much data to read when filling buffer */
  size_t chunk_size;
  int eof;
#if ZEND_DEBUG
  const char *open_filename;
  uint open_lineno;
#endif
  struct _php_stream *enclosing_stream; /* this is a private stream owned by enclosing_stream */
}; /* php_stream */

typedef struct _php_stream_ops  {
  /* stdio like functions - these are mandatory! */
  size_t (*write)(php_stream *stream, const char *buf, size_t count TSRMLS_DC);
  size_t (*read)(php_stream *stream, char *buf, size_t count TSRMLS_DC);
  int    (*close)(php_stream *stream, int close_handle TSRMLS_DC);
  int    (*flush)(php_stream *stream TSRMLS_DC);
  const char *label; /* label for this ops structure */
  /* these are optional */
  int (*seek)(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC);
  int (*cast)(php_stream *stream, int castas, void **ret TSRMLS_DC);
  int (*stat)(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC);
  int (*set_option)(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC);
} php_stream_ops;

php_stream結構體最重要的成員是ops和abstract。ops包含了流實例的所有操作邏輯,特別地,php_stream_ops->close在php_stream結構被回收前提供了回收與該流實例相關的資源的機會。abstract用來存儲一個自定義結構的數據,在流的操作邏輯裡可以方便的訪問。

實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define PHP_DONIESTREAM_STREAMTYPE "doniestream"

static size_t php_doniestream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
{
  donie_stream_data *data = stream->abstract;

  php_printf("Write to stream: %s\n", buf);

  return count;
}
static size_t php_doniestream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
{
  donie_stream_data *data = stream->abstract;
  zval **val;
  size_t read_size = count;

  php_printf("Read from stream: %s\n", data->key);

  return read_size;
}
static int php_doniestream_close(php_stream *stream, int close_handle TSRMLS_DC)
{
  donie_stream_data *data = stream->abstract;
  efree(data->key);
  efree(data);
  return 0;
}
static php_stream_ops php_doniestream_ops = {
  php_doniestream_write,
  php_doniestream_read,
  php_doniestream_close,
  NULL, /* flush */
  PHP_DONIESTREAM_STREAMTYPE,
  NULL, /* seek */
  NULL, /* cast */
  NULL, /* stat */
  NULL, /* set_option */
};

主要是流的操作邏輯的實現,最後構建的php_stream_ops結構用於後面流包裝器中初始化流實例時賦給php_stream->ops。

包裝器的實現

Wrapper是對某一協議的封裝,主要包含對該類型的流的一系列操作邏輯的實現。

存儲結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct _php_stream_wrapper    {
  php_stream_wrapper_ops *wops;   /* operations the wrapper can perform */
  void *abstract;            /* context for the wrapper */
  int is_url;           /* so that PG(allow_url_fopen) can be respected */
};

typedef struct _php_stream_wrapper_ops {
  /* open/create a wrapped stream */
  php_stream *(*stream_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);
  /* close/destroy a wrapped stream */
  int (*stream_closer)(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC);
  /* stat a wrapped stream */
  int (*stream_stat)(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC);
  /* stat a URL */
  int (*url_stat)(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC);
  /* open a "directory" stream */
  php_stream *(*dir_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);
  const char *label;
  /* delete a file */
  int (*unlink)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC);
  /* rename a file */
  int (*rename)(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC);
  /* Create/Remove directory */
  int (*stream_mkdir)(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context TSRMLS_DC);
  int (*stream_rmdir)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC);
  /* Metadata handling */
  int (*stream_metadata)(php_stream_wrapper *wrapper, const char *url, int options, void *value, php_stream_context *context TSRMLS_DC);
} php_stream_wrapper_ops;

php_stream_wrapper中最重要的是ops成員,它包含了所有該類型的流的操作邏輯的實現。其中最重要的是stream_opener和stream_closer,前者是流的實例化邏輯,後者是流的析構邏輯。特別的,stream_closer主要是用來銷毀php_stream結構,而php_stream_ops->close是用來回收所有和該流實例相關的資源。

實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#define PHP_DONIESTREAM_WRAPPER "donie"
typedef struct _donie_stream_data {
  off_t position;
  char *key;
  int key_len;
} donie_stream_data;

static php_stream *php_doniestream_wrapper_open(
      php_stream_wrapper *wrapper,
      const char *filename, const char *mode, int options,
      char **opened_path, php_stream_context *context
      STREAMS_DC TSRMLS_DC)
{
  donie_stream_data *data;
  php_url *url;

  if (options & STREAM_OPEN_PERSISTENT)
  {
      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to open %s persistently.", filename);
      return NULL;
  }

  url = php_url_parse(filename);
  if (!url)
  {
      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to parse url %s.", filename);
      return NULL;
  }

  data = emalloc(sizeof(donie_stream_data));
  data->position = 0;
  data->key_len = strlen(url->host);
  data->key = estrndup(url->host, data->key_len+1);
  php_url_free(url);

  return php_stream_alloc(&php_doniestream_ops, data, 0, mode);
}
static php_stream_wrapper_ops php_doniestream_wrapper_ops = {
  php_doniestream_wrapper_open,
  NULL, /* stream_closer */
  NULL, /* stream_stat */
  NULL, /* url_stat */
  NULL, /* dir_opener */
  PHP_DONIESTREAM_WRAPPER,
  NULL, /* unlink */
  NULL, /* rename */
  NULL, /* mkdir */
  NULL, /* rmdir */
  NULL  /* stream_metadata */
};
static php_stream_wrapper php_doniestream_wrapper = {
  &php_doniestream_wrapper_ops,
  NULL, /* abstract */
  0, /* is_url */
};

PHP_MINIT_FUNCTION(donie)
{
  /* register stream wrapper */
  if (php_register_url_stream_wrapper(PHP_DONIESTREAM_WRAPPER, &php_doniestream_wrapper TSRMLS_CC) == FAILURE)
  {
      return FAILURE;
  }

  return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(donie)
{
  /* unregister stream wrapper */
  if (php_unregister_url_stream_wrapper(PHP_DONIESTREAM_WRAPPER TSRMLS_CC) == FAILURE)
  {
      return FAILURE;
  }

  return SUCCESS;
}

PHP_DONIESTREAM_WRAPPER定義了協議名“donie”,所有對格式為“donie://XXX”地址的操作將由這個流實現。

donie_stream_data是一個自定義的結構體,在創建流實例的時候初始化並賦給php_stream->abstract,為以後對流的操作提供方便。

這裡只實現了最關鍵的stream_opener函數,其中,用php_stream_alloc()創建新的流實例。

最後在模塊的MINIT中用php_register_url_stream_wrapper()註冊包裝器,並在MSHUTDOWN中用php_unregister_url_stream_wrapper()註銷。