尋找失落的匈奴(201409)

| Comments

行程

D1.AM 失落的匈奴

匈奴是中國統一後的第一個大敵,極盛時号稱控弦四十萬,以至于漢武帝窮高、文、景三代積蓄,鏖戰數十年,延及元帝,郅支授首,才終于換來一句“犯強漢者,雖遠必誅”。

東漢招引外族入居内地,想同化并加以利用,至于魏晉,欺壓各族,又不加防備。

西晉初年,由封建制的複興導緻的八王之亂,紛纭十六年,耗盡了皇族勢力。被壓制已久的民族矛盾于是爆發,匈奴、鮮卑、羯、氐、羌五族先後建立十六個國家,混戰一百三十多年,史稱五胡十六國。

其中,匈奴鐵弗部赫連勃勃建立大夏國,橫行秦晉,役民十萬蒸土築城,号統萬城。蒸土就是在土中混入生石灰,然後澆水夯築,所以城牆是白色的,當地人至今都叫白城子。築城要求極爲嚴格,工程驗收人以鐵錐刺牆,刺入一寸殺築者,屍體填入城牆,所以城牆極爲堅固,是夯土建築的巅峰之作。後來北魏太武帝拓跋焘滅夏入城,說“統馭萬邦,在德不在險”。

統萬城遺址在陝西靖邊。我喜歡去這種沒有被過度商業化開發的地方,再過幾年,也許就再也見不到這種荒草孤城的景象了。

早上四點火車到站,車站門口有很多黑車,會漫天要價,事先做好功課就不會被宰,議定一百五,要說明白包往返,到地方等兩個小時。

路上停車吃洋芋擦擦,就是炒土豆絲……

火車站到統萬城大概三十公裏,所以不會用太長時間,五點多到的時候,天還沒有亮,這個時候沒人收費,可以直接開進去。

天色微亮,從停車場走向統萬城還是條土路。

遺址正在進行小規模的修複。

城墩填充的外表被刻畫成磚的樣子,而夯土建築的輪廓應該是一層一層的。

像這樣:

走在質地堅硬的城牆上,可以想象當年它曾是怎樣的堅不可摧。

凸出的城墩和城牆形成有縱深的防禦體系,可以分割敵人的陣形,将攻城者置于被三面夾擊的死地。殘存的城牆尚且這麽高,不知道當年是如何雄偉的一座城!

荒原晨露,日出統萬。

沿着小路走向城中的高台,沙深露重,很不好走。

土墩離外城城門不遠,可能是内城的一部分。高處的洞裏,栖滿了野鴿子。

城的中部,據說是宮殿遺址。

城北一處正在發掘的現場。

北角的城墩,淹沒在一望無際的荒草中。

我在這裏休息,讀完了《晉書·赫連勃勃載記》。

東角的城墩已經破壞得快消失了。

回望城内。

東南城牆已經快消失在荒草中了。

南角墩曾被挖做窯洞。

隻有西角殘存的遺迹最完整。

因爲去得早,而且遺址少有人去,所以整個早上隻有我一個人,不緊不慢地走遍全城,用了三個小時,好在司機沒有催我,如果能帶着帳篷來這裏紮營,重讀通鑒永嘉南渡後三百年的烽火狼煙,一定很刺激,不過可能再也沒有機會了。

D1.PM 龍洲丹霞(波浪谷)

從統萬城出來,時間還很早,上車直奔龍洲丹霞,車上議價,往返加等待,100¥。

路上還有一段長城的遺迹,隻剩下壟起的一條痕迹,可能是漢長城。

景區已經修了觀景台和公路,不過還沒有收費。

看這個就知道爲什麽叫波浪谷了。

下面是深溝,岩石表面是風化的沙土,走在上面很驚險。

靖邊有石油,能看到遠方油井噴出的火。

其它地方見到的丹霞都是在地上的,這裏是在地下,要從公路邊的野路下去,所以找個知道路口在哪的司機很重要。

有的小路不好走,隻能手腳屁股并用。

下到谷底。

很美,真的很美。

中間有一段很窄,一隻腳都插不下。

像什麽,我當時自然地想到了大地的菊花……

下面這一段更難走,順着溝再往前是懸崖,隻能從左手邊的崖壁上繞過去。

這段很危險,旁邊就是懸崖,石頭風化嚴重,我爬得很慢、很糾結。

過了這段就好了,前面谷底别有洞天。

谷裏的水是從這個谷口裏流出來的,水源是人工的,源頭有隻死貓,惡心死我了,因爲溯源前用溪水洗了手,還挺涼。

前面這個山包就是傳說中的土匪窩,山體已經被挖空了,在前朝是土匪的藏身之地。

谷底再多些草和野花就好了。

水流經過的地方倒是水草豐茂。

土匪窩下的人工湖可以坐船。

從土匪窩旁的土路上到公路路口就很容易了,在路口吃了碗荞麥涼粉,挺好吃的。

波浪谷不大,不過确實很漂亮,據說雨後初晴時來更好看,丹霞會呈現通紅的顔色。由于野路不好走,而且我慢慢玩兒、拍照多,用了大概三個小時。

大概下午兩三點的樣子,時間還早,所以讓司機把我拉到縣城裏的電影院,看了場《猩球崛起2》,小縣城因爲有石油,居然就可以看3D,而且是眼鏡夾片,真是我大四眼黨的福音,比3D眼鏡舒服多了。隻是小縣城的影院不打折,于是看了自《阿凡達》以來最貴的電影。

看完電影,步行去之前司機推薦的“喬溝灣老婆風幹羊肉剁荞面”館吃剁荞面(23¥),離電影院不遠。然後就醬紫了:

沒想到吃面可以這麽鋪張,不知道從哪開始發動,恬着臉問了服務員,吃得好爽,也很撐。

出門邊散步邊打車,時間還早,慢慢講價,這小縣城的出租車死貴,到火車站沒多遠,要價20¥,後來終于找到一個15¥的。

D2.AM 晉祠

一夜火車,早上4點到太原,在車站門口等804路公交到晉祠,大概七點多到的。

相機沒電了,隻拍了這一張。

剩下的隻能用手機拍了,效果很差。

李世民和其功臣塑像。李唐以國号始自周成王弟叔虞所封的唐國,所以大修晉祠。

難老泉,就是奔它來的。

從晉祠出來大概十一點,吃完飯坐公交到山西博物館。

D2.PM 山西省博物館

博物館最能看出一個地方的底蘊。山西省博在國内算是一流的,不可不看。

山西省博東西很多,走馬觀花也沒看完,到閉館時還有一個展廳沒看。

出門打車奔火車站,差一點沒趕上高鐵。

費用

火車:北京到靖邊215¥,靖邊到太原114.5¥,太原到北京197¥
包車:靖邊250¥
打車:靖邊縣城到火車站15¥,山西省博到火車南站35¥
公交:太原火車站到晉祠2.5¥,晉祠到山西省博2.5¥
門票:晉祠75¥
導遊:山西省博自動講解器10¥
旅遊保險:3¥

自駕雞鳴驿、草沿天路(201507)

| Comments

行程

D1:上午,北京到懷來,土木之變戰場遺址和雞鳴驿;下午,宣化,清遠樓、鎮朔樓、時恩寺、遼代古墓壁畫群。

D2:草沿天路,張北野狐嶺入,崇禮桦皮嶺出。

D1.AM 土木堡、雞鳴驿

雖然起得很早,上京藏線也七點多了,趕上草原音樂節,出城的車巨多,在居庸關前面一段堵了半小時。

到土木堡的時候已經十點多了,唯一的标志物就是這個路邊的牌樓。

裏面是一片荒地,可能是土木之變的戰場遺址。當年一戰,二十萬大軍覆沒,大明精銳盡喪,英宗北狩,瓦剌圍城。可惜于謙,打赢了北京保衛戰,卻輸掉了奪門之變。所以老子說:“持而盈之,不若其已。揣而群之,不可長保也。金玉盈室,莫能守也。富貴而驕,自遺咎也。功遂身退,天之道也。”,實在是金玉良言,不過知進易,知退難,幾千年來,有幾個張良?

數百年的烽火狼煙,劫波度盡,誰能想到土木堡真正的被毀發生在本朝。路的另一邊就是土木村,據說還有土木堡殘存的遺迹,時間關系,沒有去看。

事實證明,沿G110奔雞鳴驿不是個好選擇,路況不太好,大貨車很多,應該走高速的。

城門被修成這樣也是醉了,我頭回見城門口橫着一影壁牆的。

反而從城内看城門,才有點兒意思。

城門口的塑像,後面是驿城博物館,主要介紹一下驿城的曆史。

城門要遠看才有意境。

城中主幹道旁的戲台,其它地方還有兩個,小小的驿城,修那麽多戲台幹嘛,規劃有問題。

多久沒見這種風格的房子了。

驿城署,現在是一家人的住處,可以進去參觀,有人剪票,不過裏面什麽都沒有。

文昌宮,當年是驿城的學校。

晴耕雨讀,用的殘體字……

龍神廟裏也有個戲台,得是多喜歡看戲!

範家大院,庚子國變慈禧逃跑時曾在這住了一宿,現在也就剩這麽一進。敢向全世界宣戰的隻有兩個人,一個是天朝聖母皇太後,一個是巴格達迪。

伏瞰驿城,懷疑原來真有這麽大麽,似乎和西夏黑水城和統萬城差不多。

坐在城頭等日落。

我懷疑這城牆是做舊,明朝以後,大多數城牆已經換用磚石結構,應該不會用這種過時的夯土建築了吧。

城外的雞鳴山形态突兀,很顯眼,如果是下雨的天氣,真有“風雨如晦,雞鳴不已”的感覺。

城外不知道爲什麽弄了幾截綠皮火車,很不搭。

D1.PM 宣化古城、遼代古墓壁畫

從雞鳴驿出來奔宣化,三點多到,吃莜面窩窩,又叫栲姥姥,據說是當地特色,在電視上見過。蘸着羊肉臊子,味道很不錯。

清遠樓和鎮朔樓是宣化古城的标志性建築,始建于明朝,氣勢恢宏,不可不看。

清遠樓是鍾樓,在維修。

鎮朔樓是鼓樓,門票5¥,我包場。

一層是個展廳,居然是介紹宣化當代發展的。

二樓很空,隻有一面大鼓。

樓外就是時恩寺。

殿裏正在做法事,很多人,順時針轉圈,口宣佛号,淨土宗的路子。

就是奔這塊匾來的。

從寺裏出來,開始下雨,趕緊鑽到車裏,奔遼代古墓壁畫群,路經大新門,這是一排新修的城牆,今天已經逛夠城牆了,時間也不夠,于是繼續趕路。

穿過又窄又爛的土路,深入下八裏村,就是遼代古墓壁畫群,如果不是導航,誰能找到這鬼地方。時間已經是5點後,售票處沒有人,墓道鎖門了,隻能看看園子,看售票處的标價,票價要50¥,比滿城漢墓黑多了。不過這種地方遊人很少,即便白天來,一個人逛古墓是一種什麽樣的體驗?或者如何優雅的和古人神交?這是知乎看多了。

D2 草沿天路

第二天,早五點起床,奔天路東線,從張北野狐嶺入,崇禮桦皮嶺出。

現在這條路知名度越來越高,來的人很多,七點多到的時候,野狐嶺入口的一段路,車就已經一輛接一輛了。路的兩邊越來越多地被開發旅遊項目,中國旅遊模式正在再次被複制,草原旅遊就那麽幾項,燒烤、騎馬、射箭、沙灘摩托,毫無新意,而且同質化嚴重。使天路出名的原生态自然風光,正在慢慢消退。

費用

北京到宣化過路費:65¥
雞鳴驿門票:35¥
鎮朔樓門票:5¥
宣化經天路回北京過路費:120¥
加油:416¥

變形金剛:電影4領袖級大黃蜂

| Comments

當電影2那堆玩具已經成為神物,入個4的L級大黃蜂也未嘗不是一種自我安慰。

電影4的L級BB是3的同模異涂,但是閹割了武器背包,也就是說,這貨沒有聲光效果!雖然3的背包很雞肋,但是本著先看有沒有,再看好不好的原則,4的玩具再一次見證了大縮水時代,我不敢想下一代會做成什麼樣。多麼懷念那個連D級玩具都遠超現在L級的時代。

雖然電影2的U級和戰刃是公認最經典的BB模具,退而求其次,3的模具也不錯。人形很有層次感和曲線感。塗裝較3有改進的地方,黑色部件的引入增強了色彩對比,比3的非灰即黃要好很多;最大的亮點是臉和觸角的銀柒塗裝,3的灰臉看著像骷髏頭,很恐怖。不過退步和進步一樣明顯,首先面具改成了V形描邊,遠不如之前的面部橫描好看,現在一點都不想把面具拉下來了;此外,腳尖沒有涂色也是個敗筆。

頭雕的細節刻畫不錯,還是BB一貫的嗪奶嘴的骷髏頭,面具的塗裝也很差,銀柒塗裝是唯一能挽回點兒顏面的地方。

手臂採用非對稱設計,右手手炮,左手是個半假手,只有大拇指可動,什麼都握不住,只是個擺設,看看當年電影2的D級的戰刃,完全不能比啊。

腳的設計很漂亮,不過雖然看起來支撐面很大,但由於踝關節不可左右擺動,導致接地性不好。

所有BB模具都不能解決的大背包,現在似乎已經成為BB的特征了,好吧,即便接受這一點,這一整塊棺材板好意思跟戰刃的兩級背板比麼?

關節的鬆緊度沒有問題,可動性也不錯,不過腰部不能轉,頭部只能左右轉,不能抬頭、低頭。

武器就是右手手炮,這貨沒有任何配件,如果說噱頭的話,也就是手炮可以拉長和縮短,這點連D級玩具都不如啊。

載具形態也不錯,滾動性良好,可以擺出3的武裝爆甲的形態,但是武裝呢?!

底部隱藏度一般,似乎電影系所有BB都有頭無法隱藏的問題。

最後放一張3的武裝爆甲照,緬懷BB入宮前的崢嶸歲月。

自駕白石山、滿城漢墓(201507)

| 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級模具,手居然是假手,很不可理解。

踝關節可以左右擺動,所以腳的接地性沒有問題,不過全身最大的兩塊電鍍件居然踩在腳底板當鞋墊,真是無語了。

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

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

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

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

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

由於車輪沒有卡扣固定,並且有的車輪不能著地,所以載具形態的滾動性不好。

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

把玩性方面,由于体形较大,变形复杂度高,把玩起来流畅度并不高,不过这是L级模具的通病。

此外,KO版的普遍問題都有,像左遮光闆卡不緊、胸甲滑動不流暢。

總之,這款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