PHP socket初探 --- 硬着頭皮繼續libevent(二)

[原文地址:https://blog.ti-node.com/blog...]php

實際上php.net上是有event擴展的使用說明手冊,可是呢,對於初學者來講卻並無什麼卵用,由於沒有太多的強有力使用案例代碼,也沒有給力的User Contributed Notes,因此可能形成的結果就是:根本就看不懂。node

這就是event文檔,點擊這裏,大家能夠感覺一下。從文檔上看,event擴展一共實現了以下圖幾個基礎類,其中最經常使用重要的就是Event和EventBase以及EventConfig三個類了,因此,先圍繞這三位開展一下工做。socket

考慮到大家、我、還有正在看這個文章的其餘未知物種,大多數可能並非搞C語言的老兵油子,因此我得用一些可能並不恰當的案例和比喻來嘗試引入這些概念。函數

libevent中有五個字母是event,實際上就是說「event纔是王道」。工具

Event類就是產生各類不一樣類型事件的產出器,好比定時器事件、讀寫事件等等,爲了提高民族榮譽感,咱們將這些各類事件比做各類戰鬥機:好比殲十、殲15和殲20。oop

EventBase類就相對容易介入了,這玩意顯然就是一個航空母艦了,爲了提高民族榮譽感,咱們就把EventBase類看成是遼寧艦。各類Event都必須依靠EventBase才能混口飯吃,這和戰鬥機有遼寧艦纔有底氣飛的更高更遠是一個道理。必定是先有航母(EventBase),其次是戰鬥機(Event)掛在航母(EventBase)上。性能

EventConfig則是一個配置類,實例化後的對象做爲參數能夠傳遞給EventBase類,這樣在初始化EventBase類的時候會根據這個配置初始化出不一樣的EventBase實例。類比的話,這個類則有點兒相似於遼寧艦的艦島,能夠配置指揮整個遼寧艦。航空母艦的發展趨勢是不須要艦島的,一樣,在實例化EventBase類時候一樣也能夠不傳入EventConfig對象,直接進行實例化也是沒有問題的。ui

下面咱們從開始寫一個php定時器來步入到代碼的節奏中。定時器是你們經常使用的一個工具,通常phper一說定時器,腦海中第一個想起的絕逼是Linux中的crontab。難道phper們離開了crontab真的就無法混了嗎?是的,真的好羞恥,現實告訴咱們就是這樣的,他們離開了crontab真的就無法混了。那麼,是時候經過純php來搞一波兒定時器實現了!spa

注意是真的純php,連Event擴展都不用的那種。.net

<?php
// 給當前php進程安裝一個alarm信號處理器
// 當進程收到alarm時鐘信號後會做出動做
pcntl_signal( SIGALRM, function(){
  echo "tick.".PHP_EOL;
} );
// 定義一個時鐘間隔時間,1秒鐘吧
$tick = 1;
while( true ){
  // 當過了tick時間後,向進程發送一個alarm信號
  pcntl_alarm( $tick );
  // 分發信號,呼喚起安裝好的各類信號處理器
  pcntl_signal_dispatch();
  // 睡個1秒鐘,繼續
  sleep( $tick );
}

代碼保存成timer.php,而後php timer.php運行下,若是不出問題應該能跑起來。可是吧,這個代碼有一坨問題。

  • 首先是性能通常( 可是,比使用declare(ticks=1)仍是要好很多的 )
  • 其次是代碼量確實短小,短小的都讓人懷疑:這特麼玩意能用?
  • 最後是即使我硬着頭皮用,但這玩意只能精確到秒級,逗我?

因此,爲了解決以上問題,是時候操做一波兒Event擴展了!

<?php
// 初始化一個EventConfig(艦島),雖然是個僅用於演示的空配置
$eventConfig = new EventConfig();
// 根據EventConfig初始化一個EventBase(遼寧艦,根據艦島配置下遼寧艦)
$eventBase = new EventBase( $eventConfig );
// 初始化一個定時器event(殲15,而後放到遼寧艦機庫中)
$timer = new Event( $eventBase, -1, Event::TIMEOUT | Event::PERSIST, function(){
  echo microtime( true )." : 殲15,滑躍,起飛!".PHP_EOL;
} );
// tick間隔爲0.05秒鐘,咱們還能夠改爲0.5秒鐘甚至0.001秒,也就是毫秒級定時器
$tick = 0.05;
// 將定時器event添加(將殲15拖到甲板加上彈射器)
$timer->add( $tick );
// eventBase進入loop狀態(遼寧艦!走你!)
$eventBase->loop();

將代碼保存爲tick.php,而後php tick.php執行一下,以下圖所示:

這種定時器是持久的定時器(每隔X時間必定會執行一次),若是想要一次性的定時器(隔X時間後就會執行一次,執行事後不再執行了),那麼將上述代碼中的「Event::TIMEOUT | Event::PERSIST」修改成「Event::TIMEOUT」便可。

若是你有一些自定義用戶數據傳遞給回調函數,能夠利用new Event()的第五個參數,這五個參數能夠給回調函數用,以下所示:

<?php
$timer = new Event( $eventBase, -1, Event::TIMEOUT | Event::PERSIST, function() use( &$custom ){
  //echo microtime( true )." : 殲15,滑躍,起飛!".PHP_EOL;
  print_r( $custom );
}, $custom = array(
  'name' => 'woshishui',
) );

須要重點說明的是new Event()這行代碼了,我把原型貼過來給你們看下:

public Event::__construct ( EventBase $base , mixed $fd , int $what , callable $cb [, mixed $arg = NULL ] )
  • 第一個參數是一個eventBase對象便可
  • 第二個參數是文件描述符,能夠是一個監聽socket、一個鏈接socket、一個fopen打開的文件或者stream流等。若是是時鐘時間,則傳入-1。若是是其餘信號事件,用相應的信號常量便可,好比SIGHUP、SIGTERM等等
  • 第三個參數表示事件類型,依次是Event::READ、Event::WRITE、Event::SIGNAL、Event::TIMEOUT。其中,加上Event::PERSIST則表示是持久發生,而不是隻發生一次就再也沒反應了。好比Event::READ | Event::PERSIST就表示某個文件描述第一次可讀的時候發生一次,後面若是又可讀就緒了那麼還會繼續發生一次。
  • 第四個參數就熟悉的很了,就是事件回調了,意思就是當某個事件發生後那麼應該具體作什麼相應
  • 第五個參數是自定義數據,這個數據會傳遞給第四個參數的回調函數,回調函數中能夠用這個數據。

經過以上的案例代碼能夠總結一下平常流程:

  1. 建立EventConfig(非必需)
  2. 建立EventBase
  3. 建立Event
  4. 將Event掛起,也就是執行了Event對象的add方法,不執行add方法那麼這個event對象就沒法掛起,也就不會執行
  5. 將EventBase執行進入循環中,也就是loop方法

捋清楚了定時器代碼,咱們嘗試來解決一個信號的問題。好比咱們的進程是常駐內存的daemon,再接收到某個信號後就會做出相應的動做,好比收到term信號後進程就會退出、收到usr1信號就會執行reload等等。

<?php
// 依然是照例行事,儘管暫時沒什麼實際意義上的配置
$eventConfig = new EventConfig();
// 初始化eventBase
$eventBase = new EventBase( $eventConfig );
// 初始化event
$event = new Event( $eventBase, SIGTERM, Event::SIGNAL, function(){
  echo "signal term.".PHP_EOL;
} );
// 掛起event對象
$event->add();
// 進入循環
echo "進入循環".PHP_EOL;
$eventBase->loop();

將代碼保存成tick.php,而後執行php tick.php,代碼已經進入循環了,而後咱們打開另一個終端,輸入ps aux|grep tick查看一個php進程的pid進程號,對這個進程發送term信號,以下圖所示:


奇怪啊,從第一張圖看到確實收到term信號了,可是很奇怪爲何這個php進程退出了呢?是由於沒有添加Event::PERSIST,修改以下代碼以下:

<?php
$event = new Event( $eventBase, SIGTERM, Event::SIGNAL | Event::PERSIST, function(){
  echo "signal term.".PHP_EOL;
} );

有些心眼多雞賊的,IO多路複用的方法一共有三個select、poll和epoll(Mac下叫作kqueue),那麼咱們當前的event擴展用的是哪一個方法呢?那麼,再表演一波兒:

<?php
// 查看當前系統平臺支持的IO多路複用的方法都有哪些?
$method = Event::getSupportedMethods();
print_r( $method );
// 查看當前用的方法是哪個?
$eventBase = new EventBase();
echo "當前event的方法是:".$eventBase->getMethod().PHP_EOL;
// 跑了許久龍套的config此次也得真的露露手腳了
$eventConfig = new EventConfig;
// 避免使用方法kqueue
$eventConfig->avoidMethod('kqueue');
// 利用config初始化event base
$eventBase = new EventBase( $eventConfig );
echo "當前event的方法是:".$eventBase->getMethod().PHP_EOL;

將代碼保存了,而後執行一下,能夠看到結果以下圖所示:

那麼,還有一些更雞賊的人繼續發問,前面提到的邊緣觸發和水平觸發,如何確認呢?既然都用上epoll或者kqueue了,就必定要用邊緣觸發。

<?php
$base = new EventBase();
echo "特性:".PHP_EOL;
$features = $base->getFeatures();
// 看不到這個判斷條件的,請反思本身「位運算」相關欠缺
if( $features & EventConfig::FEATURE_ET ){
  echo "邊緣觸發".PHP_EOL;
}
if( $features & EventConfig::FEATURE_O1 ){
  echo "O1添加刪除事件".PHP_EOL;
}
if( $features & EventConfig::FEATURE_FDS ){
  echo "任意文件描述符,不光socket".PHP_EOL;
}

運行結果以下圖所示:

小小裝個逼總結一下,今兒這些個內容就是講述event的基礎三大類,下個篇章依然是圍繞這三個傢伙和IO操做結合到一塊兒。

[原文地址:https://blog.ti-node.com/blog...]

圖片描述

[原文地址:https://blog.ti-node.com/blog...]

相關文章
相關標籤/搜索