Browse Source

add files

walkor 12 năm trước cách đây
commit
63f4e168aa

+ 135 - 0
Clients/StatisticClient.php

@@ -0,0 +1,135 @@
+<?php
+/**
+ *
+ * 上报接口调用统计信息的客户端 UDP协议
+ * 用来统计调用量、成功率、耗时、错误码等信息
+ *
+ * @author liangl
+ */
+class StatisticClient
+{
+    // udp最大包长 linux:65507 mac:9216
+    const MAX_UDP_PACKGE_SIZE  = 65507;
+    
+    // char类型能保存的最大数值
+    const MAX_CHAR_VALUE = 255;
+    // usigned short 能保存的最大数值
+    const MAX_UNSIGNED_SHORT_VALUE = 65535;
+    // 固定包长
+    const PACKEGE_FIXED_LENGTH = 25;
+    
+    /**
+     * [module=>[interface=>time_start, interface=>time_start ...], module=>[interface=>time_start..],..]
+     * @var array
+     */
+    protected static $timeMap = array();
+
+
+    /**
+     * 模块接口上报消耗时间记时
+     * @param string $module
+     * @param string $interface
+     * @return void
+     */
+    public static function tick($module = '', $interface = '')
+    {
+        self::$timeMap[$module][$interface] = microtime(true);
+    }
+
+
+    /**
+     * 模块接口上报统计
+     * 格式:
+     * struct{
+     *     int                                    code,                 // 返回码
+     *     unsigned int                           time,                 // 时间
+     *     float                                  cost_time,            // 消耗时间 单位秒 例如1.xxx
+     *     unsigned int                           source_ip,            // 来源ip
+     *     unsigned int                           target_ip,            // 目标ip
+     *     unsigned char                          success,              // 是否成功
+     *     unsigned char                          module_name_length,   // 模块名字长度
+     *     unsigned char                          interface_name_length,// 接口名字长度
+     *     unsigned short                         msg_length,           // 日志信息长度
+     *     unsigned char[module_name_length]      module,               // 模块名字
+     *     unsigned char[interface_name_length]   interface,            // 接口名字
+     *     char[msg_length]                       msg                   // 日志内容
+     *  }
+     * @param string $module 模块名/类名
+     * @param string $interface 接口名/方法名
+     * @param int $code 返回码
+     * @param string $msg 日志内容
+     * @param bool $success 是否成功
+     * @param string $ip ip1
+     * @param string $source_ip ip2
+     * @return true/false
+     */
+    public static function report($module, $interface, $code = 0, $msg = '', $success = true, $source_ip = '', $target_ip = '')
+    {
+        if(isset(self::$timeMap[$module][$interface]) && self::$timeMap[$module][$interface] > 0)
+        {
+            $time_start = self::$timeMap[$module][$interface];
+            self::$timeMap[$module][$interface] = 0;
+        }
+        else if(isset(self::$timeMap['']['']) && self::$timeMap[''][''] > 0)
+        {
+            $time_start = self::$timeMap[''][''];
+            self::$timeMap[''][''] = 0;
+        }
+        else
+        {
+            $time_start = microtime(true);
+        }
+         
+        if(strlen($module) > self::MAX_CHAR_VALUE)
+        {
+            $module = substr($module, 0, self::MAX_CHAR_VALUE);
+        }
+        if(strlen($interface) > self::MAX_CHAR_VALUE)
+        {
+            $interface = substr($interface, 0, self::MAX_CHAR_VALUE);
+        }
+        $module_name_length = strlen($module);
+        $interface_name_length = strlen($interface);
+        //花费的时间
+        $cost_time = microtime(true) - $time_start;
+        $avalible_size = self::MAX_UDP_PACKGE_SIZE - self::PACKEGE_FIXED_LENGTH - $module_name_length - $interface_name_length;
+        if(strlen($msg) > $avalible_size)
+        {
+            $msg = substr($msg, 0, $avalible_size);
+        }
+         
+        $data = pack("iIfIICCCS",
+                $code,
+                time(),
+                $cost_time,
+                $source_ip ? ip2long($source_ip) : ip2long('127.0.0.1'),
+                $target_ip ? ip2long($target_ip) : ip2long('127.0.0.1'),
+                $success ? 1 : 0,
+                $module_name_length,
+                $interface_name_length,
+                strlen($msg)
+        );
+         
+        return self::sendData($data.$module.$interface.$msg);
+    }
+
+    /**
+     * 发送统计数据到监控进程
+     *
+     * @param string $bin_data
+     * @param string $ip
+     * @param int $port
+     * @param string $protocol upd/tcp
+     * @return bool
+     */
+    private static function sendData($bin_data, $ip = '127.0.0.1', $port = 2207, $protocol = 'udp')
+    {
+        $socket = stream_socket_client("{$protocol}://$ip:{$port}");
+        if(!$socket)
+        {
+            return false;
+        }
+        $len = stream_socket_sendto($socket, $bin_data);
+        return $len == strlen($bin_data);
+    }
+}

+ 58 - 0
Config/main.ini

@@ -0,0 +1,58 @@
+[main]
+;dev or production dev环境中var_dump、echo、php notcie等不会在终端上打印出来
+debug=1
+;保存主进程pid的文件
+pid_file=/var/run/php-server.pid
+;日志文件目录
+log_dir=../logs/
+;共享内存及消息队列用到的key
+ipc_key=0x70010a2e
+;开启共享内存大小
+shm_size=393216
+
+;监控框架的进程,并提供telnet接口
+[Monitor]
+socket[protocol] = tcp
+socket[port] = 10101
+socket[ip] = 0.0.0.0
+socket[persistent] = 1
+children_count=1
+max_requests=10000
+user=root
+preread_length=64
+max_worker_exit_count=2000
+max_mem_limit=83886
+
+[StatisticWorker]
+socket[protocol]=udp
+socket[port]=2207
+children_count=1
+user=www-data
+
+[StatisticService]
+socket[protocol]=tcp
+socket[port]=20202
+children_count=1
+user=www-data
+
+;[FileMonitor]
+;children_count = 1
+
+[EchoWorker]
+socket[protocol] = tcp
+socket[port] = 20304
+socket[persistent] = 0
+children_count=5
+max_requests=10000
+user=www-data
+preread_length=64
+
+[BufferWorker]
+socket[protocol] = tcp
+socket[port] = 20305
+socket[persistent] = 1
+children_count=1
+max_requests=1000000
+user=www-data
+preread_length=15
+

+ 265 - 0
Core/AbstractWorker.php

@@ -0,0 +1,265 @@
+<?php 
+namespace WORKERMAN\Core;
+require_once WORKERMAN_ROOT_DIR . 'Core/Events/Select.php';
+
+/**
+ * 抽象Worker类
+ * 必须实现start方法
+* @author walkor <worker-man@qq.com>
+*/
+abstract class AbstractWorker
+{
+    /**
+     * worker状态 运行中
+     * @var integer
+     */
+    const STATUS_RUNNING = 2;
+    
+    /**
+     * worker状态 停止中
+     * @var integer
+     */
+    const STATUS_SHUTDOWN = 4;
+    
+    /**
+     * 消息队列状态消息类型
+     * @var integer
+     */
+    const MSG_TYPE_STATUS = 1;
+    
+    /**
+     * 消息队列文件监控消息类型
+     * @var integer
+     */
+    const MSG_TYPE_FILE_MONITOR = 2;
+    
+    /**
+     * worker监听端口的Socket
+     * @var resource
+     */
+    protected $mainSocket = null;
+    
+    /**
+     * 当前worker的服务状态
+     * @var integer
+     */
+    protected $workerStatus = self::STATUS_RUNNING;
+    
+    /**
+     * 让该worker实例开始服务
+     *
+     * @return void
+     */
+    abstract public function start();
+    
+    /**
+     * 构造函数,主要是初始化信号处理函数
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->installSignal();
+        $this->addShutdownHook();
+    }
+    
+    /**
+     * 设置监听的socket
+     * @param resource $socket
+     * @return void
+     */
+    public function setListendSocket($socket)
+    {
+        // 初始化
+        $this->mainSocket = $socket;
+        stream_set_blocking($this->mainSocket, 0);
+    }
+    
+    /**
+     * 安装信号处理函数
+     * @return void
+     */
+    protected function installSignal()
+    {
+        // 如果是由worker脚本启动则不安装信号
+        if(!defined('WORKERMAN_PID_FILE'))
+        {
+            return;
+        }
+        // 报告进程状态
+        pcntl_signal(SIGINT, array($this, 'signalHandler'));
+        pcntl_signal(SIGHUP, array($this, 'signalHandler'));
+        // 设置忽略信号
+        pcntl_signal(SIGALRM, SIG_IGN);
+        pcntl_signal(SIGUSR1, SIG_IGN);
+        pcntl_signal(SIGUSR2, SIG_IGN);
+        pcntl_signal(SIGTTIN, SIG_IGN);
+        pcntl_signal(SIGTTOU, SIG_IGN);
+        pcntl_signal(SIGQUIT, SIG_IGN);
+        pcntl_signal(SIGPIPE, SIG_IGN);
+        pcntl_signal(SIGCHLD, SIG_IGN);
+    }
+    
+    /**
+     * 设置server信号处理函数
+     * @param integer $signal
+     * @return void
+     */
+    public function signalHandler($signal)
+    {
+        switch($signal)
+        {
+                // 停止该进程
+            case SIGINT:
+                // 平滑重启
+            case SIGHUP:
+                $this->workerStatus = self::STATUS_SHUTDOWN;
+                break;
+        }
+    }
+    
+    /**
+     * 判断该进程是否收到退出信号,收到信号后要马上退出,否则稍后会被住进成强行杀死
+     * @return boolean
+     */
+    public function hasShutDown()
+    {
+        pcntl_signal_dispatch();
+        return $this->workerStatus == self::STATUS_SHUTDOWN;
+    }
+    
+    /**
+     * 获取主进程统计信息
+     * @return array
+     */
+    protected function getMasterStatus()
+    {
+        if(!Master::getShmId())
+        {
+            return array();
+        }
+        return shm_get_var(Master::getShmId(), Master::STATUS_VAR_ID);
+    }
+    
+    /**
+     * 获取worker与pid的映射关系
+     * @return array ['worker_name1'=>[pid1=>pid1,pid2=>pid2,..], 'worker_name2'=>[pid3,..], ...]
+     */
+    protected function getWorkerPidMap()
+    {
+        $status = $this->getMasterStatus();
+        if(empty($status))
+        {
+            return array();
+        }
+        return $status['pid_map'];
+    }
+    
+    /**
+     * 获取pid与worker的映射关系
+     * @return array  ['pid1'=>'worker_name1','pid2'=>'worker_name2', ...]
+     */
+    protected function getPidWorkerMap()
+    {
+        $pid_worker_map = array();
+        if($worker_pid_map = $this->getWorkerPidMap())
+        {
+            foreach($worker_pid_map as $worker_name=>$pid_array)
+            {
+                foreach($pid_array as $pid)
+                {
+                    $pid_worker_map[$pid] = $worker_name;
+                }
+            }
+        }
+        return $pid_worker_map;
+    }
+    
+    
+    /**
+     * 进程关闭时进行错误检查
+     * @return void
+     */
+    protected function addShutdownHook()
+    {
+        register_shutdown_function(array($this, 'checkErrors'));
+    }
+    
+    /**
+     * 检查错误
+     * @return void
+     */
+    public function checkErrors()
+    {
+        if(self::STATUS_SHUTDOWN != $this->workerStatus) 
+        {
+            $error_msg = "WORKER EXIT UNEXPECTED  ";
+            if($errors = error_get_last())
+            {
+                $error_msg .= $this->getErrorType($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}";
+            }
+            $this->notice($error_msg);
+        }
+    }
+    
+    /**
+     * 获取错误类型对应的意义
+     * @param integer $type
+     * @return string
+     */
+    public function getErrorType($type)
+    {
+        switch($type)
+        {
+            case E_ERROR: // 1 //
+                return 'E_ERROR';
+            case E_WARNING: // 2 //
+                return 'E_WARNING';
+            case E_PARSE: // 4 //
+                return 'E_PARSE';
+            case E_NOTICE: // 8 //
+                return 'E_NOTICE';
+            case E_CORE_ERROR: // 16 //
+                return 'E_CORE_ERROR';
+            case E_CORE_WARNING: // 32 //
+                return 'E_CORE_WARNING';
+            case E_CORE_ERROR: // 64 //
+                return 'E_COMPILE_ERROR';
+            case E_CORE_WARNING: // 128 //
+                return 'E_COMPILE_WARNING';
+            case E_USER_ERROR: // 256 //
+                return 'E_USER_ERROR';
+            case E_USER_WARNING: // 512 //
+                return 'E_USER_WARNING';
+            case E_USER_NOTICE: // 1024 //
+                return 'E_USER_NOTICE';
+            case E_STRICT: // 2048 //
+                return 'E_STRICT';
+            case E_RECOVERABLE_ERROR: // 4096 //
+                return 'E_RECOVERABLE_ERROR';
+            case E_DEPRECATED: // 8192 //
+                return 'E_DEPRECATED';
+            case E_USER_DEPRECATED: // 16384 //
+                return 'E_USER_DEPRECATED';
+        }
+        return "";
+    } 
+
+    /**
+     * 记录日志
+     * @param sring $str
+     * @return void
+     */
+    protected function notice($str, $display = true)
+    {
+        $str = 'Worker['.get_class($this).']:'.$str;
+        Lib\Log::add($str);
+        if($display && Lib\Config::get('debug') == 1)
+        {
+            echo $str."\n";
+        }
+    }
+    
+}
+
+
+

+ 134 - 0
Core/Events/Libevent.php

@@ -0,0 +1,134 @@
+<?php 
+namespace WORKERMAN\Core\Events;
+require_once WORKERMAN_ROOT_DIR . 'Core/Events/interfaces.php';
+/**
+ * 
+ * libevent事件轮询库的封装
+ * Worker类事件的轮询库
+ * 
+ * @author walkor <worker-man@qq.com>
+ */
+class Libevent implements BaseEvent
+{
+    /**
+     * eventBase实例
+     * @var object
+     */
+    public $eventBase = null;
+    
+    /**
+     * 记录所有监听事件 
+     * @var array
+     */
+    public $allEvents = array();
+    
+    /**
+     * 记录信号回调函数
+     * @var array
+     */
+    public $eventSignal = array();
+    
+    /**
+     * 初始化eventBase
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->eventBase = event_base_new();
+    }
+   
+    /**
+     * 添加事件
+     * @see \WORKERMAN\Core\Events\BaseEvent::add()
+     */
+    public function add($fd, $flag, $func, $args = null)
+    {
+        $event_key = (int)$fd;
+        
+        if ($flag == self::EV_SIGNAL)
+        {
+            $real_flag = EV_SIGNAL | EV_PERSIST;
+            // 创建一个用于监听的event
+            $this->eventSignal[$event_key] = event_new();
+            // 设置监听处理函数
+            if(!event_set($this->eventSignal[$event_key], $fd, $real_flag, $func, $args))
+            {
+                return false;
+            }
+            // 设置event base
+            if(!event_base_set($this->eventSignal[$event_key], $this->eventBase))
+            {
+                return false;
+            }
+            // 添加事件
+            if(!event_add($this->eventSignal[$event_key]))
+            {
+                return false;
+            }
+            return true;
+        }
+        
+        $real_flag = EV_READ | EV_PERSIST;
+        
+        // 创建一个用于监听的event
+        $this->allEvents[$event_key][$flag] = event_new();
+        
+        // 设置监听处理函数
+        if(!event_set($this->allEvents[$event_key][$flag], $fd, $real_flag, $func, $args))
+        {
+            return false;
+        }
+        
+        // 设置event base
+        if(!event_base_set($this->allEvents[$event_key][$flag], $this->eventBase))
+        {
+            return false;
+        }
+        
+        // 添加事件
+        if(!event_add($this->allEvents[$event_key][$flag]))
+        {
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * 删除fd的某个事件
+     * @see \WORKERMAN\Core\Events\BaseEvent::del()
+     */
+    public function del($fd ,$flag)
+    {
+        $event_key = (int)$fd;
+        switch($flag)
+        {
+            // 读事件
+            case \WORKERMAN\Core\Events\BaseEvent::EV_READ:
+            case \WORKERMAN\Core\Events\BaseEvent::EV_WRITE:
+                if(isset($this->allEvents[$event_key][$flag]))
+                {
+                    event_del($this->allEvents[$event_key][$flag]);
+                }
+                unset($this->allEvents[$event_key][$flag]);
+            case  \WORKERMAN\Core\Events\BaseEvent::EV_SIGNAL:
+                if(isset($this->eventSignal[$event_key]))
+                {
+                    event_del($this->eventSignal[$event_key]);
+                }
+                unset($this->eventSignal[$event_key]);
+        }
+        return true;
+    }
+
+    /**
+     * 事件轮训主循环
+     * @see \WORKERMAN\Core\Events\BaseEvent::loop()
+     */
+    public function loop()
+    {
+        event_base_loop($this->eventBase);
+    }
+    
+}
+

+ 209 - 0
Core/Events/Select.php

@@ -0,0 +1,209 @@
+<?php 
+namespace WORKERMAN\Core\Events;
+require_once WORKERMAN_ROOT_DIR . 'Core/Events/interfaces.php';
+/**
+ * 
+ * select 轮询封装
+ * 目前master进程使用这个库
+ * 如果没有其它可用库worker进程也会自动使用该库
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+
+class Select implements BaseEvent
+{
+    /**
+     * 记录所有事件处理函数及参数
+     * @var array
+     */
+    public $allEvents = array();
+    
+    /**
+     * 记录所有信号处理函数及参数
+     * @var array
+     */
+    public $signalEvents = array();
+    
+    /**
+     * 监听的读描述符
+     * @var array
+     */
+    public $readFds = array();
+    
+    /**
+     * 监听的写描述符
+     * @var array
+     */
+    public $writeFds = array();
+    
+    /**
+     * 搞个fd,避免 $readFds $writeFds 都为空时select 失败
+     * @var resource
+     */
+    public $channel = null;
+    
+    /**
+     *  读超时 毫秒
+     * @var integer
+     */
+    protected $readTimeout = 1000;
+    
+    /**
+     * 写超时 毫秒
+     * @var integer
+     */
+    protected $writeTimeout = 1000;
+    
+    /**
+     * 超时触发的事件
+     * @var array
+     */
+    protected $selectTimeOutEvent = array();
+    
+    /**
+     * 系统调用被打断触发的事件,一般是收到信号
+     * @var array
+     */
+    protected $selectInterruptEvent = array();
+    
+    /**
+     * 构造函数 创建一个管道,避免select空fd
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->channel = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+        if($this->channel)
+        {
+            stream_set_blocking($this->channel[0], 0);
+            $this->readFds[0] = $this->channel[0];
+        }
+        fclose($this->channel[0]);
+    }
+   
+    /**
+     * 添加事件
+     * @see \WORKERMAN\Core\Events\BaseEvent::add()
+     */
+    public function add($fd, $flag, $func, $args = null)
+    {
+        // key
+        $event_key = (int)$fd;
+        switch ($flag)
+        {
+            // 可读事件
+            case self::EV_READ:
+                $this->allEvents[$event_key][$flag] = array('args'=>$args, 'func'=>$func, 'fd'=>$fd);
+                $this->readFds[$event_key] = $fd;
+                break;
+            // 写事件 目前没用到,未实现
+            case self::EV_WRITE:
+                $this->allEvents[$event_key][$flag] = array('args'=>$args, 'func'=>$func, 'fd'=>$fd);
+                $this->writeFds[$event_key] = $fd;
+                break;
+            // 信号处理事件
+            case self::EV_SIGNAL:
+                $this->signalEvents[$event_key][$flag] = array('args'=>$args, 'func'=>$func, 'fd'=>$fd);
+                pcntl_signal($fd, array($this, 'signalHandler'));
+                break;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * 回调信号处理函数
+     * @param int $signal
+     */
+    public function signalHandler($signal)
+    {
+        call_user_func_array($this->signalEvents[$signal][self::EV_SIGNAL]['func'], array($signal, self::EV_SIGNAL, $signal));
+    }
+    
+    /**
+     * 删除某个fd的某个事件
+     * @see \WORKERMAN\Core\Events\BaseEvent::del()
+     */
+    public function del($fd ,$flag)
+    {
+        $event_key = (int)$fd;
+        switch ($flag)
+        {
+            // 可读事件
+            case self::EV_READ:
+                unset($this->allEvents[$event_key][$flag], $this->readFds[$event_key]);
+                if(empty($this->allEvents[$event_key]))
+                {
+                    unset($this->allEvents[$event_key]);
+                }
+                break;
+            // 可写事件
+            case self::EV_WRITE:
+                unset($this->allEvents[$event_key][$flag], $this->writeFds[$event_key]);
+                if(empty($this->allEvents[$event_key]))
+                {
+                    unset($this->allEvents[$event_key]);
+                }
+                break;
+            // 信号
+            case self::EV_SIGNAL:
+                unset($this->signalEvents[$event_key]);
+                pcntl_signal($fd, SIG_IGN);
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * 事件轮训库主循环
+     * @see \WORKERMAN\Core\Events\BaseEvent::loop()
+     */
+    public function loop()
+    {
+        $e = null;
+        while (1)
+        {
+            $read = $this->readFds;
+            $write = $this->writeFds;
+            // stream_select false:出错 0:超时
+            if(!($ret = @stream_select($read, $write, $e, 1)))
+            {
+                // 超时
+                if($ret === 0)
+                {
+                }
+                // 被系统调用或者信号打断
+                elseif($ret === false)
+                {
+                }
+                // 触发信号处理函数
+                pcntl_signal_dispatch();
+                continue;
+            }
+            // 触发信号处理函数
+            pcntl_signal_dispatch();
+            
+            // 检查所有可读描述符
+            foreach($read as $fd)
+            {
+                $event_key = (int) $fd;
+                if(isset($this->allEvents[$event_key][self::EV_READ]))
+                {
+                    call_user_func_array($this->allEvents[$event_key][self::EV_READ]['func'], array($this->allEvents[$event_key][self::EV_READ]['fd'], self::EV_READ,  $this->allEvents[$event_key][self::EV_READ]['args']));
+                }
+            }
+            
+            // 检查可写描述符,没用到,暂不实现
+            foreach($write as $fd)
+            {
+                $event_key = (int) $fd;
+                if(isset($this->allEvents[$event_key][self::EV_WRITE]))
+                {
+                    // 留空
+                }
+            }
+        }
+    }
+    
+}
+

+ 32 - 0
Core/Events/interfaces.php

@@ -0,0 +1,32 @@
+<?php 
+namespace WORKERMAN\Core\Events;
+/**
+ * 
+ * 事件轮询库的通用接口
+ * 其它事件轮询库需要实现这些接口才能在这个server框架中使用
+ * 目前 Select libevent libev libuv这些事件轮询库已经封装好这些接口可以直接使用
+ * 
+ * @author walkor <worker-man@qq.com>
+ *
+ */
+interface BaseEvent
+{
+    // 数据可读事件
+    const EV_READ = 1;
+    
+    // 数据可写事件
+    const EV_WRITE = 2;
+    
+    // 信号事件
+    const EV_SIGNAL = 4;
+    
+    // 事件添加
+    public function add($fd, $flag, $func);
+    
+    // 事件删除
+    public function del($fd, $flag);
+    
+    // 轮询事件
+    public function loop();
+}
+

+ 245 - 0
Core/Lib/Checker.php

@@ -0,0 +1,245 @@
+<?php
+namespace WORKERMAN\Core\Lib;
+/**
+ * 环境检查相关
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+class Checker
+{
+    
+    /**
+     * 检查启动worker进程的的用户是否合法
+     * @return void
+     */
+    public static function checkWorkerUserName($worker_user)
+    {
+        if($worker_user)
+        {
+            $user_info = posix_getpwnam($worker_user);
+            return !empty($user_info);
+        }
+    }
+    
+    /**
+     * 检查扩展支持情况
+     * @return void
+     */
+    public static function checkExtension()
+    {
+        // 扩展名=>是否是必须
+        $need_map = array(
+                        'posix'     => true,
+                        'pcntl'     => true,
+                        'sysvshm'   => false,
+                        'sysvmsg'   => false,
+                        'libevent'  => false,
+                        'ev'        => false,
+                        'uv'        => false,
+                        'proctitle' => false,
+        );
+    
+        // 检查每个扩展支持情况
+        echo "----------------------EXTENSION--------------------\n";
+        $pad_length = 26;
+        foreach($need_map as $ext_name=>$must_required)
+        {
+            $suport = extension_loaded($ext_name);
+            if($must_required && !$suport)
+            {
+                \WORKERMAN\Core\Master::notice($ext_name. " [NOT SUPORT BUT REQUIRED] \tYou have to compile CLI version of PHP with --enable-{$ext_name} \tServer start fail");
+                exit($ext_name. " \033[31;40m [NOT SUPORT BUT REQUIRED] \033[0m\n\n\033[31;40mYou have to compile CLI version of PHP with --enable-{$ext_name} \033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+            }
+    
+            // 支持扩展
+            if($suport)
+            {
+                echo str_pad($ext_name, $pad_length), "\033[32;40m [OK] \033[0m\n";
+            }
+            // 不支持
+            else
+            {
+                // ev uv inotify不是必须
+                if('ev' == $ext_name || 'uv' == $ext_name || 'proctitle' == $ext_name)
+                {
+                    continue;
+                }
+                echo str_pad($ext_name, $pad_length), "\033[33;40m [NOT SUPORT] \033[0m\n";
+            }
+        }
+    }
+    
+    /**
+     * 检查禁用的函数
+     * @return void
+     */
+    public static function checkDisableFunction()
+    {
+        // 可能禁用的函数
+        $check_func_map = array(
+                        'stream_socket_server',
+                        'stream_socket_client',
+                        'pcntl_signal_dispatch',
+        );
+        if($disable_func_string = ini_get("disable_functions"))
+        {
+            $disable_func_map = array_flip(explode(',', $disable_func_string));
+        }
+        // 遍历查看是否有禁用的函数
+        foreach($check_func_map as $func)
+        {
+            if(isset($disable_func_map[$func]))
+            {
+                \WORKERMAN\Core\Master::notice("Function $func may be disabled\tPlease check disable_functions in php.ini \t Server start fail");
+                exit("\n\033[31;40mFunction $func may be disabled\nPlease check disable_functions in php.ini\033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+            }
+        }
+    }
+    
+    /**
+     * 检查worker配置、worker语法错误等
+     * @return void
+     */
+    public static function checkWorkersConfig()
+    {
+        $pad_length = 26;
+        $total_worker_count = 0;
+        // 检查worker 是否有语法错误
+        echo "----------------------WORKERS--------------------\n";
+        foreach (Config::get('workers') as $worker_name=>$config)
+        {
+            if(isset($config['socket']))
+            {
+                // 端口、协议、进程数等信息
+                if(empty($config['socket']['port']))
+                {
+                    \WORKERMAN\Core\Master::notice(str_pad($worker_name, $pad_length)." [port not set] \t Server start fail");
+                    exit(str_pad($worker_name, $pad_length)."\033[31;40m [port not set] \033[0m\n\n\033[31;40mServer start fail\033[0m\n");
+                }
+                if(empty($config['socket']['protocol']))
+                {
+                    \WORKERMAN\Core\Master::notice(str_pad($worker_name, $pad_length)." [protocol not set]\tServer start fail");
+                    exit(str_pad($worker_name, $pad_length)."\033[31;40m [protocol not set]\033[0m\n\n\033[31;40mServer start fail\033[0m\n");
+                }
+            }
+            if(empty($config['children_count']))
+            {
+                \WORKERMAN\Core\Master::notice(str_pad($worker_name, $pad_length)." [children_count not set]\tServer start fail");
+                exit(str_pad($worker_name, $pad_length)."\033[31;40m [children_count not set]\033[0m\n\n\033[31;40mServer start fail\033[0m\n");
+            }
+    
+            $total_worker_count += $config['children_count'];
+    
+            // 语法检查
+            if(0 != self::checkSyntaxError(WORKERMAN_ROOT_DIR . "Workers/$worker_name.php", $worker_name))
+            {
+                unset(Config::instance()->config['workers'][$worker_name]);
+                \WORKERMAN\Core\Master::notice("$worker_name has Fatal Err");
+                echo str_pad($worker_name, $pad_length),"\033[31;40m [Fatal Err] \033[0m\n";
+                continue;
+            }
+            
+            if(isset($config['user']))
+            {
+                $worker_user = $config['user'];
+                if(!self::checkWorkerUserName($worker_user))
+                {
+                    echo str_pad($worker_name, $pad_length),"\033[31;40m [FAIL] \033[0m\n";
+                    \WORKERMAN\Core\Master::notice("Can not run $worker_name processes as user $worker_user , User $worker_user not exists\tServer start fail");
+                    exit("\n\033[31;40mCan not run $worker_name processes as user $worker_user , User $worker_user not exists\033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+                }
+            }
+            
+            echo str_pad($worker_name, $pad_length),"\033[32;40m [OK] \033[0m\n";
+        }
+    
+        if($total_worker_count > \WORKERMAN\Core\Master::SERVER_MAX_WORKER_COUNT)
+        {
+            \WORKERMAN\Core\Master::notice("Number of worker processes can not be more than " . \WORKERMAN\Core\Master::SERVER_MAX_WORKER_COUNT . ".\tPlease check children_count in " . WORKERMAN_ROOT_DIR . "config/main.php\tServer start fail");
+            exit("\n\033[31;40mNumber of worker processes can not be more than " . \WORKERMAN\Core\Master::SERVER_MAX_WORKER_COUNT . ".\nPlease check children_count in " . WORKERMAN_ROOT_DIR . "config/main.php\033[0m\n\n\033[31;40mServer start fail\033[0m\n");
+        }
+    
+        echo "-------------------------------------------------\n";
+    }
+    
+    /**
+     * 检查worker文件是否有语法错误
+     * @param string $worker_name
+     * @return int 0:无语法错误 其它:可能有语法错误
+     */
+    public static function checkSyntaxError($file, $class_name = null)
+    {
+        $pid = pcntl_fork();
+        // 父进程
+        if($pid > 0)
+        {
+            // 退出状态不为0说明可能有语法错误
+            $pid = pcntl_wait($status);
+            return $status;
+        }
+        // 子进程
+        elseif($pid == 0)
+        {
+            // 载入对应worker
+            require_once $file;
+            if($class_name && !class_exists($class_name))
+            {
+                throw new \Exception("Class $class_name not exists");
+            }
+            exit(0);
+        }
+    }
+    
+    /**
+     * 检查打开文件限制
+     * @return void
+     */
+    public static function checkLimit()
+    {
+        if($limit_info = posix_getrlimit())
+        {
+            if('unlimited' != $limit_info['soft openfiles'] && $limit_info['soft openfiles'] < \WORKERMAN\Core\Master::MIN_SOFT_OPEN_FILES)
+            {
+                echo "Notice : Soft open files now is {$limit_info['soft openfiles']},  We recommend greater than " . \WORKERMAN\Core\Master::MIN_SOFT_OPEN_FILES . "\n";
+            }
+            if('unlimited' != $limit_info['hard filesize'] && $limit_info['hard filesize'] < \WORKERMAN\Core\Master::MIN_SOFT_OPEN_FILES)
+            {
+                echo "Notice : Hard open files now is {$limit_info['hard filesize']},  We recommend greater than " . \WORKERMAN\Core\Master::MIN_HARD_OPEN_FILES . "\n";
+            }
+        }
+    }
+    
+    /**
+     * 检查配置的pid文件是否可写
+     * @return void
+     */
+    public static function checkPidFile()
+    {
+        // 已经有进程pid可能server已经启动
+        if(@file_get_contents(WORKERMAN_PID_FILE))
+        {
+            \WORKERMAN\Core\Master::notice("Server already started", true);
+            exit;
+        }
+        
+        if(is_dir(WORKERMAN_PID_FILE))
+        {
+            exit("\n\033[31;40mpid-file ".WORKERMAN_PID_FILE." is Directory\033[0m\n\n\033[31;40mServer start failed\033[0m\n\n");
+        }
+        
+        $pid_dir = dirname(WORKERMAN_PID_FILE);
+        if(!is_dir($pid_dir))
+        {
+            if(!mkdir($pid_dir, true))
+            {
+                exit("Create dir $pid_dir fail\n");
+            }
+        }
+        
+        if(!is_writeable($pid_dir))
+        {
+            exit("\n\033[31;40mYou should start the server as root\033[0m\n\n\033[31;40mServer start failed\033[0m\n\n");
+        }
+        
+    }
+}

+ 58 - 0
Core/Lib/Config.php

@@ -0,0 +1,58 @@
+<?php
+namespace WORKERMAN\Core\Lib;
+class Config
+{
+    public $filename;
+    public $config;
+    protected static $instances = array();
+
+    private function __construct($domain = 'main') {
+        $folder = WORKERMAN_ROOT_DIR . 'Config';
+        $filename = $folder . '/' . $domain;
+        $filename .= '.ini';
+
+        if (!file_exists($filename)) {
+            throw new \Exception('Configuration file "' . $filename . '" not found');
+        }
+
+        $config = parse_ini_file($filename, true);
+        if (!is_array($config) || empty($config)) {
+            throw new \Exception('Invalid configuration file format');
+        }
+
+        $this->config = $config['main'];
+        unset($config['main']);
+        $this->config['workers'] = $config;
+        $this->filename = realpath($filename);
+    }
+
+    public static function instance($domain = 'main')
+    {
+        if (empty(self::$instances[$domain])) {
+            self::$instances[$domain] = new self($domain);
+        }
+        return self::$instances[$domain];
+    }
+
+    public static function get($uri, $domain = 'main')
+    {
+        $node = self::instance($domain)->config;
+
+        $paths = explode('.', $uri);
+        while (!empty($paths)) {
+            $path = array_shift($paths);
+            if (!isset($node[$path])) {
+                return null;
+            }
+            $node = $node[$path];
+        }
+
+        return $node;
+    }
+    
+    public static function reload()
+    {
+        self::$instances = array();
+    }
+    
+}

+ 110 - 0
Core/Lib/Log.php

@@ -0,0 +1,110 @@
+<?php
+namespace WORKERMAN\Core\Lib;
+/**
+ * 
+ * 日志类
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+class Log 
+{
+    /**
+     * 能捕获的错误抛出的异常的错误码
+     * @var int
+     */
+    const CATCHABLE_ERROR = 505;
+    
+    /**
+     * 初始化
+     * @return bool
+     */
+    public static function init()
+    {
+        set_error_handler(array('\WORKERMAN\Core\Lib\Log', 'errHandle'), E_RECOVERABLE_ERROR | E_USER_ERROR);
+        return self::checkWriteable();
+    }
+    
+    /**
+     * 检查log目录是否可写
+     * @return bool
+     */
+    public static function checkWriteable()
+    {
+        $ok = true;
+        if(!is_dir(WORKERMAN_LOG_DIR))
+        {
+            // 检查log目录是否可读
+            umask(0);
+            if(@mkdir(WORKERMAN_LOG_DIR, 0777) === false)
+            {
+                $ok = false;
+            }
+            @chmod(WORKERMAN_LOG_DIR, 0777);
+        }
+        
+        if(!is_readable(WORKERMAN_LOG_DIR) || !is_writeable(WORKERMAN_LOG_DIR))
+        {
+            $ok = false;
+        }
+        
+        if(!$ok)
+        {
+            $pad_length = 26;
+            Master::notice(WORKERMAN_LOG_DIR." Need to have read and write permissions\tServer start fail");
+            exit("------------------------LOG------------------------\n".str_pad(WORKERMAN_LOG_DIR, $pad_length) . "\033[31;40m [NOT READABLE/WRITEABLE] \033[0m\n\n\033[31;40mDirectory ".WORKERMAN_LOG_DIR." Need to have read and write permissions\033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+        }
+    }
+    
+    /**
+     * 添加日志
+     * @param string $msg
+     * @return void
+     */
+    public static function add($msg)
+    {
+        $log_dir = WORKERMAN_LOG_DIR. '/'.date('Y-m-d');
+        umask(0);
+        // 没有log目录创建log目录
+        if(!is_dir($log_dir))
+        {
+            mkdir($log_dir,  0777, true);
+        }
+        if(!is_readable($log_dir))
+        {
+            return false;
+        }
+        
+        $log_file = $log_dir . "/server.log";
+        file_put_contents($log_file, date('Y-m-d H:i:s') . " " . $msg . "\n", FILE_APPEND);
+    }
+    
+    /**
+     * 错误日志捕捉函数
+     * @param int $errno
+     * @param string $errstr
+     * @param string $errfile
+     * @param int $errline
+     * @return false
+     */
+    public static function errHandle($errno, $errstr, $errfile, $errline)
+    {
+        $err_type_map = array(
+           E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
+           E_USER_ERROR        => 'E_USER_ERROR',
+        );
+        
+        switch ($errno) 
+        {
+            case E_RECOVERABLE_ERROR:
+            case E_USER_ERROR:
+                $msg = "{$err_type_map[$errno]} $errstr";
+                self::add($msg);
+                //trigger_error($errstr);
+                throw new \Exception($msg, self::CATCHABLE_ERROR);
+                break;
+            default :
+                return false;
+        }
+    }
+    
+}

+ 134 - 0
Core/Lib/Task.php

@@ -0,0 +1,134 @@
+<?php
+namespace WORKERMAN\Core\Lib;
+/**
+ * 
+ * 定时任务
+ * 
+ * <b>使用示例:</b>
+ * <pre>
+ * <code>
+ * \WORKERMAN\Core\Lib\Task::init();
+ * \WORKERMAN\Core\Lib\Task::add(5, array('class', 'method'), array($arg1, $arg2..));
+ * <code>
+ * </pre>
+* @author walkor <worker-man@qq.com>
+ */
+class Task 
+{
+    /**
+     * 每个任务定时时长及对应的任务(函数)
+     * [
+     *   run_time => [[$func, $args, $persistent, timelong],[$func, $args, $persistent, timelong],..]],
+     *   run_time => [[$func, $args, $persistent, timelong],[$func, $args, $persistent, timelong],..]],
+     *   .. 
+     * ]
+     * @var array
+     */
+    protected static $tasks = array();
+    
+    
+    /**
+     * 初始化任务
+     * @return void
+     */
+    public static function init($event = null)
+    {
+        pcntl_alarm(1);
+        if($event)
+        {
+            $event->add(SIGALRM, \WORKERMAN\Core\Events\BaseEvent::EV_SIGNAL, array('\WORKERMAN\Core\Lib\Task', 'signalHandle'));
+        }
+        else 
+        {
+            pcntl_signal(SIGALRM, array('\WORKERMAN\Core\Lib\Task', 'signalHandle'), false);
+        }
+    }
+    
+    /**
+     * 捕捉alarm信号
+     * @return void
+     */
+    public static function signalHandle()
+    {
+        self::tick();
+        pcntl_alarm(1);
+    }
+    
+    
+    /**
+     * 
+     * 添加一个任务
+     * 
+     * @param int $time_long 多长时间运行一次 单位秒
+     * @param callback $func 任务运行的函数或方法
+     * @param mix $args 任务运行的函数或方法使用的参数
+     * @return void
+     */
+    public static function add($time_long, $func, $args = array(), $persistent = true)
+    {
+        if($time_long <= 0)
+        {
+            return false;
+        }
+        if(!is_callable($func) && class_exists('Log'))
+        {
+            \WORKERMAN\Core\Lib\Log::add(var_export($func, true). "not callable\n");
+            return false;
+        }
+        $time_now = time();
+        $run_time = $time_now + $time_long;
+        if(!isset(self::$tasks[$run_time]))
+        {
+            self::$tasks[$run_time] = array();
+        }
+        self::$tasks[$run_time][] = array($func, $args, $persistent, $time_long);
+        return true;
+    }
+    
+    
+    /**
+     * 
+     * 定时被调用,用于触发定时任务
+     * 
+     * @return void
+     */
+    public static function tick()
+    {
+        if(empty(self::$tasks))
+        {
+            return;
+        }
+        
+        $time_now = time();
+        foreach (self::$tasks as $run_time=>$task_data)
+        {
+            // 时间到了就运行一下
+            if($time_now >= $run_time)
+            {
+                foreach($task_data as $index=>$one_task)
+                {
+                    $task_func = $one_task[0];
+                    $task_args = $one_task[1];
+                    $persistent = $one_task[2];
+                    $time_long = $one_task[3];
+                    call_user_func_array($task_func, $task_args);
+                    // 持久的放入下一个任务队列
+                    if($persistent)
+                    {
+                        self::add($time_long, $task_func, $task_args);
+                    }
+                }
+                unset(self::$tasks[$run_time]);
+            }
+        }
+    }
+    
+    /**
+     * 删除所有的任务
+     */
+    public static function delAll()
+    {
+        self::$tasks = array();
+    }
+    
+}

+ 897 - 0
Core/Master.php

@@ -0,0 +1,897 @@
+<?php 
+namespace WORKERMAN\Core;
+error_reporting(E_ALL);
+ini_set('display_errors', 'on');
+ini_set('limit_memory','512M');
+date_default_timezone_set('Asia/Shanghai');
+
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Checker.php';
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Config.php';
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Task.php';
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Log.php';
+
+/**
+ * 
+ * 主进程
+ * 
+ * @package Core
+ * 
+* @author walkor <worker-man@qq.com>
+ * <b>使用示例:</b>
+ * <pre>
+ * <code>
+ * Master::run();
+ * <code>
+ * </pre>
+ * 
+ */
+class Master
+{
+    /**
+     * 版本
+     * @var string
+     */
+    const VERSION = '2.0.1';
+    
+    /**
+     * 服务名
+     * @var string
+     */
+    const NAME = 'WorkerMan';
+    
+    /**
+     * 服务状态 启动中
+     * @var integer
+     */ 
+    const STATUS_STARTING = 1;
+    
+    /**
+     * 服务状态 运行中
+     * @var integer
+     */
+    const STATUS_RUNNING = 2;
+    
+    /**
+     * 服务状态 关闭中
+     * @var integer
+     */
+    const STATUS_SHUTDOWN = 4;
+    
+    /**
+     * 服务状态 平滑重启中
+     * @var integer
+     */
+    const STATUS_RESTARTING_WORKERS = 8;
+    
+    /**
+     * 整个服务能够启动的最大进程数
+     * @var integer
+     */
+    const SERVER_MAX_WORKER_COUNT = 5000;
+    
+    /**
+     * 单个进程打开文件数限制
+     * @var integer
+     */
+    const MIN_SOFT_OPEN_FILES = 10000;
+    
+    /**
+     * 单个进程打开文件数限制 硬性限制
+     * @var integer
+     */
+    const MIN_HARD_OPEN_FILES = 10000;
+    
+    /**
+     * 共享内存中用于存储主进程统计信息的变量id
+     * @var integer
+     */
+    const STATUS_VAR_ID = 1;
+    
+    /**
+     * 发送停止命令多久后worker没退出则发送sigkill信号
+     * @var integer
+     */
+    const KILL_WORKER_TIME_LONG = 4;
+    
+    /**
+     * 用于保存所有子进程pid ['worker_name1'=>[pid1=>pid1,pid2=>pid2,..], 'worker_name2'=>[pid3,..], ...]
+     * @var array
+     */
+    protected static $workerPids = array();
+    
+    /**
+     * 服务的状态,默认是启动中
+     * @var integer
+     */
+    protected static $serverStatus = self::STATUS_STARTING;
+    
+    /**
+     * 用来监听端口的Socket数组,用来fork worker使用
+     * @var array
+     */
+    protected static $listenedSockets = array();
+    
+    /**
+     * 要重启的worker的pid数组 [pid1=>time_stamp, pid2=>time_stamp, ..]
+     * @var array
+     */
+    protected static $workerToRestart = array();
+    
+    /**
+     * 共享内存resource id
+     * @var resource
+     */
+    protected static $shmId = 0;
+    
+    /**
+     * 消息队列 resource id
+     * @var resource
+     */
+    protected static $queueId = 0;
+    
+    /**
+     * master进程pid
+     * @var integer
+     */
+    protected static $masterPid = 0;
+    
+    /**
+     * server统计信息 ['start_time'=>time_stamp, 'worker_exit_code'=>['worker_name1'=>[code1=>count1, code2=>count2,..], 'worker_name2'=>[code3=>count3,...], ..] ]
+     * @var array
+     */
+    protected static $serverStatusInfo = array(
+        'start_time' => 0,
+        'worker_exit_code' => array(),
+    );
+    
+    /**
+     * 服务运行
+     * @return void
+     */
+    public static function run()
+    {
+        self::notice("Server is starting ...", true);
+        // 初始化
+        self::init();
+        // 检查环境
+        self::checkEnv();
+        // 变成守护进程
+        self::daemonize();
+        // 保存进程pid
+        self::savePid();
+        // 安装信号
+        self::installSignal();
+        // 创建监听套接字
+        self::createSocketsAndListen();
+        // 创建worker进程
+        self::createWorkers();
+        
+        self::notice("Server start success ...", true);
+        // 标记sever状态为运行中...
+        self::$serverStatus = self::STATUS_RUNNING;
+        // 关闭标准输出
+        self::resetStdFd();
+        // 主循环
+        self::loop();
+    }
+    
+    
+    /**
+     * 初始化 配置、进程名、共享内存、消息队列等
+     * @return void
+     */
+    public static function init()
+    {
+        // 获取配置文件
+        $config_path = Lib\Config::instance()->filename;
+    
+        // 设置进程名称,如果支持的话
+        self::setProcessTitle(self::NAME.':master with-config:' . $config_path);
+        
+        // 初始化共享内存消息队列
+        if(extension_loaded('sysvmsg') && extension_loaded('sysvshm'))
+        {
+            self::$shmId = shm_attach(IPC_KEY, DEFAULT_SHM_SIZE, 0666);
+            self::$queueId = msg_get_queue(IPC_KEY, 0666);
+            msg_set_queue(self::$queueId,array('msg_qbytes'=>65535));
+        }
+    }
+    
+    /**
+     * 检查环境配置
+     * @return void
+     */
+    public static function checkEnv()
+    {
+        Lib\Checker::checkPidFile();
+        
+        // 检查扩展支持情况
+        Lib\Checker::checkExtension();
+        
+        // 检查函数禁用情况
+        Lib\Checker::checkDisableFunction();
+        
+        // 检查log目录是否可读
+        Lib\Log::init();
+        
+        // 检查配置和语法错误等
+        Lib\Checker::checkWorkersConfig();
+        
+        // 检查文件限制
+        Lib\Checker::checkLimit();
+    }
+    
+    /**
+     * 使之脱离终端,变为守护进程
+     * @return void
+     */
+    protected static function daemonize()
+    {
+        // 设置umask
+        umask(0);
+        // fork一次
+        $pid = pcntl_fork();
+        if(-1 == $pid)
+        {
+            // 出错退出
+            exit("Daemonize fail ,can not fork");
+        }
+        elseif($pid > 0)
+        {
+            // 父进程,退出
+            exit(0);
+        }
+        // 子进程使之成为session leader
+        if(-1 == posix_setsid())
+        {
+            // 出错退出
+            exit("Daemonize fail ,setsid fail");
+        }
+    
+        // 再fork一次
+        $pid2 = pcntl_fork();
+        if(-1 == $pid2)
+        {
+            // 出错退出
+            exit("Daemonize fail ,can not fork");
+        }
+        elseif(0 !== $pid2)
+        {
+            // 结束第一子进程,用来禁止进程重新打开控制终端
+            exit(0);
+        }
+    
+        // 记录server启动时间
+        self::$serverStatusInfo['start_time'] = time();
+    }
+    
+    /**
+     * 保存主进程pid
+     */
+    public static function savePid()
+    {
+        // 保存在变量中
+        self::$masterPid = posix_getpid();
+        
+        // 保存到文件中,用于实现停止、重启
+        if(false === @file_put_contents(WORKERMAN_PID_FILE, self::$masterPid))
+        {
+            exit("\033[31;40mCan not save pid to pid-file(" . WORKERMAN_PID_FILE . ")\033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+        }
+        
+        // 更改权限
+        chmod(WORKERMAN_PID_FILE, 0644);
+    }
+    
+    /**
+     * 获取主进程pid
+     * @return int
+     */
+    public static function getMasterPid()
+    {
+        return self::$masterPid;
+    }
+    
+    /**
+     * 根据配置文件,创建监听套接字
+     * @return void
+     */
+    protected static function createSocketsAndListen()
+    {
+        // 循环读取配置创建socket
+        foreach (Lib\Config::get('workers') as $worker_name=>$config)
+        {
+            if(isset($config['socket']))
+            {
+                $flags = $config['socket']['protocol'] == 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+                $ip = isset($config['socket']['ip']) ? $config['socket']['ip'] : "0.0.0.0";
+                $error_no = 0;
+                $error_msg = '';
+                // 创建监听socket
+                self::$listenedSockets[$worker_name] = stream_socket_server("{$config['socket']['protocol']}://{$ip}:{$config['socket']['port']}", $error_no, $error_msg, $flags);
+                if(!self::$listenedSockets[$worker_name])
+                {
+                    Lib\Log::add("can not create socket {$config['socket']['protocol']}://{$ip}:{$config['socket']['port']} info:{$error_no} {$error_msg}\tServer start fail");
+                    exit("\n\033[31;40mcan not create socket {$config['socket']['protocol']}://{$ip}:{$config['socket']['port']} info:{$error_no} {$error_msg}\033[0m\n\n\033[31;40mServer start fail\033[0m\n\n");
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * 根据配置文件创建Workers
+     * @return void
+     */
+    protected static function createWorkers()
+    {
+        // 循环读取配置创建一定量的worker进程
+        foreach (Lib\Config::get('workers') as $worker_name=>$config)
+        {
+            // 初始化
+            if(empty(self::$workerPids[$worker_name]))
+            {
+                self::$workerPids[$worker_name] = array();
+            }
+    
+            while(count(self::$workerPids[$worker_name]) < $config['children_count'])
+            {
+                $pid = self::forkOneWorker($worker_name);
+                // 子进程退出
+                if($pid == 0)
+                {
+                    self::notice("CHILD EXIT ERR");
+                }
+            }
+        }
+    }
+    
+    /**
+     * 创建一个worker进程
+     * @param string $worker_name worker的名称
+     * @return int 父进程:>0得到新worker的pid ;<0 出错; 子进程:始终为0
+     */
+    protected static function forkOneWorker($worker_name)
+    {
+        // 创建子进程
+        $pid = pcntl_fork();
+        
+        // 先处理收到的信号
+        pcntl_signal_dispatch();
+        
+        // 父进程
+        if($pid > 0)
+        {
+            // 初始化master的一些东东
+            self::$workerPids[$worker_name][$pid] = $pid;
+            // 更新进程信息到共享内存
+            self::updateStatusToShm();
+            
+            return $pid;
+        }
+        // 子进程
+        elseif($pid === 0)
+        {
+            // 忽略信号
+            self::ignoreSignal();
+            
+            // 清空任务
+            Lib\Task::delAll();
+            
+            // 关闭不用的监听socket
+            foreach(self::$listenedSockets as $tmp_worker_name => $tmp_socket)
+            {
+                if($tmp_worker_name != $worker_name)
+                {
+                    fclose($tmp_socket);
+                }
+            }
+    
+            // 尝试以指定用户运行worker
+            if($worker_user = Lib\Config::get('workers.' . $worker_name . '.user'))
+            {
+                self::setWorkerUser($worker_user);
+            }
+    
+            // 关闭输出
+            self::resetStdFd();
+    
+            // 尝试设置子进程进程名称
+            self::setWorkerProcessTitle($worker_name);
+    
+            // 创建worker实例
+            include_once WORKERMAN_ROOT_DIR . "Workers/$worker_name.php";
+            $worker = new $worker_name($worker_name);
+            // 如果该worker有配置监听端口,则将监听端口的socket传递给子进程
+            if(isset(self::$listenedSockets[$worker_name]))
+            {
+                $worker->setListendSocket(self::$listenedSockets[$worker_name]);
+            }
+            
+            // 使worker开始服务
+            $worker->start();
+            return 0;
+        }
+        // 出错
+        else
+        {
+            self::notice("create worker fail worker_name:$worker_name detail:pcntl_fork fail");
+            return $pid;
+        }
+    }
+    
+    
+    /**
+     * 安装相关信号控制器
+     * @return void
+     */
+    protected static function installSignal()
+    {
+        // 设置终止信号处理函数
+        pcntl_signal(SIGINT,  array('\WORKERMAN\Core\Master', 'signalHandler'), false);
+        // 设置SIGUSR1信号处理函数,测试用
+        pcntl_signal(SIGUSR1, array('\WORKERMAN\Core\Master', 'signalHandler'), false);
+        // 设置SIGUSR2信号处理函数,平滑重启Server
+        pcntl_signal(SIGHUP, array('\WORKERMAN\Core\Master', 'signalHandler'), false);
+        // 设置子进程退出信号处理函数
+        pcntl_signal(SIGCHLD, array('\WORKERMAN\Core\Master', 'signalHandler'), false);
+    
+        // 设置忽略信号
+        pcntl_signal(SIGPIPE, SIG_IGN);
+        pcntl_signal(SIGTTIN, SIG_IGN);
+        pcntl_signal(SIGTTOU, SIG_IGN);
+        pcntl_signal(SIGQUIT, SIG_IGN);
+        pcntl_signal(SIGALRM, SIG_IGN);
+    }
+    
+    /**
+     * 忽略信号
+     * @return void
+     */
+    protected static function ignoreSignal()
+    {
+        // 设置忽略信号
+        pcntl_signal(SIGPIPE, SIG_IGN);
+        pcntl_signal(SIGTTIN, SIG_IGN);
+        pcntl_signal(SIGTTOU, SIG_IGN);
+        pcntl_signal(SIGQUIT, SIG_IGN);
+        pcntl_signal(SIGALRM, SIG_IGN);
+        pcntl_signal(SIGINT, SIG_IGN);
+        pcntl_signal(SIGUSR1, SIG_IGN);
+        pcntl_signal(SIGHUP, SIG_IGN);
+        pcntl_signal(SIGCHLD, SIG_IGN);
+    }
+    
+    /**
+     * 设置server信号处理函数
+     * @param null $null
+     * @param int $signal
+     * @return void
+     */
+    public static function signalHandler($signal)
+    {
+        switch($signal)
+        {
+            // 停止server信号
+            case SIGINT:
+                self::notice("Server is shutting down");
+                self::stop();
+                break;
+            // 测试用
+            case SIGUSR1:
+                break;
+            // worker退出信号
+            case SIGCHLD:
+                // 不要在这里fork,fork出来的子进程无法收到信号
+                // self::checkWorkerExit();
+                break;
+            // 平滑重启server信号
+            case SIGHUP:
+                Lib\Config::reload();
+                self::notice("Server reloading");
+                self::addToRestartWorkers(array_keys(self::getPidWorkerNameMap()));
+                self::restartWorkers();
+                break;
+        }
+    }
+    
+    /**
+     * 设置子进程进程名称
+     * @param string $worker_name
+     * @return void
+     */
+    public static function setWorkerProcessTitle($worker_name)
+    {
+        if(isset(self::$listenedSockets[$worker_name]))
+        {
+            // 获得socket的信息
+            $sock_name = stream_socket_get_name(self::$listenedSockets[$worker_name], false);
+            
+            // 更改进程名,如果支持的话
+            $mata_data = stream_get_meta_data(self::$listenedSockets[$worker_name]);
+            $protocol = substr($mata_data['stream_type'], 0, 3);
+            self::setProcessTitle(self::NAME.":worker $worker_name {$protocol}://$sock_name");
+        }
+        else
+        {
+            self::setProcessTitle(self::NAME.":worker $worker_name");
+        }
+            
+    }
+    
+    /**
+     * 主进程主循环 主要是监听子进程退出、服务终止、平滑重启信号
+     * @return void
+     */
+    public static function loop()
+    {
+        $siginfo = array();
+        while(1)
+        {
+            @pcntl_sigtimedwait(array(SIGCHLD), $siginfo, 1);
+            // 初始化任务系统
+            Lib\Task::tick();
+            // 检查是否有进程退出
+            self::checkWorkerExit();
+            // 触发信号处理
+            pcntl_signal_dispatch();
+        }
+    }
+    
+    
+    /**
+     * 监控worker进程状态,退出重启
+     * @param resource $channel
+     * @param int $flag
+     * @param int $pid 退出的进程id
+     * @return mixed
+     */
+    public static function checkWorkerExit()
+    {
+        // 由于SIGCHLD信号可能重叠导致信号丢失,所以这里要循环获取所有退出的进程id
+        while(($pid = pcntl_waitpid(-1, $status, WUNTRACED | WNOHANG)) != 0)
+        {
+            // 如果是重启的进程,则继续重启进程
+            if(isset(self::$workerToRestart[$pid]) && self::$serverStatus != self::STATUS_SHUTDOWN)
+            {
+                unset(self::$workerToRestart[$pid]);
+                self::restartWorkers();
+            }
+    
+            // 出错
+            if($pid == -1)
+            {
+                // 没有子进程了,可能是出现Fatal Err 了
+                if(pcntl_get_last_error() == 10)
+                {
+                    self::notice('Server has no workers now');
+                }
+                return -1;
+            }
+    
+            // 查找子进程对应的woker_name
+            $pid_workname_map = self::getPidWorkerNameMap();
+            $worker_name = isset($pid_workname_map[$pid]) ? $pid_workname_map[$pid] : '';
+            // 没找到worker_name说明出错了 哪里来的野孩子?
+            if(empty($worker_name))
+            {
+                self::notice("child exist but not found worker_name pid:$pid");
+                break;
+            }
+    
+            // 进程退出状态不是0,说明有问题了
+            if($status !== 0)
+            {
+                self::notice("worker exit status $status pid:$pid worker:$worker_name");
+            }
+            // 记录进程退出状态
+            self::$serverStatusInfo['worker_exit_code'][$worker_name][$status] = isset(self::$serverStatusInfo['worker_exit_code'][$worker_name][$status]) ? self::$serverStatusInfo['worker_exit_code'][$worker_name][$status] + 1 : 1;
+            // 更新状态到共享内存
+            self::updateStatusToShm();
+            
+            // 清理这个进程的数据
+            self::clearWorker($worker_name, $pid);
+    
+            // 如果服务是不是关闭中
+            if(self::$serverStatus != self::STATUS_SHUTDOWN)
+            {
+                // 重新创建worker
+                self::createWorkers();
+            }
+            // 判断是否都重启完毕
+            else
+            {
+                $all_worker_pid = self::getPidWorkerNameMap();
+                if(empty($all_worker_pid))
+                {
+                    // 删除共享内存
+                    self::removeShmAndQueue();
+                    // 发送提示
+                    self::notice("Server stoped");
+                    // 删除pid文件
+                    @unlink(WORKERMAN_PID_FILE);
+                    exit(0);
+                }
+            }//end if
+        }//end while
+    }
+    
+    /**
+     * 获取pid 到 worker_name 的映射
+     * @return array ['pid1'=>'worker_name1','pid2'=>'worker_name2', ...]
+     */
+    public static function getPidWorkerNameMap()
+    {
+        $all_pid = array();
+        foreach(self::$workerPids as $worker_name=>$pid_array)
+        {
+            foreach($pid_array as $pid)
+            {
+                $all_pid[$pid] = $worker_name;
+            }
+        }
+        return $all_pid;
+    }
+    
+    /**
+     * 放入重启队列中
+     * @param array $restart_pids
+     * @return void
+     */
+    public static function addToRestartWorkers($restart_pids)
+    {
+        if(!is_array($restart_pids))
+        {
+            self::notice("addToRestartWorkers(".var_export($restart_pids, true).") \$restart_pids not array");
+            return false;
+        }
+    
+        // 将pid放入重启队列
+        foreach($restart_pids as $pid)
+        {
+            if(!isset(self::$workerToRestart[$pid]))
+            {
+                // 重启时间=0
+                self::$workerToRestart[$pid] = 0;
+            }
+        }
+    }
+    
+    /**
+     * 重启workers
+     * @return void
+     */
+    public static function restartWorkers()
+    {
+        // 标记server状态
+        if(self::$serverStatus != self::STATUS_RESTARTING_WORKERS && self::$serverStatus != self::STATUS_SHUTDOWN)
+        {
+            self::$serverStatus = self::STATUS_RESTARTING_WORKERS;
+        }
+    
+        // 没有要重启的进程了
+        if(empty(self::$workerToRestart))
+        {
+            self::$serverStatus = self::STATUS_RUNNING;
+            self::notice("\nWorker Restart Success");
+            return true;
+        }
+    
+        // 遍历要重启的进程 标记它们重启时间
+        foreach(self::$workerToRestart as $pid => $stop_time)
+        {
+            if($stop_time == 0)
+            {
+                self::$workerToRestart[$pid] = time();
+                posix_kill($pid, SIGHUP);
+                Lib\Task::add(self::KILL_WORKER_TIME_LONG, array('\WORKERMAN\Core\Master', 'forceKillWorker'), array($pid), false);
+                break;
+            }
+        }
+    }
+    
+    /**
+     * worker进程退出时,master进程的一些清理工作
+     * @param string $worker_name
+     * @param int $pid
+     * @return void
+     */
+    protected static function clearWorker($worker_name, $pid)
+    {
+        // 释放一些不用了的数据
+        unset(self::$workerToRestart[$pid], self::$workerPids[$worker_name][$pid]);
+    }
+    
+    /**
+     * 停止服务
+     * @return void
+     */
+    public static function stop()
+    {
+        
+        // 如果没有子进程则直接退出
+        $all_worker_pid = self::getPidWorkerNameMap();
+        if(empty($all_worker_pid))
+        {
+            exit(0);
+        }
+    
+        // 标记server开始关闭
+        self::$serverStatus = self::STATUS_SHUTDOWN;
+    
+        // killWorkerTimeLong 秒后如果还没停止则强制杀死所有进程
+        Lib\Task::add(self::KILL_WORKER_TIME_LONG, array('\WORKERMAN\Core\Master', 'stopAllWorker'), array(true), false);
+    
+        // 停止所有worker
+        self::stopAllWorker();
+    }
+    
+    /**
+     * 停止所有worker
+     * @param bool $force 是否强制退出
+     * @return void
+     */
+    public static function stopAllWorker($force = false)
+    {
+        // 获得所有pid
+        $all_worker_pid = self::getPidWorkerNameMap();
+    
+        // 强行杀死?
+        if($force)
+        {
+            // 杀死所有子进程
+            foreach($all_worker_pid as $pid=>$worker_name)
+            {
+                // 发送kill信号
+                self::forceKillWorker($pid);
+                self::notice("Kill workers($worker_name) force!");
+            }
+        }
+        else
+        {
+            // 向所有子进程发送终止信号
+            foreach($all_worker_pid as $pid=>$worker_name)
+            {
+                // 发送SIGINT信号
+                posix_kill($pid, SIGINT);
+            }
+        }
+    }
+    
+    
+    /**
+     * 强制杀死进程
+     * @param int $pid
+     * @return void
+     */
+    public static function forceKillWorker($pid)
+    {
+        if(posix_kill($pid, 0))
+        {
+            self::notice("Kill workers $pid force!");
+            posix_kill($pid, SIGKILL);
+        }
+    }
+    
+    
+    /**
+     * 设置运行用户
+     * @param string $worker_user
+     * @return void
+     */
+    protected static function setWorkerUser($worker_user)
+    {
+        $user_info = posix_getpwnam($worker_user);
+        // 尝试设置gid uid
+        if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid']))
+        {
+            $notice = 'Notice : Can not run woker as '.$worker_user." , You shuld be root\n";
+            self::notice($notice, true);
+        }
+    }
+    
+    /**
+     * 获取共享内存资源id
+     * @return resource
+     */
+    public static function getShmId()
+    {
+        return self::$shmId;
+    }
+    
+    /**
+     * 获取消息队列资源id
+     * @return resource
+     */
+    public static function getQueueId()
+    {
+        return self::$queueId;
+    }
+    
+    
+    /**
+     * 关闭标准输入输出
+     * @return void
+     */
+    protected static function resetStdFd()
+    {
+        // 开发环境不关闭标准输出,用于调试
+        if(Lib\Config::get('debug') == 1 && posix_ttyname(STDOUT))
+        {
+            return;
+        }
+        global $STDOUT, $STDERR;
+        @fclose(STDOUT);
+        @fclose(STDERR);
+        // 将标准输出重定向到/dev/null
+        $STDOUT = fopen('/dev/null',"rw+");
+        $STDERR = fopen('/dev/null',"rw+");
+    }
+    
+    /**
+     * 更新主进程收集的状态信息到共享内存
+     * @return bool
+     */
+    protected static function updateStatusToShm()
+    {
+        if(!self::$shmId)
+        {
+            return true;
+        }
+        return shm_put_var(self::$shmId, self::STATUS_VAR_ID, array_merge(self::$serverStatusInfo, array('pid_map'=>self::$workerPids)));
+    }
+    
+    /**
+     * 销毁共享内存以及消息队列
+     * @return void
+     */
+    protected static function removeShmAndQueue()
+    {
+        if(self::$shmId)
+        {
+            shm_remove(self::$shmId);
+        }
+        if(self::$queueId)
+        {
+            msg_remove_queue(self::$queueId);
+        }
+    }
+    
+    /**
+     * 设置进程名称,需要proctitle支持 或者php>=5.5
+     * @param string $title
+     * @return void
+     */
+    protected static function setProcessTitle($title)
+    {
+        // >=php 5.5
+        if (version_compare(phpversion(), "5.5", "ge") && function_exists('cli_set_process_title'))
+        {
+            cli_set_process_title($title);
+        }
+        // 需要扩展
+        elseif(extension_loaded('proctitle') && function_exists('setproctitle'))
+        {
+            setproctitle($title);
+        }
+    }
+    
+    /**
+     * notice,记录到日志
+     * @param string $msg
+     * @param bool $display
+     * @return void
+     */
+    public static function notice($msg, $display = false)
+    {
+        Lib\Log::add("Server:".$msg);
+        if($display)
+        {
+            if(self::$serverStatus == self::STATUS_STARTING)
+            {
+                echo($msg."\n");
+            }
+        }
+    }
+}
+

+ 770 - 0
Core/SocketWorker.php

@@ -0,0 +1,770 @@
+<?php
+namespace WORKERMAN\Core;
+require_once WORKERMAN_ROOT_DIR . 'Core/Events/Select.php';
+require_once WORKERMAN_ROOT_DIR . 'Core/AbstractWorker.php';
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Config.php';
+
+/**
+ * SocketWorker 监听某个端口,对外提供网络服务的worker
+ * 
+* @author walkor <worker-man@qq.com>
+* 
+ * <b>使用示例:</b>
+ * <pre>
+ * <code>
+ * $worker = new SocketWorker();
+ * $worker->start();
+ * <code>
+ * </pre>
+ */
+
+abstract class SocketWorker extends AbstractWorker
+{
+    
+    /**
+     * udp最大包长 linux:65507 mac:9216
+     * @var integer
+     */ 
+    const MAX_UDP_PACKEG_SIZE = 65507;
+    
+    /**
+     * 停止服务后等待EXIT_WAIT_TIME秒后还没退出则强制退出
+     * @var integer
+     */
+    const EXIT_WAIT_TIME = 3;
+    
+    /**
+     * 进程意外退出状态码
+     * @var integer
+     */ 
+    const EXIT_UNEXPECT_CODE = 119;
+    
+    /**
+     * worker的传输层协议
+     * @var string
+     */
+    protected $protocol = "tcp";
+    
+    /**
+     * worker监听端口的Socket
+     * @var resource
+     */
+    protected $mainSocket = null;
+    
+    /**
+     * worker接受的所有链接
+     * @var array
+     */
+    protected $connections = array();
+    
+    /**
+     * worker的所有读buffer
+     * @var array
+     */
+    protected $recvBuffers = array();
+    
+    /**
+     * 当前处理的fd
+     * @var integer
+     */
+    protected $currentDealFd = 0;
+    
+    /**
+     * UDP当前处理的客户端地址
+     * @var string
+     */
+    protected $currentClientAddress = '';
+    
+    /**
+     * worker的服务状态
+     * @var integer
+     */
+    protected $workerStatus = self::STATUS_RUNNING;
+    
+    /**
+     * 是否是长链接,(短连接每次请求后服务器主动断开,长连接一般是客户端主动断开)
+     * @var bool
+     */
+    protected $isPersistentConnection = false;
+    
+    /**
+     * 事件轮询库的名称
+     * @var string
+     */
+    protected $eventLoopName ="\\WORKERMAN\\Core\\Events\\Select";
+    
+    /**
+     * 时间轮询库实例
+     * @var object
+     */
+    protected $event = null;
+    
+    /**
+     * worker名称
+     * @var string
+     */
+    protected $workerName = __CLASS__;
+    
+    /**
+     * 该worker进程处理多少请求后退出,0表示不自动退出
+     * @var integer
+     */
+    protected $maxRequests = 0;
+    
+    /**
+     * 预读长度
+     * @var integer
+     */
+    protected $prereadLength = 4;
+    
+    /**
+     * 该进程使用的php文件
+     * @var array
+     */
+    protected $includeFiles = array();
+    
+    /**
+     * 统计信息
+     * @var array
+     */
+    protected $statusInfo = array(
+        'start_time'      => 0, // 该进程开始时间戳
+        'total_request'   => 0, // 该进程处理的总请求数
+        'recv_timeout'    => 0, // 该进程接收数据超时总数
+        'proc_timeout'    => 0, // 该进程逻辑处理超时总数
+        'packet_err'      => 0, // 该进程收到错误数据包的总数
+        'throw_exception' => 0, // 该进程逻辑处理时收到异常的总数
+        'thunder_herd'    => 0, // 该进程受惊群效应影响的总数
+        'client_close'    => 0, // 客户端提前关闭链接总数
+        'send_fail'       => 0, // 发送数据给客户端失败总数
+    );
+    
+    
+    /**
+     * 用户worker继承此worker类必须实现该方法,根据具体协议和当前收到的数据决定是否继续收包
+     * @param string $recv_str 收到的数据包
+     * @return int/false 返回0表示接收完毕/>0表示还有多少字节没有接收到/false出错
+     */
+    abstract public function dealInput($recv_str);
+    
+    
+    /**
+     * 用户worker继承此worker类必须实现该方法,根据包中的数据处理逻辑
+     * 逻辑处理
+     * @param string $recv_str 收到的数据包
+     * @return void
+     */
+    abstract public function dealProcess($recv_str);
+    
+    
+    /**
+     * 构造函数
+     * @param int $port
+     * @param string $ip
+     * @param string $protocol
+     * @return void
+     */
+    public function __construct($worker_name = '')
+    {
+        // worker name
+        if(!empty($worker_name))
+        {
+            $this->workerName = $worker_name;
+        }
+        else
+        {
+            $this->workerName = get_class($this);
+        }
+        
+        // 是否开启长连接
+        $this->isPersistentConnection = (bool)Lib\Config::get('workers.' . $worker_name . '.socket.persistent');
+        // 最大请求数,如果没有配置则使用PHP_INT_MAX
+        $this->maxRequests = (int)Lib\Config::get('workers.' . $worker_name . '.max_requests');
+        $this->maxRequests = $this->maxRequests <= 0 ? PHP_INT_MAX : $this->maxRequests;
+
+        $preread_length = (int)Lib\Config::get('workers.' . $worker_name . '.preread_length');
+        if($preread_length > 0)
+        {
+            $this->prereadLength = $preread_length;
+        }
+        elseif(!$this->isPersistentConnection)
+        {
+            $this->prereadLength = 65535;
+        }
+        
+        // worker启动时间
+        $this->statusInfo['start_time'] = time();
+        
+        //事件轮询库
+        if(extension_loaded('libevent'))
+        {
+            $this->setEventLoopName('Libevent');
+        }
+        
+        // 检查退出状态
+        $this->addShutdownHook();
+        
+        // 初始化事件轮询库
+        // $this->event = new Libevent();
+        // $this->event = new Select();
+        $this->event = new $this->eventLoopName();
+    }
+    
+    
+    /**
+     * 让该worker实例开始服务
+     *
+     * @return void
+     */
+    public function start()
+    {
+        // 安装信号处理函数
+        $this->installSignal();
+        
+        // 触发该worker进程onStart事件,该进程整个生命周期只触发一次
+        if($this->onStart())
+        {
+            return;
+        }
+
+        if($this->protocol == 'udp')
+        {
+            // 添加读udp事件
+            $this->event->add($this->mainSocket,  Events\BaseEvent::EV_READ, array($this, 'recvUdp'));
+        }
+        else
+        {
+            // 添加accept事件
+            $ret = $this->event->add($this->mainSocket,  Events\BaseEvent::EV_READ, array($this, 'accept'));
+        }
+        
+        // 主体循环,整个子进程会阻塞在这个函数上
+        $ret = $this->event->loop();
+        $this->notice("evet->loop returned " . var_export($ret, true));
+        
+        exit(self::EXIT_UNEXPECT_CODE);
+    }
+    
+    /**
+     * 停止服务
+     * @param bool $exit 是否退出
+     * @return void
+     */
+    public function stop($exit = true)
+    {
+        // 触发该worker进程onStop事件
+        if($this->onStop())
+        {
+            return;
+        }
+        
+        // 标记这个worker开始停止服务
+        if($this->workerStatus != self::STATUS_SHUTDOWN)
+        {
+            // 停止接收连接
+            $this->event->del($this->mainSocket, Events\BaseEvent::EV_READ);
+            fclose($this->mainSocket);
+            $this->workerStatus = self::STATUS_SHUTDOWN;
+        }
+        
+        // 没有链接要处理了
+        if($this->allTaskHasDone())
+        {
+            if($exit)
+            {
+                exit(0);
+            }
+        }
+    }
+    
+    /**
+     * 设置worker监听的socket
+     * @param resource $socket
+     * @return void
+     */
+    public function setListendSocket($socket)
+    {
+        // 初始化
+        $this->mainSocket = $socket;
+        // 设置监听socket非阻塞
+        stream_set_blocking($this->mainSocket, 0);
+        // 获取协议
+        $mata_data = stream_get_meta_data($socket);
+        $this->protocol = substr($mata_data['stream_type'], 0, 3);
+        
+    }
+    
+    /**
+     * 设置worker的事件轮询库的名称
+     * @param string 
+     * @return void
+     */
+    public function setEventLoopName($event_loop_name)
+    {
+        $this->eventLoopName = "\\WORKERMAN\\Core\\Events\\".$event_loop_name;
+        require_once WORKERMAN_ROOT_DIR . 'Core/Events/'.ucfirst(str_replace('WORKERMAN', '', $event_loop_name)).'.php';
+    }
+    
+    /**
+     * 接受一个链接
+     * @param resource $socket
+     * @param $null_one $flag
+     * @param $null_two $base
+     * @return void
+     */
+    public function accept($socket, $null_one = null, $null_two = null)
+    {
+        // 获得一个连接
+        $new_connection = @stream_socket_accept($socket, 0);
+        // 可能是惊群效应
+        if(false === $new_connection)
+        {
+            $this->statusInfo['thunder_herd']++;
+            return false;
+        }
+        
+        // 接受请求数加1
+        $this->statusInfo['total_request'] ++;
+        
+        // 连接的fd序号
+        $fd = (int) $new_connection;
+        $this->connections[$fd] = $new_connection;
+        $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>$this->prereadLength);
+        
+        // 非阻塞
+        stream_set_blocking($this->connections[$fd], 0);
+        $this->event->add($this->connections[$fd], Events\BaseEvent::EV_READ , array($this, 'dealInputBase'), $fd);
+        return $new_connection;
+    }
+    
+    /**
+     * 接收Udp数据 
+     * 如果数据超过一个udp包长,需要业务自己解析包体,判断数据是否全部到达
+     * @param resource $socket
+     * @param $null_one $flag
+     * @param $null_two $base
+     * @return void
+     */
+    public function recvUdp($socket, $null_one = null, $null_two = null)
+    {
+         $data = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $address);
+         // 可能是惊群效应
+         if(false === $data || empty($address))
+         {
+             $this->statusInfo['thunder_herd']++;
+             return false;
+         }
+         
+         // 接受请求数加1
+         $this->statusInfo['total_request'] ++;
+         
+         $this->currentClientAddress = $address;
+         if(0 === $this->dealInput($data))
+         {
+             $this->dealProcess($data);
+         }
+    }
+    
+    /**
+     * 处理受到的数据
+     * @param event_buffer $event_buffer
+     * @param int $fd
+     * @return void
+     */
+    public function dealInputBase($connection, $flag, $fd = null)
+    {
+        $this->currentDealFd = $fd;
+        $buffer = stream_socket_recvfrom($connection, $this->recvBuffers[$fd]['remain_len']);
+        // 出错了
+        if('' == $buffer)
+        {
+            if(feof($connection))
+            {
+                // 客户端提前断开链接
+                $this->statusInfo['client_close']++;
+                // 如果该链接对应的buffer有数据,说明放生错误
+                if(!empty($this->recvBuffers[$fd]['buf']))
+                {
+                    $this->notice("CLIENT_CLOSE\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:[".var_export($this->recvBuffers[$fd]['buf'],true)."]\n");
+                }
+            }
+            else
+            {
+                // 超时了
+                $this->statusInfo['recv_timeout']++;
+                // 如果该链接对应的buffer有数据,说明放生错误
+                if(!empty($this->recvBuffers[$fd]['buf']))
+                {
+                    $this->notice("RECV_TIMEOUT\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:[".var_export($this->recvBuffers[$fd]['buf'],true)."]\n");
+                }
+            }
+            
+            // 关闭链接
+            $this->closeClient($fd);
+            if($this->workerStatus == self::STATUS_SHUTDOWN)
+            {
+                $this->stop();
+            }
+            return;
+        }
+        
+        $this->recvBuffers[$fd]['buf'] .= $buffer;
+        
+        $remain_len = $this->dealInput($this->recvBuffers[$fd]['buf']);
+        // 包接收完毕
+        if(0 === $remain_len)
+        {
+            // 执行处理
+            try{
+                // 业务处理
+                $this->dealProcess($this->recvBuffers[$fd]['buf']);
+            }
+            catch(\Exception $e)
+            {
+                // 关闭闹钟
+                //pcntl_alarm(0);
+                $this->notice('CODE:' . $e->getCode() . ' MESSAGE:' . $e->getMessage()."\n".$e->getTraceAsString()."\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:[".var_export($this->recvBuffers[$fd]['buf'],true)."]\n");
+                $this->statusInfo['throw_exception'] ++;
+                $this->sendToClient($e->getMessage());
+            }
+            
+            // 是否是长连接
+            if($this->isPersistentConnection)
+            {
+                // 清空缓冲buffer
+                $this->recvBuffers[$fd] = array('buf'=>'', 'remain_len'=>$this->prereadLength);
+            }
+            else
+            {
+                // 关闭链接
+                $this->closeClient($fd);
+            }
+        }
+        // 出错
+        else if(false === $remain_len)
+        {
+            // 出错
+            $this->statusInfo['packet_err']++;
+            $this->sendToClient('packet_err:'.$this->recvBuffers[$fd]['buf']);
+            $this->notice("PACKET_ERROR\nCLIENT_IP:".$this->getRemoteIp()."\nBUFFER:[".var_export($this->recvBuffers[$fd]['buf'],true)."]\n");
+            $this->closeClient($fd);
+        }
+        else 
+        {
+            $this->recvBuffers[$fd]['remain_len'] = $remain_len;
+        }
+
+        // 检查是否是关闭状态或者是否到达请求上限
+        if($this->workerStatus == self::STATUS_SHUTDOWN || $this->statusInfo['total_request'] >= $this->maxRequests)
+        {
+            // 关闭链接
+            if($this->isPersistentConnection)
+            {
+                $this->closeClient($fd);
+            }
+            // 停止服务
+            $this->stop();
+            // EXIT_WAIT_TIME秒后退出进程
+            pcntl_alarm(self::EXIT_WAIT_TIME);
+        }
+    }
+    
+    /**
+     * 根据fd关闭链接
+     * @param int $fd
+     * @return void
+     */
+    protected function closeClient($fd)
+    {
+        // udp忽略
+        if($this->protocol != 'udp')
+        {
+            $this->event->del($this->connections[$fd], Events\BaseEvent::EV_READ);
+            fclose($this->connections[$fd]);
+            unset($this->connections[$fd], $this->recvBuffers[$fd]);
+        }
+    }
+    
+    /**
+     * 安装信号处理函数
+     * @return void
+     */
+    protected function installSignal()
+    {
+        // 如果是由worker脚本启动则不安装信号
+        if(!defined('WORKERMAN_PID_FILE'))
+        {
+            return;
+        }
+        
+        // 闹钟信号
+        $this->event->add(SIGALRM, Events\BaseEvent::EV_SIGNAL, array($this, 'signalHandler'), SIGALRM);
+        // 终止进程信号
+        $this->event->add(SIGINT, Events\BaseEvent::EV_SIGNAL, array($this, 'signalHandler'), SIGINT);
+        // 平滑重启信号
+        $this->event->add(SIGHUP, Events\BaseEvent::EV_SIGNAL, array($this, 'signalHandler'), SIGHUP);
+        // 报告进程状态
+        $this->event->add(SIGUSR1, Events\BaseEvent::EV_SIGNAL, array($this, 'signalHandler'), SIGUSR1);
+        // 报告该进程使用的文件
+        $this->event->add(SIGUSR2, Events\BaseEvent::EV_SIGNAL, array($this, 'signalHandler'), SIGUSR2);
+        
+        // 设置忽略信号
+        pcntl_signal(SIGTTIN, SIG_IGN);
+        pcntl_signal(SIGTTOU, SIG_IGN);
+        pcntl_signal(SIGQUIT, SIG_IGN);
+        pcntl_signal(SIGPIPE, SIG_IGN);
+        pcntl_signal(SIGCHLD, SIG_IGN);
+    }
+    
+    /**
+     * 设置server信号处理函数
+     * @param null $null
+     * @param int $signal
+     */
+    public function signalHandler($signal, $null = null, $null = null)
+    {
+        switch($signal)
+        {
+            // 时钟处理函数
+            case SIGALRM:
+                // 停止服务后EXIT_WAIT_TIME秒还没退出则强制退出
+                if($this->workerStatus == self::STATUS_SHUTDOWN)
+                {
+                    exit(0);
+                }
+                break;
+            // 停止该进程
+            case SIGINT:
+            // 平滑重启
+            case SIGHUP:
+                $this->stop();
+                // EXIT_WAIT_TIME秒后退出进程
+                pcntl_alarm(self::EXIT_WAIT_TIME);
+                break;
+            // 报告进程状态
+            case SIGUSR1:
+                $this->writeStatusToQueue();
+                break;
+            // 报告进程使用的php文件
+            case SIGUSR2:
+                $this->writeFilesListToQueue();
+                break;
+        }
+    }
+    
+    /**
+     * 发送数据到客户端
+     * @return bool
+     */
+    public function sendToClient($str_to_send)
+    {
+        // tcp
+        if($this->protocol != 'udp')
+        {
+            // tcp 如果一次没写完(一般是缓冲区满的情况),则阻塞写
+            if(!$this->blockWrite($this->connections[$this->currentDealFd], $str_to_send, 500))
+            {
+                $this->notice('sendToClient fail ,Data length = ' . strlen($str_to_send));
+                $this->statusInfo['send_fail']++;
+                return false;
+            }
+            return true;
+        }
+        // udp 直接发送,要求数据包不能超过65515
+        $len = stream_socket_sendto($this->mainSocket, $str_to_send, 0, $this->currentClientAddress);
+        return $len == strlen($str_to_send);
+    }
+    
+    /**
+     * 向fd写数据,如果socket缓冲区满了,则改用阻塞模式写数据
+     * @param resource $fd
+     * @param string $str_to_write
+     * @param int $time_out 单位毫秒
+     * @return bool
+     */
+    protected function blockWrite($fd, $str_to_write, $timeout_ms = 500)
+    {
+        $send_len = @fwrite($fd, $str_to_write);
+        if($send_len == strlen($str_to_write))
+        {
+            return true;
+        }
+        
+        // 客户端关闭
+        if(feof($fd))
+        {
+            $this->notice("blockWrite client close");
+            return false;
+        }
+        
+        // 设置阻塞
+        stream_set_blocking($fd, 1);
+        // 设置超时
+        $timeout_sec = floor($timeout_ms/1000);
+        $timeout_ms = $timeout_ms%1000;
+        stream_set_timeout($fd, $timeout_sec, $timeout_ms*1000);
+        $send_len += @fwrite($fd, substr($str_to_write, $send_len));
+        // 改回非阻塞
+        stream_set_blocking($fd, 0);
+        
+        return $send_len == strlen($str_to_write);
+    }
+    
+    /**
+     * 获取客户端ip
+     * @param int $fd 已经链接的socket id
+     * @return string
+     */
+    public function getRemoteIp($fd = null)
+    {
+        if(empty($fd))
+        {
+            if(!isset($this->connections[$this->currentDealFd]))
+            {
+                return '0.0.0.0';
+            }
+            $fd = $this->currentDealFd;
+        }
+        
+        $ip = '';
+        if($this->protocol == 'udp')
+        {
+            $sock_name = $this->currentClientAddress;
+        }
+        else
+        {
+            $sock_name = stream_socket_get_name($this->connections[$fd], true);
+        }
+        
+        if($sock_name)
+        {
+            $tmp = explode(':', $sock_name);
+            $ip = $tmp[0];
+        }
+        
+        return $ip;
+    }
+    
+    /**
+     * 获取本地ip
+     * @return string 
+     */
+    public function getLocalIp()
+    {
+        $ip = '';
+        $sock_name = '';
+        if($this->protocol == 'udp' || !isset($this->connections[$this->currentDealFd]))
+        {
+            $sock_name = stream_socket_get_name($this->mainSocket, false);
+        }
+        else
+        {
+            $sock_name = stream_socket_get_name($this->connections[$this->currentDealFd], false);
+        }
+        
+        if($sock_name)
+        {
+            $tmp = explode(':', $sock_name);
+            $ip = $tmp[0];
+        }
+        
+        if(empty($ip) || '127.0.0.1' == $ip)
+        {
+            $ip = gethostbyname(trim(`hostname`));
+        }
+        
+        return $ip;
+    }
+    
+    /**
+     * 将当前worker进程状态写入消息队列
+     * @return void
+     */
+    protected function writeStatusToQueue()
+    {
+        if(!Master::getQueueId())
+        {
+            return;
+        }
+        $error_code = 0;
+        msg_send(Master::getQueueId(), self::MSG_TYPE_STATUS, array_merge($this->statusInfo, array('memory'=>memory_get_usage(true), 'pid'=>posix_getpid(), 'worker_name' => $this->workerName)), true, false, $error_code);
+    }
+    
+    /**
+     * 开发环境将当前进程使用的文件写入消息队列,用于FileMonitor监控文件更新
+     * @return void
+     */
+    protected function writeFilesListToQueue()
+    {
+        if(!Master::getQueueId())
+        {
+            return;
+        }
+        $error_code = 0;
+        $flip_file_list = array_flip(get_included_files());
+        $file_list = array_diff_key($flip_file_list, $this->includeFiles);
+        $this->includeFiles = $flip_file_list;
+        if($file_list)
+        {
+            msg_send(Master::getQueueId(), self::MSG_TYPE_FILE_MONITOR, array_keys($file_list), true, false, $error_code);
+        }
+    }
+    
+    /**
+     * 是否所有任务都已经完成
+     * @return bool
+     */
+    protected function allTaskHasDone()
+    {
+        // 如果是长链接并且没有要处理的数据则是任务都处理完了
+        return $this->noConnections() || ($this->isPersistentConnection && $this->allBufferIsEmpty());
+    }
+    
+    /**
+     * 检查是否所有的链接的缓冲区都是空
+     * @return bool
+     */
+    protected function allBufferIsEmpty()
+    {
+        foreach($this->recvBuffers as $fd => $buf)
+        {
+            if(!empty($buf['buf']))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * 该进程收到的任务是否都已经完成,重启进程时需要判断
+     * @return bool
+     */
+    protected function noConnections()
+    {
+        return empty($this->connections);
+    }
+    
+    
+    /**
+     * 该worker进程开始服务的时候会触发一次,可以在这里做一些全局的事情
+     * @return bool
+     */
+    protected function onStart()
+    {
+        return false;
+    }
+    
+    /**
+     * 该worker进程停止服务的时候会触发一次,可以在这里做一些全局的事情
+     * @return bool
+     */
+    protected function onStop()
+    {
+        return false;
+    }
+    
+}
+
+
+

+ 0 - 0
Docs/.placeholder


+ 155 - 0
Protocols/Buffer.php

@@ -0,0 +1,155 @@
+<?php 
+namespace WORKERMAN\Protocols;
+/**
+ * 通用的server协议,二进制协议
+ * 
+ * struct JMProtocol
+ * {
+ *     unsigned char     version,//版本
+ *     unsigned short    series_id,//序列号 udp协议使用
+ *     unsigned short    cmd,//主命令字
+ *     unsigned short    sub_cmd,//子命令字
+ *     int               code,//返回码
+ *     unsigned int      pack_len,//包长
+ *     char[pack_length] body//包体
+ * }
+ * 
+ * @author walkor <worker-man@qq.com>
+ */
+
+class Buffer
+{
+    /**
+     * 版本
+     * @var integer
+     */
+    const VERSION = 0x01;
+    
+    /**
+     * 包头长度
+     * @var integer
+     */
+    const HEAD_LEN = 15;
+    
+    /**
+     * 默认包体序列化类型
+     * @var integer
+     */
+    
+    const DEFAULT_SERIALIZE_TYPE = 0;
+    /**
+     * 序列号,防止串包
+     * @var integer
+     */
+    protected static $seriesId = 0;
+    
+    /**
+     * 协议头
+     * @var array
+     */
+    public $header = array(
+        'version'        => self::VERSION,
+        'series_id'      => 0,
+        'cmd'            => 0,
+        'sub_cmd'        => 0,
+        'code'           => 0,
+        'pack_len'       => self::HEAD_LEN
+    );
+    
+    /**
+     * 包体
+     * @var string
+     */
+    public $body = '';
+    
+    /**
+     * 初始化
+     * @return void
+     */
+    public function __construct($buffer = null)
+    {
+        if($buffer)
+        {
+            $data = self::bufferToData($buffer);
+            $this->body = $data['body'];
+            unset($data['body']);
+            $this->header = $data;
+        }
+        else
+        {
+            if(self::$seriesId>=65535)
+            {
+                self::$seriesId = 0;
+            }
+            else
+            {
+                $this->header['series_id'] = self::$seriesId++;
+            }
+        }
+    }
+    
+    /**
+     * 判断数据包是否都到了
+     * @param string $bin
+     * @return int int=0数据是完整的 int>1数据不完整,还要继续接收int字节
+     */
+    public static function input($bin)
+    {
+        $len = strlen($bin);
+        if($len < self::HEAD_LEN)
+        {
+            return self::HEAD_LEN - $len;
+        }
+        
+        $unpack_data = unpack("Cversion/Sseries_id/Scmd/Ssub_cmd/icode/Ipack_len", $bin);
+        if($unpack_data['pack_len'] > $len)
+        {
+            return $unpack_data['pack_len'] - $len;
+        }
+        
+        return 0;
+    }
+    
+    
+    /**
+     * 设置包体
+     * @param string $body_str
+     * @return void
+     */
+    public function setBody($body_str)
+    {
+        $this->body = (string) $body_str;
+    }
+    
+    /**
+     * 获取整个包的buffer
+     * @param string $data
+     * @return string
+     */
+    public function getBuffer()
+    {
+        $this->header['pack_len'] = self::HEAD_LEN + strlen($this->body);
+        return pack("CSSSiI", $this->header['version'],  $this->header['series_id'], $this->header['cmd'], $this->header['sub_cmd'], $this->header['code'], $this->header['pack_len']).$this->body;
+    }
+    
+    /**
+     * 从二进制数据转换为数组
+     * @param string $bin
+     * @return array
+     */    
+    public static function decode($buffer)
+    {
+        $data = unpack("Cversion/Sseries_id/Scmd/Ssub_cmd/icode/Ipack_len", $buffer);
+        $data['body'] = '';
+        $body_len = $data['pack_len'] - self::HEAD_LEN;
+        if($body_len > 0)
+        {
+            $data['body'] = substr($buffer, self::HEAD_LEN, $body_len);
+        }
+        return $data;
+    }
+    
+}
+
+
+

+ 194 - 0
Protocols/FastCGI.php

@@ -0,0 +1,194 @@
+<?php 
+
+/**
+ * fastcgi 协议解析 相关
+ * 简单实现,测试时使用,可能会有bug,不要用到生产环境
+* @author walkor <worker-man@qq.com>
+* */
+class FastCGI{
+    
+    const VERSION_1            = 1;
+
+    const BEGIN_REQUEST        = 1;
+    const ABORT_REQUEST        = 2;
+    const END_REQUEST          = 3;
+    const PARAMS               = 4;
+    const STDIN                = 5;
+    const STDOUT               = 6;
+    const STDERR               = 7;
+    const DATA                 = 8;
+    const GET_VALUES           = 9;
+    const GET_VALUES_RESULT    = 10;
+    const UNKNOWN_TYPE         = 11;
+    const MAXTYPE              = self::UNKNOWN_TYPE;
+    
+    const HEAD_LENGTH      = 8;
+    
+    
+    private  function __construct(){}
+    
+    /**
+     * 判断数据包是否全部接收完成
+     * 
+     * @param string $data
+     * @return int 0:完成 >0:还要接收int字节
+     */
+    public static function input($data)
+    {
+        while(1)
+        {
+            $data_length = strlen($data);
+            // 长度小于包头长度,继续读
+            if($data_length < self::HEAD_LENGTH)
+            {
+                return self::HEAD_LENGTH - $data_length;
+            }
+        
+            $headers = unpack(
+                    "Cversion/".
+                    "Ctype/".
+                    "nrequestId/".
+                    "ncontentLength/".
+                    "CpaddingLength/".
+                    "Creserved/"
+                    , $data);
+        
+            $total_length = self::HEAD_LENGTH + $headers['contentLength'] + $headers['paddingLength'];
+            
+            // 全部接收完毕
+            if($data_length == $total_length)
+            {
+                return 0;
+            }
+            // 数据长度不够一个包长
+            else if($data_length < $total_length)
+            {
+                return $total_length - $data_length;
+            }
+            // 数据长度大于一个包长,还有后续包
+            else
+            {
+                $data = substr($data, $total_length);
+            }
+        }
+        return 0;
+    }    
+    
+    /**
+     * 解析全部fastcgi协议包,并设置相应环境变量
+     * 
+     * @param string $data
+     * @return array
+     */
+    public static function decode($data)
+    {
+        $params = array();
+        $_GET = $_POST = $GLOBALS['HTTP_RAW_POST_DATA'] = array();
+        
+        while(1)
+        {
+            if(!$data)
+            {
+                break;
+            }
+            $headers = unpack(
+                    "Cversion/".
+                    "Ctype/".
+                    "nrequestId/".
+                    "ncontentLength/".
+                    "CpaddingLength/".
+                    "Creserved/"
+                    , $data);
+            
+            // 获得环境变量等
+            if($headers['type'] == self::PARAMS)
+            {
+                // 解析名-值
+                $offset = self::HEAD_LENGTH;
+                while($offset + $headers['paddingLength'] < $headers['contentLength'])
+                {
+                    $namelen = ord($data[$offset++]);
+                    // 127字节或更少的长度能在一字节中编码,而更长的长度总是在四字节中编码
+                    if($namelen > 127)
+                    {
+                        $namelen = (($namelen & 0x7f) << 24) +
+                        (ord($data[$offset++]) << 16) +
+                        (ord($data[$offset++]) << 8) +
+                        ord($data[$offset++]);
+                    }
+            
+                    // 值的长度
+                    $valuelen = ord($data[$offset++]);
+                    if($valuelen > 127)
+                    {
+                        $valuelen = (($valuelen & 0x7f) << 24) +
+                        (ord($data[$offset++]) << 16) +
+                        (ord($data[$offset++]) << 8) +
+                        ord($data[$offset++]);
+                    }
+            
+                    // 名
+                    $name = substr($data, $offset, $namelen);
+                    $offset += $namelen;
+                    $value = substr($data, $offset, $valuelen);
+                    $offset += $valuelen;
+                    $params[$name] = $value;
+                }
+                
+                // 解析$_SERVER
+                foreach($params as $key=>$value)
+                {
+                    $_SERVER[$key]=$value;
+                }
+                if(array_key_exists('HTTP_COOKIE', $params))
+                {
+                    foreach(explode(';', $params['HTTP_COOKIE']) as $coo)
+                    {
+                        $nameval = explode('=', trim($coo));
+                        $_COOKIE[$nameval[0]] = urldecode($nameval[1]);
+                    }
+                }
+            }
+            elseif($headers['type'] == self::STDIN)
+            {
+                // 为啥是8,还要研究下
+                $data = substr($data, 8, $headers['contentLength']);
+                
+                // 解析$GLOBALS['HTTP_RAW_POST_DATA']
+                $GLOBALS['HTTP_RAW_POST_DATA'] = $data;
+                // 解析POST
+                parse_str($data, $_POST);
+            }
+            
+            $total_length = self::HEAD_LENGTH + $headers['contentLength'] + $headers['paddingLength'];
+            
+            $data = substr($data, $total_length);
+        }
+        
+        // 解析GET
+        parse_str(preg_replace('/^\/.*?\?/', '', $_SERVER['REQUEST_URI']), $_GET);
+        
+        return array('header' => $headers, 'data' => '');
+    }
+    
+    /**
+     * 打包fastcgi协议,用于返回数据给nginx
+     * 
+     * @param array $header
+     * @param string $data
+     * @return string
+     */
+    public static function encode($header, $data)
+    {
+        $data = "Content-type: text/html\r\n\r\n" . $data;
+        $contentLength = strlen($data);
+        $head_data = pack("CCnnxx",
+                self::VERSION_1,
+                self::STDOUT,
+                $header['requestId'],
+                $contentLength
+        );
+        
+        return $head_data.$data;
+    }
+}

+ 233 - 0
Protocols/HTTP.php

@@ -0,0 +1,233 @@
+<?php 
+
+/**
+ * http 协议解析 相关
+ * 简单的实现,可能会有bug,不要用于生产环境
+* @author walkor <worker-man@qq.com>
+* */
+class HTTP{
+    
+    /**
+     * 构造函数
+     */
+    private  function __construct(){}
+    
+    /**
+     * http头
+     * @var array
+     */
+    public static $header = array();
+    
+    /**
+     * cookie 
+     * @var array
+     */
+    protected static $cookie = array();
+    
+    /**
+     * 判断数据包是否全部接收完成
+     * 
+     * @param string $data
+     * @return int 0:完成 1:还要接收数据
+     */
+    public static function input($data)
+    {
+        // 查找\r\n\r\n
+        $data_length = strlen($data);
+        
+        if(!strpos($data, "\r\n\r\n"))
+        {
+            return 1;
+        }
+        
+        // POST请求还要读包体
+        if(strpos($data, "POST"))
+        {
+            // 找Content-Length
+            $match = array();
+            if(preg_match("/\r\nContent-Length: ?(\d?)\r\n/", $data, $match))
+            {
+                $content_lenght = $match[1];
+            }
+            else
+            {
+                return 0;
+            }
+            
+            // 看包体长度是否符合
+            $tmp = explode("\r\n\r\n", $data);
+            if(strlen($tmp[1]) >= $content_lenght)
+            {
+                return 0;
+            }
+            return 1;
+        }
+        else 
+        {
+            return 0;
+        }
+        
+        // var_export($header_data);
+        return 0;
+    }    
+    
+    /**
+     * 解析http协议包,并设置相应环境变量
+     * 
+     * @param string $data
+     * @return array
+     */
+    public static function decode($data)
+    {
+        $_SERVER = array(
+                'REQUEST_URI'    => '/',
+                'HTTP_HOST'      => '127.0.0.1',
+                'HTTP_COOKIE'    => '',
+                );
+        
+        $_POST = array();
+        $_GET = array();
+        $GLOBALS['HTTP_RAW_POST_DATA'] = array();
+        
+        // 将header分割成数组
+        $header_data = explode("\r\n", $data);
+        
+        // 需要解析$_POST
+        if(strpos($data, "POST") === 0)
+        {
+            $tmp = explode("\r\n\r\n", $data);
+            parse_str($tmp[1], $_POST);
+            
+            // $GLOBALS['HTTP_RAW_POST_DATA']
+            $GLOBALS['HTTP_RAW_POST_DATA'] = $tmp[1];
+        }
+        
+        // REQUEST_URI
+        $tmp = explode(' ', $header_data[0]);
+        $_SERVER['REQUEST_URI'] = isset($tmp[1]) ? $tmp[1] : '/';
+        
+        // PHP_SELF
+        $base_name = basename($_SERVER['REQUEST_URI']);
+        $_SERVER['PHP_SELF'] = empty($base_name) ? 'index.php' : $base_name;
+        
+        unset($header_data[0]);
+        foreach($header_data as $content)
+        {
+            // 解析HTTP_HOST
+            if(strpos($content, 'Host') === 0)
+            {
+                $tmp = explode(':', $content);
+                if(isset($tmp[1]))
+                {
+                    $_SERVER['HTTP_HOST'] = $tmp[1];
+                }
+                if(isset($tmp[2]))
+                {
+                    $_SERVER['SERVER_PORT'] = $tmp[2];
+                }
+            }
+            // 解析Cookie
+            elseif(strpos($content, 'Cookie') === 0)
+            {
+                $tmp = explode(' ', $content);
+                if(isset($tmp[1]))
+                {
+                    $_SERVER['HTTP_COOKIE'] = $tmp[1];
+                }
+            }
+        }
+        
+        // 'REQUEST_TIME_FLOAT' => 1375774613.237,
+        $_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
+        $_SERVER['REQUEST_TIME'] = intval($_SERVER['REQUEST_TIME_FLOAT']);
+        
+        // GET
+        parse_str(preg_replace('/^\/.*?\?/', '', $_SERVER['REQUEST_URI']), $_GET);
+        unset($_GET['/']);
+        
+    }
+    
+    /**
+     * 设置http头
+     * @return bool
+     */
+    public static function header($content)
+    {
+        if(strpos($content, 'HTTP') === 0)
+        {
+            $key = 'Http-Code';
+        }
+        else
+        {
+            $key = strstr($content, ":", true);
+            if(empty($key))
+            {
+                return false;
+            }
+        }
+        self::$header[$key] = $content;
+        return true;
+    }
+    
+    /**
+     * 
+     * @param string $name
+     * @param string/int $value
+     * @param int $expire
+     */
+    public static function setcookie($name, $value='', $expire=0)
+    {
+        // 待完善
+    }
+    
+    /**
+     * 清除header
+     * @return void
+     */
+    public static function clear()
+    {
+        self::$header = array();
+    }
+    
+    /**
+     * 打包http协议,用于返回数据给nginx
+     * 
+     * @param string $data
+     * @return string
+     */
+    public static function encode($data)
+    {
+        // header
+        $header = "Server: PHPServer/1.0\r\nContent-Length: ".strlen($data)."\r\n";
+        
+        // 没有Content-Type默认给个
+        if(!isset(self::$header['Content-Type']))
+        {
+            $header = "Content-Type: text/html;charset=utf-8\r\n".$header;
+        }
+        
+        // 没有http-code默认给个
+        if(!isset(self::$header['Http-Code']))
+        {
+            $header = "HTTP/1.1 200 OK\r\n".$header;
+        }
+        else
+        {
+            $header = self::$header['Http-Code']."\r\n".$header;
+            unset(self::$header['Http-Code']);
+        }
+        
+        // 其它header
+        foreach(self::$header as $content)
+        {
+            $header .= $content."\r\n";
+        }
+        
+        $header .= "\r\n";
+        
+        self::clear();
+        
+        // 整个http包
+        return $header.$data;
+    }
+}

+ 194 - 0
Protocols/SimpleFastCgi.php

@@ -0,0 +1,194 @@
+<?php 
+namespace WORKERMAN\Protocols;
+/**
+ * fastcgi 协议解析 相关
+ * 简单实现,测试时使用,可能会有bug,不要用到生产环境
+ * @author walkor <worker-man@qq.com>
+ * */
+class FastCGI{
+    
+    const VERSION_1            = 1;
+
+    const BEGIN_REQUEST        = 1;
+    const ABORT_REQUEST        = 2;
+    const END_REQUEST          = 3;
+    const PARAMS               = 4;
+    const STDIN                = 5;
+    const STDOUT               = 6;
+    const STDERR               = 7;
+    const DATA                 = 8;
+    const GET_VALUES           = 9;
+    const GET_VALUES_RESULT    = 10;
+    const UNKNOWN_TYPE         = 11;
+    const MAXTYPE              = self::UNKNOWN_TYPE;
+    
+    const HEAD_LENGTH      = 8;
+    
+    
+    private  function __construct(){}
+    
+    /**
+     * 判断数据包是否全部接收完成
+     * 
+     * @param string $data
+     * @return int 0:完成 >0:还要接收int字节
+     */
+    public static function input($data)
+    {
+        while(1)
+        {
+            $data_length = strlen($data);
+            // 长度小于包头长度,继续读
+            if($data_length < self::HEAD_LENGTH)
+            {
+                return self::HEAD_LENGTH - $data_length;
+            }
+        
+            $headers = unpack(
+                    "Cversion/".
+                    "Ctype/".
+                    "nrequestId/".
+                    "ncontentLength/".
+                    "CpaddingLength/".
+                    "Creserved/"
+                    , $data);
+        
+            $total_length = self::HEAD_LENGTH + $headers['contentLength'] + $headers['paddingLength'];
+            
+            // 全部接收完毕
+            if($data_length == $total_length)
+            {
+                return 0;
+            }
+            // 数据长度不够一个包长
+            else if($data_length < $total_length)
+            {
+                return $total_length - $data_length;
+            }
+            // 数据长度大于一个包长,还有后续包
+            else
+            {
+                $data = substr($data, $total_length);
+            }
+        }
+        return 0;
+    }    
+    
+    /**
+     * 解析全部fastcgi协议包,并设置相应环境变量
+     * 
+     * @param string $data
+     * @return array
+     */
+    public static function decode($data)
+    {
+        $params = array();
+        $_GET = $_POST = $GLOBALS['HTTP_RAW_POST_DATA'] = array();
+        
+        while(1)
+        {
+            if(!$data)
+            {
+                break;
+            }
+            $headers = unpack(
+                    "Cversion/".
+                    "Ctype/".
+                    "nrequestId/".
+                    "ncontentLength/".
+                    "CpaddingLength/".
+                    "Creserved/"
+                    , $data);
+            
+            // 获得环境变量等
+            if($headers['type'] == self::PARAMS)
+            {
+                // 解析名-值
+                $offset = self::HEAD_LENGTH;
+                while($offset + $headers['paddingLength'] < $headers['contentLength'])
+                {
+                    $namelen = ord($data[$offset++]);
+                    // 127字节或更少的长度能在一字节中编码,而更长的长度总是在四字节中编码
+                    if($namelen > 127)
+                    {
+                        $namelen = (($namelen & 0x7f) << 24) +
+                        (ord($data[$offset++]) << 16) +
+                        (ord($data[$offset++]) << 8) +
+                        ord($data[$offset++]);
+                    }
+            
+                    // 值的长度
+                    $valuelen = ord($data[$offset++]);
+                    if($valuelen > 127)
+                    {
+                        $valuelen = (($valuelen & 0x7f) << 24) +
+                        (ord($data[$offset++]) << 16) +
+                        (ord($data[$offset++]) << 8) +
+                        ord($data[$offset++]);
+                    }
+            
+                    // 名
+                    $name = substr($data, $offset, $namelen);
+                    $offset += $namelen;
+                    $value = substr($data, $offset, $valuelen);
+                    $offset += $valuelen;
+                    $params[$name] = $value;
+                }
+                
+                // 解析$_SERVER
+                foreach($params as $key=>$value)
+                {
+                    $_SERVER[$key]=$value;
+                }
+                if(array_key_exists('HTTP_COOKIE', $params))
+                {
+                    foreach(explode(';', $params['HTTP_COOKIE']) as $coo)
+                    {
+                        $nameval = explode('=', trim($coo));
+                        $_COOKIE[$nameval[0]] = urldecode($nameval[1]);
+                    }
+                }
+            }
+            elseif($headers['type'] == self::STDIN)
+            {
+                // 为啥是8,还要研究下
+                $data = substr($data, 8, $headers['contentLength']);
+                
+                // 解析$GLOBALS['HTTP_RAW_POST_DATA']
+                $GLOBALS['HTTP_RAW_POST_DATA'] = $data;
+                // 解析POST
+                parse_str($data, $_POST);
+            }
+            
+            $total_length = self::HEAD_LENGTH + $headers['contentLength'] + $headers['paddingLength'];
+            
+            $data = substr($data, $total_length);
+        }
+        
+        // 解析GET
+        parse_str(preg_replace('/^\/.*?\?/', '', $_SERVER['REQUEST_URI']), $_GET);
+        
+        return array('header' => $headers, 'data' => '');
+    }
+    
+    /**
+     * 打包fastcgi协议,用于返回数据给nginx
+     * 
+     * @param array $header
+     * @param string $data
+     * @return string
+     */
+    public static function encode($header, $data)
+    {
+        $data = "Content-type: text/html\r\n\r\n" . $data;
+        $contentLength = strlen($data);
+        $head_data = pack("CCnnxx",
+                self::VERSION_1,
+                self::STDOUT,
+                $header['requestId'],
+                $contentLength
+        );
+        
+        return $head_data.$data;
+    }
+}

+ 233 - 0
Protocols/SimpleHttp.php

@@ -0,0 +1,233 @@
+<?php 
+namespace WORKERMAN\Protocols;
+/**
+ * http 协议解析 相关
+ * 简单的实现,可能会有bug,不要用于生产环境
+ * @author walkor <worker-man@qq.com>
+ * */
+class SimpleHttp{
+    
+    /**
+     * 构造函数
+     */
+    private  function __construct(){}
+    
+    /**
+     * http头
+     * @var array
+     */
+    public static $header = array();
+    
+    /**
+     * cookie 
+     * @var array
+     */
+    protected static $cookie = array();
+    
+    /**
+     * 判断数据包是否全部接收完成
+     * 
+     * @param string $data
+     * @return int 0:完成 1:还要接收数据
+     */
+    public static function input($data)
+    {
+        // 查找\r\n\r\n
+        $data_length = strlen($data);
+        
+        if(!strpos($data, "\r\n\r\n"))
+        {
+            return 1;
+        }
+        
+        // POST请求还要读包体
+        if(strpos($data, "POST"))
+        {
+            // 找Content-Length
+            $match = array();
+            if(preg_match("/\r\nContent-Length: ?(\d?)\r\n/", $data, $match))
+            {
+                $content_lenght = $match[1];
+            }
+            else
+            {
+                return 0;
+            }
+            
+            // 看包体长度是否符合
+            $tmp = explode("\r\n\r\n", $data);
+            if(strlen($tmp[1]) >= $content_lenght)
+            {
+                return 0;
+            }
+            return 1;
+        }
+        else 
+        {
+            return 0;
+        }
+        
+        // var_export($header_data);
+        return 0;
+    }    
+    
+    /**
+     * 解析http协议包,并设置相应环境变量
+     * 
+     * @param string $data
+     * @return array
+     */
+    public static function decode($data)
+    {
+        $_SERVER = array(
+                'REQUEST_URI'    => '/',
+                'HTTP_HOST'      => '127.0.0.1',
+                'HTTP_COOKIE'    => '',
+                );
+        
+        $_POST = array();
+        $_GET = array();
+        $GLOBALS['HTTP_RAW_POST_DATA'] = array();
+        
+        // 将header分割成数组
+        $header_data = explode("\r\n", $data);
+        
+        // 需要解析$_POST
+        if(strpos($data, "POST") === 0)
+        {
+            $tmp = explode("\r\n\r\n", $data);
+            parse_str($tmp[1], $_POST);
+            
+            // $GLOBALS['HTTP_RAW_POST_DATA']
+            $GLOBALS['HTTP_RAW_POST_DATA'] = $tmp[1];
+        }
+        
+        // REQUEST_URI
+        $tmp = explode(' ', $header_data[0]);
+        $_SERVER['REQUEST_URI'] = isset($tmp[1]) ? $tmp[1] : '/';
+        
+        // PHP_SELF
+        $base_name = basename($_SERVER['REQUEST_URI']);
+        $_SERVER['PHP_SELF'] = empty($base_name) ? 'index.php' : $base_name;
+        
+        unset($header_data[0]);
+        foreach($header_data as $content)
+        {
+            // 解析HTTP_HOST
+            if(strpos($content, 'Host') === 0)
+            {
+                $tmp = explode(':', $content);
+                if(isset($tmp[1]))
+                {
+                    $_SERVER['HTTP_HOST'] = $tmp[1];
+                }
+                if(isset($tmp[2]))
+                {
+                    $_SERVER['SERVER_PORT'] = $tmp[2];
+                }
+            }
+            // 解析Cookie
+            elseif(strpos($content, 'Cookie') === 0)
+            {
+                $tmp = explode(' ', $content);
+                if(isset($tmp[1]))
+                {
+                    $_SERVER['HTTP_COOKIE'] = $tmp[1];
+                }
+            }
+        }
+        
+        // 'REQUEST_TIME_FLOAT' => 1375774613.237,
+        $_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
+        $_SERVER['REQUEST_TIME'] = intval($_SERVER['REQUEST_TIME_FLOAT']);
+        
+        // GET
+        parse_str(preg_replace('/^\/.*?\?/', '', $_SERVER['REQUEST_URI']), $_GET);
+        unset($_GET['/']);
+        
+    }
+    
+    /**
+     * 设置http头
+     * @return bool
+     */
+    public static function header($content)
+    {
+        if(strpos($content, 'HTTP') === 0)
+        {
+            $key = 'Http-Code';
+        }
+        else
+        {
+            $key = strstr($content, ":", true);
+            if(empty($key))
+            {
+                return false;
+            }
+        }
+        self::$header[$key] = $content;
+        return true;
+    }
+    
+    /**
+     * 
+     * @param string $name
+     * @param string/int $value
+     * @param int $expire
+     */
+    public static function setcookie($name, $value='', $expire=0)
+    {
+        // 待完善
+    }
+    
+    /**
+     * 清除header
+     * @return void
+     */
+    public static function clear()
+    {
+        self::$header = array();
+    }
+    
+    /**
+     * 打包http协议,用于返回数据给nginx
+     * 
+     * @param string $data
+     * @return string
+     */
+    public static function encode($data)
+    {
+        // header
+        $header = "Server: PHPServer/1.0\r\nContent-Length: ".strlen($data)."\r\n";
+        
+        // 没有Content-Type默认给个
+        if(!isset(self::$header['Content-Type']))
+        {
+            $header = "Content-Type: text/html;charset=utf-8\r\n".$header;
+        }
+        
+        // 没有http-code默认给个
+        if(!isset(self::$header['Http-Code']))
+        {
+            $header = "HTTP/1.1 200 OK\r\n".$header;
+        }
+        else
+        {
+            $header = self::$header['Http-Code']."\r\n".$header;
+            unset(self::$header['Http-Code']);
+        }
+        
+        // 其它header
+        foreach(self::$header as $content)
+        {
+            $header .= $content."\r\n";
+        }
+        
+        $header .= "\r\n";
+        
+        self::clear();
+        
+        // 整个http包
+        return $header.$data;
+    }
+}

+ 8 - 0
Protocols/interfaces.php

@@ -0,0 +1,8 @@
+<?php
+
+interface IProtocol
+{
+    public static function input($data);
+    public static function decode($data);
+    public static function encode($data);
+}

+ 18 - 0
Tests/testBufferWorker.php

@@ -0,0 +1,18 @@
+<?php 
+error_reporting(E_ALL);
+ini_set('display_errors', 'on');
+include '../Protocols/Buffer.php';
+
+$sock = stream_socket_client("tcp://127.0.0.1:20305");
+if(!$sock)exit("can not create sock\n");
+
+$code = 0;
+while(1)
+{
+    $buf = new \WORKERMAN\Protocols\Buffer();
+    $buf->body = 'HELLO YAOYAO';
+    $buf->head['code'] = $code++;
+    fwrite($sock, $buf->getBuffer());
+    $ret = fread($sock, 10240);
+    var_export(\WORKERMAN\Protocols\Buffer::decode($ret));
+}

+ 25 - 0
Workers/BufferWorker.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * 
+ * 压测worker
+* @author walkor <worker-man@qq.com>
+ */
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+require_once WORKERMAN_ROOT_DIR . 'Protocols/Buffer.php';
+
+class BufferWorker extends WORKERMAN\Core\SocketWorker
+{
+    public function dealInput($recv_str)
+    {
+        $remian = \WORKERMAN\Protocols\Buffer::input($recv_str);
+        return $remian;
+    }
+
+    public function dealProcess($recv_str)
+    {
+        $buf = new \WORKERMAN\Protocols\Buffer();
+        $buf->header['code'] = 200;
+        $buf->body = 'haha';
+        $this->sendToClient($buf->getBuffer());
+    }
+}

+ 19 - 0
Workers/EchoWorker.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * 
+ * 压测worker
+ * @author walkor <worker-man@qq.com>
+ */
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+class EchoWorker extends WORKERMAN\Core\SocketWorker
+{
+    public function dealInput($recv_str)
+    {
+        return 0; 
+    }
+
+    public function dealProcess($recv_str)
+    {
+        $this->sendToClient($recv_str);
+    }
+}

+ 163 - 0
Workers/FileMonitor.php

@@ -0,0 +1,163 @@
+<?php 
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+/**
+ * 
+ * 用这个worker监控文件更新
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+class FileMonitor extends WORKERMAN\Core\AbstractWorker
+{
+    
+    /**
+     * 需要监控的文件
+     * @var array
+     */
+    protected $filesToInotify = array();
+    
+    /**
+     * 终端已经关闭
+     * @var bool
+     */
+    protected $terminalClosed = false;
+    
+    /**
+     * 该worker进程开始服务的时候会触发一次
+     * @return bool
+     */
+    public function start()
+    {
+        if(\WORKERMAN\Core\Lib\Config::get('debug') != 1)
+        {
+            return;
+        }
+        if(!Master::getQueueId())
+        {
+            while(1)
+            {
+                sleep(PHP_INT_MAX);
+            }
+        }
+        $msg_type = $message = 0;
+        \WORKERMAN\Core\Lib\Task::init();
+        \WORKERMAN\Core\Lib\Task::add(1, array($this, 'sendSignalAndGetResult'));
+        \WORKERMAN\Core\Lib\Task::add(1, array($this, 'checkFilesModify'));
+        \WORKERMAN\Core\Lib\Task::add(1, array($this, 'checkTty'));
+        while(1)
+        {
+            $this->collectFiles(true);
+            if($this->hasShutDown())
+            {
+                exit(0);
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * 发送文件上报信号,并收集文件列表
+     * @return void
+     */
+    public function sendSignalAndGetResult()
+    {
+        $this_pid = posix_getpid();
+        $pid_worker_map = $this->getPidWorkerMap();
+        foreach($pid_worker_map as $pid=>$worker_name)
+        {
+            if($pid != $this_pid)
+            {
+                posix_kill($pid, SIGUSR2);
+            }
+            $this->collectFiles();
+        }
+    }
+    
+    /**
+     * 从消息队列中获取要监控的文件列表
+     * @param bool $block
+     * @return void
+     */
+    protected function collectFiles($block = false)
+    {
+        $msg_type = $message = null;
+        $flag = $block ? 0 : MSG_IPC_NOWAIT;
+        if(msg_receive(Master::getQueueId(), self::MSG_TYPE_FILE_MONITOR, $msg_type, 10000, $message, true, $flag))
+        {
+            foreach($message as $file)
+            {
+                if(!isset($this->filesToInotify[$file]))
+                {
+                    $stat = @stat($file);
+                    $mtime = isset($stat['mtime']) ? $stat['mtime'] : 0;
+                    $this->filesToInotify[$file] = $mtime;
+                }
+            }
+        }
+    }
+
+    /**
+     * 发送信号给所有worker
+     * @param integer $signal
+     * @return void
+     */
+    public function sendSignalToAllWorker($signal)
+    {
+        $this_pid = posix_getpid();
+        $pid_worker_map = $this->getPidWorkerMap();
+        foreach($pid_worker_map as $pid=>$worker_name)
+        {
+            if($pid != $this_pid)
+            {
+                posix_kill($pid, $signal);
+            }
+        }
+    }
+    
+    /**
+     * 检查文件更新时间,如果有更改则平滑重启服务(开发的时候用到)
+     * @return void
+     */
+    public function checkFilesModify()
+    {
+        $has_send_signal = false;
+        foreach($this->filesToInotify as $file=>$mtime)
+        {
+            clearstatcache();
+            $stat = @stat($file);
+            if(false === $stat)
+            {
+                unset($this->filesToInotify[$file]);
+                continue;
+            }
+            $mtime_now = $stat['mtime'];
+            if($mtime != $mtime_now) 
+            {
+                $this->filesToInotify[$file] = $mtime_now;
+                if(!$has_send_signal)
+                {
+                    \WORKERMAN\Core\Lib\Log::add("$file updated and reload workers");
+                    $this->sendSignalToAllWorker(SIGHUP);
+                    $has_send_signal = true;
+                }
+            }
+        }
+    }
+    
+    /**
+     * 检查控制终端是否已经关闭, 如果控制终端关闭,则停止打印数据到终端(发送平滑重启信号)
+     * @return void
+     */
+    public function checkTty()
+    {
+        if(!$this->terminalClosed && !posix_ttyname(STDOUT))
+        {
+            // 日志
+            $this->notice("terminal closed and restart worker");
+            // worker重启时会检测终端是否关闭
+            $this->sendSignalToAllWorker(SIGHUP);
+            // 设置标记
+            $this->terminalClosed = true;
+        }
+    }
+    
+} 

+ 580 - 0
Workers/Monitor.php

@@ -0,0 +1,580 @@
+<?php 
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+/**
+ * 
+ * 1、提供telnet接口,查看服务状态
+ * 2、监控主进程是否挂掉
+ * 3、监控worker进程是否频繁退出
+ * 4、定时清理log文件
+ * 5、定时监控worker内存泄漏
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+class Monitor extends WORKERMAN\Core\SocketWorker
+{
+    /**
+     * 一天有多少秒
+     * @var integer
+     */
+    const SECONDS_ONE_DAY = 86400;
+    
+    /**
+     * 多长时间清理一次磁盘日志文件
+     * @var integer
+     */
+    const CLEAR_LOGS_TIME_LONG = 86400;
+    
+    /**
+     * 多长时间检测一次master进程是否存在
+     * @var integer
+     */
+    const CHECK_MASTER_PROCESS_TIME_LONG = 5;
+    
+    /**
+     * 多长时间检查一次主进程状态
+     * @var integer
+     */
+    const CHECK_MASTER_STATUS_TIME_LONG = 60;
+    
+    /**
+     * 多长时间检查一次内存占用情况
+     * @var integer
+     */
+    const CHECK_WORKER_MEM_TIME_LONG = 60;
+    
+    /**
+     * 清理多少天前的日志文件
+     * @var integer
+     */
+    const CLEAR_BEFORE_DAYS = 14;
+    
+    /**
+     * 告警发送时间间隔
+     * @var integer
+     */
+    const WARING_SEND_TIME_LONG = 300;
+    
+    /**
+     * 大量worker进程退出
+     * @var integer
+     */
+    const WARNING_TOO_MANY_WORKERS_EXIT = 1;
+    
+    /**
+     * 主进程死掉
+     * @var integer
+     */
+    const WARNING_MASTER_DEAD = 8;
+    
+    /**
+     * worker占用内存限制 单位KB
+     * @var integer
+     */
+    const DEFAULT_MEM_LIMIT = 83886;
+    
+    /**
+     * 上次获得的主进程信息 
+     * [worker_name=>[0=>xx, 9=>xxx], worker_name=>[0=>xx]]
+     * @var array
+     */
+    protected $lastMasterStatus = null;
+    
+    /**
+     * 管理员认证信息
+     * @var array
+     */
+    protected $adminAuth = array();
+    
+    /**
+     * 最长的workerName
+     * @var integer
+     */
+    protected $maxWorkerNameLength = 10;
+    
+    /**
+     * 上次发送告警的时间
+     * @var array
+     */
+    protected static $lastWarningTimeMap = array(
+        self::WARNING_TOO_MANY_WORKERS_EXIT => 0,
+        self::WARNING_MASTER_DEAD => 0,
+    );
+    
+    /**
+     * 该进程开始服务
+     * @see SocketWorker::start()
+     */
+    public function start()
+    {
+        // 安装信号
+        $this->installSignal();
+        
+        if(!is_dir(WORKERMAN_LOG_DIR . 'statistic'))
+        {
+            @mkdir(WORKERMAN_LOG_DIR . 'statistic', 0777);
+        }
+        
+        // 初始化任务
+        \WORKERMAN\Core\Lib\Task::init($this->event);
+        \WORKERMAN\Core\Lib\Task::add(self::CLEAR_LOGS_TIME_LONG, array($this, 'clearLogs'), array(WORKERMAN_LOG_DIR));
+        \WORKERMAN\Core\Lib\Task::add(self::CHECK_MASTER_PROCESS_TIME_LONG, array($this, 'checkMasterProcess'));
+        \WORKERMAN\Core\Lib\Task::add(self::CHECK_MASTER_STATUS_TIME_LONG, array($this, 'checkMasterStatus'));
+        \WORKERMAN\Core\Lib\Task::add(self::CHECK_MASTER_STATUS_TIME_LONG, array($this, 'checkMemUsage'));
+        
+        // 添加accept事件
+        $this->event->add($this->mainSocket,  \WORKERMAN\Core\Events\BaseEvent::EV_READ, array($this, 'onAccept'));
+        
+        // 主体循环
+        $ret = $this->event->loop();
+    }
+    
+    /**
+     * 当有链接事件时触发
+     * @param resource $socket
+     * @param null $null_one
+     * @param null $null_two
+     * @return void
+     */
+    public function onAccept($socket, $null_one = null, $null_two = null)
+    {
+        $fd = $this->accept($socket, $null_one , $null_two);
+        if($fd)
+        {
+            $this->currentDealFd = (int)$fd;
+            if($this->getRemoteIp() != '127.0.0.1')
+            {
+                $this->sendToClient("Password\n");
+            }
+            else 
+            {
+                $this->adminAuth[$this->currentDealFd] = time();
+                $this->sendToClient("Hello admin\n");
+            }
+        }
+    }
+    
+    
+    /**
+     * 确定包是否完整
+     * @see Worker::dealInput()
+     */
+    public function dealInput($recv_str)
+    {
+        return 0;
+    }
+    
+    /**
+     * 处理业务
+     * @see Worker::dealProcess()
+     */
+    public function dealProcess($buffer)
+    {
+        
+        $buffer = trim($buffer);
+        
+        $ip = $this->getRemoteIp();
+        if($ip != '127.0.0.1' && $buffer == 'status')
+        {
+            \WORKERMAN\Core\Lib\Log::add("IP:$ip $buffer");
+        }
+        
+        // 判断是否认证过
+        $this->adminAuth[$this->currentDealFd] = !isset($this->adminAuth[$this->currentDealFd]) ? 0 : $this->adminAuth[$this->currentDealFd];
+        if($this->adminAuth[$this->currentDealFd] < 3)
+        {
+            if($buffer != 'P@ssword')
+            {
+                if(++$this->adminAuth[$this->currentDealFd] >= 3)
+                {
+                    $this->sendToClient("Password Incorrect \n");
+                    $this->closeClient();
+                }
+                $this->sendToClient("Please Try Again\n");
+                return;
+            }
+            else
+            {
+                $this->adminAuth[$this->currentDealFd] = time();
+                $this->sendToClient("Hello Admin \n");
+                return;
+            }
+        }
+        
+        
+        // 单独停止某个worker进程
+        if(preg_match("/kill (\d+)/", $buffer, $match))
+        {
+            $pid = $match[1];
+            $this->sendToClient("Kill Pid $pid\n");
+            if(!posix_kill($pid, SIGTHUB))
+            {
+                $this->sendToClient("Pid Not Exsits\n");
+            }
+            return;
+        }
+        
+        $master_pid = file_get_contents(WORKERMAN_PID_FILE);
+        
+        switch($buffer)
+        {
+            // 展示统计信息
+            case 'status':
+                $status = $this->getMasterStatus();
+                if(empty($status))
+                {
+                    $this->sendToClient("Can not get Master status, Extension sysvshm or sysvmsg may not enabled\n");
+                    return;
+                }
+                $worker_pids = $this->getWorkerPidMap();
+                $pid_worker_name_map = $this->getPidWorkerMap();
+                foreach($worker_pids as $worker_name=>$pid_array)
+                {
+                    if($this->maxWorkerNameLength < strlen($worker_name))
+                    {
+                        $this->maxWorkerNameLength = strlen($worker_name);
+                    }
+                }
+                $msg_type = $message = 0;
+                // 将过期的消息读出来,清理掉
+                if(\WORKERMAN\Core\Master::getQueueId())
+                {
+                    while(msg_receive(\WORKERMAN\Core\Master::getQueueId(), self::MSG_TYPE_STATUS, $msg_type, 1000, $message, true, MSG_IPC_NOWAIT))
+                    {
+                    }
+                }
+                $loadavg = sys_getloadavg();
+                $this->sendToClient("---------------------------------------GLOBAL STATUS--------------------------------------------\n");
+                $this->sendToClient(\WORKERMAN\Core\Master::NAME.' version:' . \WORKERMAN\Core\Master::VERSION . "\n");
+                $this->sendToClient('start time:'. date('Y-m-d H:i:s', $status['start_time']).'   run ' . floor((time()-$status['start_time'])/(24*60*60)). ' days ' . floor(((time()-$status['start_time'])%(24*60*60))/(60*60)) . " hours   \n");
+                $this->sendToClient('load average: ' . implode(", ", $loadavg) . "\n");
+                $this->sendToClient(count($this->connections) . ' users          ' . count($worker_pids) . ' workers       ' . count($pid_worker_name_map)." processes\n");
+                $this->sendToClient(str_pad('worker_name', $this->maxWorkerNameLength) . " exit_status     exit_count\n");
+                foreach($worker_pids as $worker_name=>$pid_array)
+                {
+                    if(isset($status['worker_exit_code'][$worker_name]))
+                    {
+                        foreach($status['worker_exit_code'][$worker_name] as  $exit_status=>$exit_count)
+                        {
+                            $this->sendToClient(str_pad($worker_name, $this->maxWorkerNameLength) . " " . str_pad($exit_status, 16). " $exit_count\n");
+                        }
+                    }
+                    else
+                    {
+                        $this->sendToClient(str_pad($worker_name, $this->maxWorkerNameLength) . " " . str_pad(0, 16). " 0\n");
+                    }
+                }
+                
+                $this->sendToClient("---------------------------------------PROCESS STATUS-------------------------------------------\n");
+                $this->sendToClient("pid\tmemory    proto  port  timestamp  ".str_pad('worker_name', $this->maxWorkerNameLength)." ".str_pad('total_request', 13)." ".str_pad('recv_timeout', 12)." ".str_pad('proc_timeout',12)." ".str_pad('packet_err', 10)." ".str_pad('thunder_herd', 12)." ".str_pad('client_close', 12)." ".str_pad('send_fail', 9)." ".str_pad('throw_exception', 15)." suc/total\n");
+                if(!\WORKERMAN\Core\Master::getQueueId())
+                {
+                    return;
+                }
+                
+                $time_start = time();
+                unset($pid_worker_name_map[posix_getpid()]);
+                $total_worker_count = count($pid_worker_name_map);
+                foreach($pid_worker_name_map as $pid=>$worker_name)
+                {
+                    posix_kill($pid, SIGUSR1);
+                    if($this->getStatusFromQueue())
+                    {
+                        $total_worker_count--;
+                    }
+                }
+                
+                while($total_worker_count > 0)
+                {
+                    if($this->getStatusFromQueue())
+                    {
+                        $total_worker_count--;
+                    }
+                    if(time() - $time_start > 1)
+                    {
+                        break;
+                    }
+                }
+                break;
+                // 停止server
+            case 'stop':
+                if($master_pid)
+                {
+                    $this->sendToClient("stoping....\n");
+                    posix_kill($master_pid, SIGINT);
+                }
+                else
+                {
+                    $this->sendToClient("Can not get master pid\n");
+                }
+                break;
+                // 平滑重启server
+            case 'reload':
+                if($master_pid)
+                {
+                    posix_kill($master_pid, SIGHUP);
+                    $this->sendToClient("Restart Workers\n");
+                }
+                else
+                {
+                    $this->sendToClient("Can not get master pid\n");
+                }
+                break;
+                // admin管理员退出
+            case 'quit':
+                $this->sendToClient("Admin Quit\n");
+                $this->closeClient($this->currentDealFd);
+                break;
+            case '':
+                break;
+            default:
+                $this->sendToClient("Unkonw CMD \nAvailable CMD:\n status     show server status\n stop       stop server\n reload     graceful restart server\n quit       quit and close connection\n kill pid   kill the worker process of the pid\n");
+        }
+    }
+    
+    /**
+     * 从消息队列中获取主进程状态
+     * @return void
+     */
+    protected function getStatusFromQueue()
+    {
+        if(msg_receive(\WORKERMAN\Core\Master::getQueueId(), self::MSG_TYPE_STATUS, $msg_type, 10000, $message, true, MSG_IPC_NOWAIT))
+        {
+            $pid = $message['pid'];
+            $worker_name = $message['worker_name'];
+            $workers = \WORKERMAN\Core\Lib\Config::get('workers');
+            $port = $workers[$worker_name]['socket']['port'];
+            $proto = $workers[$worker_name]['socket']['protocol'];
+            $str = "$pid\t".str_pad(round($message['memory']/(1024*1024),2)."M", 9)." $proto    ". str_pad($port, 5) ." ". $message['start_time'] ." ".str_pad($worker_name, $this->maxWorkerNameLength)." ";
+            if($message)
+            {
+                $str = $str . str_pad($message['total_request'], 14)." ".str_pad($message['recv_timeout'], 12)." ".str_pad($message['proc_timeout'],12)." ".str_pad($message['packet_err'],10)." ".str_pad($message['thunder_herd'],12)." ".str_pad($message['client_close'], 12)." ".str_pad($message['send_fail'],9)." ".str_pad($message['throw_exception'],15)." ".($message['total_request'] == 0 ? 100 : (round(($message['total_request']-($message['proc_timeout']+$message['packet_err']+$message['send_fail']))/$message['total_request'], 6)*100))."%";
+            }
+            else
+            {
+                $str .= var_export($message, true);
+            }
+            $this->sendToClient($str."\n");
+            return true;
+        }
+        return false;
+    }
+    
+    
+    /**
+     * 清理日志目录
+     * @param string $dir
+     * @return void
+     */
+    public function clearLogs($dir)
+    {
+        $time_now = time();
+        foreach(glob($dir."/20*-*-*") as $file)
+        {
+            if(!is_dir($file)) continue;
+            $base_name = basename($file);
+            $log_time = strtotime($base_name);
+            if($log_time === false) continue;
+            if(($time_now - $log_time)/self::SECONDS_ONE_DAY >= self::CLEAR_BEFORE_DAYS)
+            {
+                $this->recursiveDelete($file);
+            }
+            
+        }
+    }
+    
+    /**
+     * 检测主进程是否存在
+     * @return void
+     */
+    public function checkMasterProcess()
+    {
+        $master_pid = \WORKERMAN\Core\Master::getMasterPid();
+        if(!posix_kill($master_pid, 0))
+        {
+            $this->onMasterDead();
+        }
+    }
+    
+    /**
+     * 主进程挂掉会触发
+     * @return void
+     */
+    protected function onMasterDead()
+    {
+        // 不要频繁告警,5分钟告警一次
+        $time_now = time();
+        if($time_now - self::$lastWarningTimeMap[self::WARNING_MASTER_DEAD] < self::WARING_SEND_TIME_LONG)
+        {
+            return;
+        }
+        // 延迟告警,启动脚本kill掉主进程不告警,该进程也会随之kill掉
+        sleep(5);
+        
+        $ip = $this->getIp();
+        
+        $this->sendSms('告警消息 PHPServer框架监控 ip:'.$ip.' 主进程意外退出');
+        
+        // 记录这次告警时间
+        self::$lastWarningTimeMap[self::WARNING_MASTER_DEAD] = $time_now;
+    }
+    
+    /**
+     * 检查主进程状态统计信息
+     * @return void
+     */
+    public function checkMasterStatus()
+    {
+        $status = $this->getMasterStatus();
+        if(empty($status))
+        {
+            $this->notice("can not get master status");
+            return;
+        }
+        $status = $status['worker_exit_code'];
+        if(null === $this->lastMasterStatus)
+        {
+            $this->lastMasterStatus = $status;
+            return;
+        }
+        
+        $max_worker_exit_count = (int)\WORKERMAN\Core\Lib\Config::get("workers.".$this->workerName.".max_worker_exit_count");
+        if($max_worker_exit_count <= 0)
+        {
+            $max_worker_exit_count = 2000;
+        }
+        
+        foreach($status as $worker_name => $code_count_info)
+        {
+            foreach($code_count_info as $code=>$count)
+            {
+                $last_count = isset($this->lastMasterStatus[$worker_name][$code]) ? $this->lastMasterStatus[$worker_name][$code] : 0;
+                $inc_count = $count - $last_count;
+                if($inc_count >= $max_worker_exit_count)
+                {
+                    $this->onTooManyWorkersExits($worker_name, $code, $inc_count);
+                }
+            }
+        }
+        $this->lastMasterStatus = $status;
+    }
+    
+    /**
+     * 检查worker进程是否有严重的内存泄漏
+     * @return void
+     */
+    public function checkMemUsage()
+    {
+        foreach($this->getPidWorkerMap() as $pid=>$worker_name)
+        {
+            $this->checkWorkerMemByPid($pid, $worker_name);
+        }
+    }
+    
+    /**
+     * 根据进程id收集进程内存占用情况
+     * @param int $pid
+     * @return void
+     */
+    protected function checkWorkerMemByPid($pid, $worker_name)
+    {
+        $mem_limit = \WORKERMAN\Core\Lib\Config::get('workers.'.__CLASS__.'.max_mem_limit');
+        if(!$mem_limit)
+        {
+            $mem_limit = self::DEFAULT_MEM_LIMIT;
+        }
+        // 读取系统对该进程统计的信息
+        $status_file = "/proc/$pid/status";
+        if(is_file($status_file))
+        {
+            // 获取信息
+            $status = file_get_contents($status_file);
+            if(empty($status))
+            {
+                return;
+            }
+            // 目前只需要进程的内存占用信息
+            $match = array();
+            if(preg_match('/VmRSS:\s+(\d+)\s+([a-zA-Z]+)/', $status, $match))
+            {
+                $memory_usage = $match[1];
+                if($memory_usage >= $mem_limit)
+                {
+                    posix_kill($pid, SIGHUP);
+                    $this->notice("worker:$worker_name pid:$pid memory exceeds the maximum $memory_usage>=$mem_limit");
+                }
+            }
+        }
+    }
+    
+    /**
+     * 当有大量进程频繁退出时触发
+     * @param string $worker_name
+     * @param int $status
+     * @param int $exit_count
+     * @return void
+     */
+    public function onTooManyWorkersExits($worker_name, $status, $exit_count)
+    {
+        // 不要频繁告警,5分钟告警一次
+        $time_now = time();
+        if($time_now - self::$lastWarningTimeMap[self::WARNING_TOO_MANY_WORKERS_EXIT] < self::WARING_SEND_TIME_LONG)
+        {
+            return;
+        }
+    
+        $ip = $this->getIp();
+    
+        $this->sendSms('告警消息 PHPServer框架监控 '.$ip.' '.$worker_name.'进程频繁退出 退出次数'.$exit_count.' 退出状态码:'.$status);
+    
+        // 记录这次告警时间
+        self::$lastWarningTimeMap[self::WARNING_TOO_MANY_WORKERS_EXIT] = $time_now;
+    }
+    
+    /**
+     * 发送短信
+     * @param int $phone_num
+     * @param string $content
+     * @return void
+     */
+    protected function sendSms($content)
+    {
+        // 短信告警
+        
+    }
+    
+    /**
+     * 获取本地ip
+     * @param string $worker_name
+     * @return string
+     */
+    public function getIp($worker_name = '')
+    {
+        $ip = $this->getLocalIp();
+        if(empty($ip) || $ip == '0.0.0.0' || $ip = '127.0.0.1')
+        {
+            if($worker_name)
+            {
+                $ip = \WORKERMAN\Core\Lib\Config::get('workers.' . $worker_name . '.ip');
+            }
+            if(empty($ip) || $ip == '0.0.0.0' || $ip = '127.0.0.1')
+            {
+                $ret_string = shell_exec('ifconfig');
+                if(preg_match("/:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/", $ret_string, $match))
+                {
+                    $ip = $match[1];
+                }
+            }
+        }
+        return $ip;
+    }
+    
+    /**
+     * 递归删除文件
+     * @param string $path
+     */
+    private function recursiveDelete($path)
+    {
+        return is_file($path) ? unlink($path) : array_map(array($this, 'recursiveDelete'),glob($path.'/*')) == rmdir($path);
+    }
+    
+} 

+ 966 - 0
Workers/StatisticService.php

@@ -0,0 +1,966 @@
+<?php 
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+require_once WORKERMAN_ROOT_DIR . 'Protocols/SimpleHttp.php';
+
+/**
+ * 
+ * 统计中心对外服务进程 查询日志 查询接口调用量 延迟 成功率等
+ * 采用http协议对外服务 使用http:://Server_ip:20202 地址查询统计结果
+ * 
+* @author walkor <worker-man@qq.com>
+ */
+
+class StatisticService extends WORKERMAN\Core\SocketWorker
+{
+    
+    /**
+     * 判断包是否都到达
+     * @see Worker::dealInput()
+     */
+    public function dealInput($recv_str)
+    {
+        return \WORKERMAN\Protocols\SimpleHttp::input($recv_str);
+    }
+    
+    /**
+     * 处理业务逻辑 查询log 查询统计信息
+     * @see Worker::dealProcess()
+     */
+    public function dealProcess($recv_str)
+    {
+        \WORKERMAN\Protocols\SimpleHttp::decode($recv_str);
+        $module = isset($_GET['module']) ? trim($_GET['module']) : '';
+        $interface = isset($_GET['interface']) ? trim($_GET['interface']) : '';
+        $start_time = isset($_GET['start_time']) ? trim($_GET['start_time']) : '';
+        $end_time = isset($_GET['end_time']) ? trim($_GET['end_time']) : '';
+        
+        if(0 === strpos($_SERVER['REQUEST_URI'], '/graph'))
+        {
+            if(!extension_loaded('gd'))
+            {
+                return $this->sendToClient("not suport gd\n");
+            }
+            $type_map = array('request','time');
+            $type = isset($_GET['type']) && in_array($_GET['type'], $type_map) ?  $_GET['type'] : 'request';
+            $this->displayGraph($module, $interface, $type, $start_time);
+        }
+        // 日志
+        elseif(0 === strpos($_SERVER['REQUEST_URI'], '/log'))
+        {
+            $right_str = '';
+            $code = isset($_GET['code']) ? $_GET['code'] : '';
+            $msg = isset($_GET['msg']) ? $_GET['msg'] : '';
+            $pointer = isset($_GET['pointer']) ? $_GET['pointer'] : '';
+            $count = isset($_GET['count']) ? $_GET['count'] : 100;
+            $log_data = $this->getStasticLog($module, $interface , $start_time , $end_time, $code, $msg , $pointer, $count);
+            
+            if($log_data['pointer'] == 0)
+            {
+                return $this->display($log_data['data']);
+            }
+            else
+            {
+                $_GET['pointer'] = $log_data['pointer'];
+                unset($_GET['end_time']);
+                $next_page_url = http_build_query($_GET);
+                $log_data['data'] .= "</br><center><a href='/log/?$next_page_url'>下一页</a></center>";
+                return $this->display(nl2br($log_data['data']));
+            }
+            
+        }
+        // 统计
+        else
+        {
+            // 首页
+            if(empty($module))
+            {
+                return $this->home();
+            }
+            else
+            {
+                if($interface)
+                {
+                    return $this->displayInterface($module, $interface, $start_time, $end_time);
+                }
+                else
+                {
+                    return $this->display();
+                }
+            }
+            
+        }
+        
+        return $this->display();
+    }
+    
+    /**
+     * 统计主页
+     * @return void
+     */
+    protected function home()
+    {
+        $data = '';
+        $address = '127.0.0.1:10101';
+        $sock = stream_socket_client($address);
+        if(!$sock)
+        {
+            return $this->display();
+        }
+        fwrite($sock, 'status');
+        $read_fds = array($sock);
+        $write_fds = $except_fds = array();
+        $time_start = time();
+        while(1)
+        {
+            $ret = @stream_select($read_fds, $write_fds, $except_fds, 1);
+            if(!$ret)
+            {
+                if(time() - $time_start >= 1)
+                {
+                    break;
+                }
+                continue;
+            }
+            foreach($read_fds as $fd)
+            {
+                if($ret_str = fread($fd, 8192))
+                {
+                    $data .= $ret_str;
+                }
+                else
+                {
+                    break;
+                }
+            }
+            if(time() - $time_start >= 1)
+            {
+                break;
+            }
+        }
+        
+        $data = '<pre>'.$data.'</pre>';
+        
+        return $this->display($data);
+    }
+    
+    /**
+     * 接口统计信息
+     * @param string $module
+     * @param string $interface
+     * @param int $start_time
+     * @param int $end_time
+     * @return void
+     */
+    protected function displayInterface($module ,$interface, $start_time, $end_time)
+    {
+        $data = $this->getStatistic($module, $interface, $start_time, $end_time);
+        $suport_gd = extension_loaded('gd');
+        $right_str = '
+        <center>模块:'.$module.' &nbsp; 接口:'.$interface.'</center>
+        </br>
+        '.($suport_gd ? '
+        <img src="/graph/?module='.$module.'&interface='.$interface.'&type=request&start_time='.$start_time.'"/>' : '未安装gd库,图形无法展示') .'
+        <center>请求量</center>
+        </br>
+        '.($suport_gd ? '
+        <img src="/graph/?module='.$module.'&interface='.$interface.'&type=time&start_time='.$start_time.'"/>' : '未安装gd库,图形无法展示') .'
+        <center>延迟单位:秒</center>
+        </br>';
+        
+        $right_str .= '<center>';
+        
+        $date_array = $this->getAvailableStDate($module, $interface);
+        $current_key = strtotime(date('Y-m-d', $start_time ? $start_time : time()));
+        if(!isset($date_array[$current_key]))
+        {
+            $date_array[$current_key] = date('Y-m-d', $current_key);
+        }
+        unset($_GET['start_time']);
+        $st_url = http_build_query($_GET);
+        $date_array_chunk = array_chunk($date_array, 7, true);
+        if($date_array_chunk)
+        {
+            foreach($date_array_chunk as $date_array)
+            {
+                foreach($date_array as $time_stamp => $date)
+                {
+                    $right_str .= ($current_key == $time_stamp) ? ('<a href=/st/?'.$st_url.'&start_time='.$time_stamp.'><b>'.$date.'</b></a>&nbsp;&nbsp;') : ('<a href=/st/?'.$st_url.'&start_time='.$time_stamp.'>'.$date.'</a>&nbsp;&nbsp;');
+                }
+                $right_str .= "<br>";
+            }
+        }
+        
+        $right_str .='<br><br></center>';
+        
+        $right_str .='<table>
+        <tr align="center">
+        <th >时间</th><th>调用总数</th><th>平均耗时</th><th>成功调用总数</th><th>成功平均耗时</th><th>失败调用总数</th><th>失败平均耗时</th><th>成功率</th>
+        </tr>
+        ';
+        
+        if($data)
+        {
+            foreach($data as $item)
+            {
+                $right_str .= "<tr align='center'><td>{$item['time']}</td><td>{$item['total_count']}</td><td>{$item['total_avg_time']}</td><td>{$item['suc_count']}</td><td>{$item['suc_avg_time']}</td><td>".($item['fail_count']>0?("<a href='/log/?module=$module&interface=$interface&start_time=".strtotime($item['time'])."&end_time=".(strtotime($item['time'])+300)."'>{$item['fail_count']}</a>"):$item['fail_count'])."</td><td>{$item['fail_avg_time']}</td><td>".($item['precent']<=98?'<font style="color:red">'.$item['precent'].'%</font>' : $item['precent'].'%')."</td></tr>\n"; 
+            }
+        }
+        
+        $right_str .= '</table>'; 
+        
+        return $this->display($right_str);
+        
+    }
+    
+    /**
+     * 展示曲线图
+     * @param string $module
+     * @param string $interface
+     * @param string $type
+     * @param integer $start_time
+     * @return void
+     */
+    protected function displayGraph($module ,$interface, $type = 'request', $start_time = '')
+    {
+        $data = $this->getStatistic($module, $interface, $start_time);
+        \WORKERMAN\Protocols\SimpleHttp::header("Content-type: image/jpeg");
+        $gg=new buildGraph();
+        $d2 = $d3 = array();
+        $time_point = $start_time ? strtotime(date('Y-m-d',$start_time)) : strtotime(date('Y-m-d'));
+        switch($type)
+        {
+            case 'time':
+                for($i=0;$i<288;$i++)
+                {
+                    $time_point +=300;
+                    $d2[$time_point] = isset($data[$time_point]['total_avg_time']) ? $data[$time_point]['total_avg_time'] : 0;
+                    $d3[$time_point] = isset($data[$time_point]['fail_avg_time']) ? $data[$time_point]['fail_avg_time'] : 0;
+                }
+                break;
+            default:
+                for($i=0;$i<288;$i++)
+                {
+                    $time_point +=300;
+                    $d2[$time_point] = isset($data[$time_point]['total_count']) ? $data[$time_point]['total_count'] : 0;
+                    $d3[$time_point] = isset($data[$time_point]['fail_count']) ? $data[$time_point]['fail_count'] : 0;
+                }
+        }
+        
+        $d2 = array_values($d2);
+        $d3 = array_values($d3);
+        
+        $gg->addData($d2);
+        $gg->addData($d3);
+        $gg->setColors("088A08,b40404");
+        ob_start();
+        // 生成曲线图
+        $gg->build("line",0);      // 参数0表示显示所有曲线,1为显示第一条,依次类推 
+        return $this->sendToClient(\WORKERMAN\Protocols\SimpleHttp::encode(ob_get_clean()));
+    }
+    
+    /**
+     * 获取模块
+     * @return array
+     */
+    public function getModules()
+    {
+        $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/';
+        return glob($st_dir."/*");
+    }
+    
+    /**
+     * 渲染页面
+     * @param string $data
+     * @return bool
+     */
+    protected function display($data=null)
+    {
+        $left_detail = '';
+        $html_left = '<ul>';
+        $current_module = empty($_GET['module']) ? '' : $_GET['module'];
+        if($current_module)
+        {
+            $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/'.$current_module.'/';
+            $all_interface = array();
+            foreach(glob($st_dir."*") as $file)
+            {
+                if(is_dir($file))
+                {
+                    continue;
+                }
+                $tmp = explode("|", basename($file));
+                $interface = trim($tmp[0]);
+                if(isset($all_interface[$interface]))
+                {
+                    continue;
+                }
+                $all_interface[$interface] = $interface;
+                $left_detail .= '<li>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/st/?module='.$current_module.'&interface='.$interface.'">'.$interface.'</a></li>';
+            }
+            
+        }
+        
+        $modules_name_array = $this->getModules();
+        if($modules_name_array)
+        {
+            foreach($modules_name_array as $module_file)
+            {
+                $tmp = explode("/", $module_file);
+                $module = end($tmp);
+                $html_left .= '<li><a href="/st/?module='.$module.'">'.$module.'</a></li>';
+                if($module == $current_module)
+                {
+                    $html_left .= $left_detail;
+                }
+            }
+        }
+        $display_str = <<<EOC
+<html>
+<head>
+<title>WORKERMAN监控</title>
+</head>
+<table>
+<tr valign='top'>
+<td style="border-right:3px solid #dddddd">$html_left</td>
+<td>$data</td>
+</tr>
+</table>
+</html>    
+EOC;
+        return $this->sendToClient(\WORKERMAN\Protocols\SimpleHttp::encode($display_str));
+    }
+    
+    /**
+     * 日志二分查找法
+     * @param int $start_point
+     * @param int $end_point
+     * @param int $time
+     * @param fd $fd
+     * @return int
+     */
+    protected function binarySearch($start_point, $end_point, $time, $fd)
+    {
+        // 计算中点
+        $mid_point = (int)(($end_point+$start_point)/2);
+        
+        // 定位文件指针在中点
+        fseek($fd, $mid_point);
+        
+        // 读第一行
+        $line = fgets($fd);
+        if(feof($fd) || false === $line)
+        {
+            return ftell($fd);
+        }
+        
+        // 第一行可能数据不全,再读一行
+        $line = fgets($fd);
+        if(feof($fd) || false === $line || trim($line) == '')
+        {
+            return ftell($fd);
+        }
+        
+        // 判断是否越界
+        $current_point = ftell($fd);
+        if($current_point>=$end_point)
+        {
+            return $end_point;
+        }
+        
+        // 获得时间
+        $tmp = explode("\t", $line);
+        $tmp_time = strtotime($tmp[0]);
+        
+        // 判断时间,返回指针位置
+        if($tmp_time > $time)
+        {
+            return $this->binarySearch($start_point, $current_point, $time, $fd);
+        } 
+        elseif($tmp_time < $time)
+        {
+            return $this->binarySearch($current_point, $end_point, $time, $fd);
+        }
+        else
+        {
+            return $current_point;
+        }
+    }
+    
+    /**
+     * 获取指定日志
+     * @return array
+     */
+    protected function getStasticLog($module, $interface , $start_time = '', $end_time = '', $code = '', $msg = '', $pointer='', $count=100)
+    {
+        // log文件
+        $log_file = WORKERMAN_LOG_DIR . 'statistic/log/'. ($start_time === '' ? date('Y-m-d') : date('Y-m-d', $start_time));
+        if(!is_readable($log_file))
+        {
+            return array('pointer'=>0, 'data'=>$log_file . 'not exists or not readable');
+        }
+        // 读文件
+        $h = fopen($log_file, 'r');
+        
+        // 如果有时间,则进行二分查找,加速查询
+        if($start_time && $pointer === '' && ($file_size = filesize($log_file) > 5000))
+        {
+            $pointer = $this->binarySearch(0, $file_size, $start_time-1, $h);
+            $pointer = $pointer < 1000 ? 0 : $pointer - 1000; 
+        }
+        
+        // 正则表达式
+        $pattern = "/^([\d: \-]+)\t";
+        
+        if($module)
+        {
+            $pattern .= $module."::";
+        }
+        else
+        {
+            $pattern .= ".*::";
+        }
+        
+        if($interface)
+        {
+            $pattern .= $interface."\t";
+        }
+        else
+        {
+            $pattern .= ".*\t";
+        }
+        
+        if($code !== '')
+        {
+            $pattern .= "code:$code\t";
+        }
+        else 
+        {
+            $pattern .= "code:\d+\t";
+        }
+        
+        if($msg)
+        {
+            $pattern .= "msg:$msg";
+        }
+       
+        $pattern .= '/';
+        
+        // 指定偏移位置
+        if($pointer >= 0)
+        {
+            fseek($h, (int)$pointer);
+        }
+        
+        // 查找符合条件的数据
+        $now_count = 0;
+        $log_buffer = '';
+        
+        while(1)
+        {
+            if(feof($h))
+            {
+                break;
+            }
+            // 读1行
+            $line = fgets($h);
+            if(preg_match($pattern, $line, $match))
+            {
+                // 判断时间是否符合要求
+                $time = strtotime($match[1]);
+                if($start_time)
+                {
+                    if($time<$start_time)
+                    {
+                        continue;
+                    }
+                }
+                if($end_time)
+                {
+                    if($time>$end_time)
+                    {
+                        break;
+                    }
+                }                                    
+                // 收集符合条件的log
+                $log_buffer .= $line;
+                if(++$now_count >= $count)
+                {
+                    break;
+                }
+            }
+        }
+        // 记录偏移位置
+        $pointer = ftell($h);
+        return array('pointer'=>$pointer, 'data'=>$log_buffer);
+    }
+    
+    
+    /**
+     * 获取统计数据
+     * @param string $module
+     * @param string $interface
+     * @param integer $start_time
+     * @param integer $end_time
+     * @return array
+     */
+    protected function getStatistic($module, $interface, $start_time='',$end_time='')
+    {
+        
+        // 正则表达式
+        $need_preg_match =  $start_time || $end_time;
+        $pattern = '';
+        if($need_preg_match)
+        {
+            $pattern .= "/^[\d\.]+\t(\d+)\t/";
+        }
+        
+        // log文件
+        $log_file = WORKERMAN_LOG_DIR . "statistic/st/{$module}/{$interface}|". ($start_time === '' ? date('Y-m-d') : date('Y-m-d', $start_time));
+        if(!is_readable($log_file))
+        {
+            return false;
+        }
+        
+        // 读文件
+        $h = fopen($log_file, 'r');
+        
+        // time:[suc_count:xx,suc_cost_time:xx,fail_count:xx,fail_cost_time:xx]
+        $st_data = array();
+        // 汇总计算
+        while(1)
+        {
+            if(feof($h))
+            {
+                break;
+            }
+            // 读1行
+            $line = fgets($h);
+            if(empty($line))
+            {
+                continue;
+            }
+            if($need_preg_match && preg_match($pattern, $line, $match))
+            {
+                // 判断时间是否符合要求
+                $time = $match[1];
+                if($start_time)
+                {
+                    if($time<=$start_time)
+                    {
+                        continue;
+                    }
+                }
+                if($end_time)
+                {
+                    if($time>=$end_time)
+                    {
+                        continue;
+                    }
+                }
+                
+            }
+            // line = IP time suc_count suc_cost_time fail_count fail_cost_time code_json
+            $line_data = explode("\t", $line);
+            $time_line = $line_data[1];
+            $suc_count = $line_data[2];
+            $suc_cost_time = $line_data[3];
+            $fail_count = $line_data[4];
+            $fail_cost_time = $line_data[5];
+            if(!isset($st_data[$time_line]))
+            {
+                $st_data[$time_line] = array('suc_count'=>0, 'suc_cost_time'=>0, 'fail_count'=>0, 'fail_cost_time'=>0);
+            }
+            $st_data[$time_line]['suc_count'] += $suc_count;
+            $st_data[$time_line]['suc_cost_time'] += $suc_cost_time;
+            $st_data[$time_line]['fail_count'] += $fail_count;
+            $st_data[$time_line]['fail_cost_time'] += $fail_cost_time;
+        }
+        // 按照时间排序
+        ksort($st_data);
+        // time => [total_count:xx,suc_count:xx,suc_avg_time:xx,fail_count:xx,fail_avg_time:xx,percent:xx]
+        $data = array();
+        // 计算成功率 耗时
+        foreach($st_data as $time_line=>$item)
+        {
+            $data[$time_line] = array(
+                'time'          => date('Y-m-d H:i:s', $time_line),
+                'total_count'   => $item['suc_count']+$item['fail_count'],
+                'total_avg_time'=> $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_cost_time']+$item['fail_cost_time'])/($item['suc_count']+$item['fail_count']), 4),
+                'suc_count'     => $item['suc_count'],
+                'suc_avg_time'  => $item['suc_count'] == 0 ? $item['suc_count'] : round($item['suc_cost_time']/$item['suc_count'], 4),
+                'fail_count'    => $item['fail_count'],
+                'fail_avg_time' => $item['fail_count'] == 0 ? 0 : round($item['fail_cost_time']/$item['fail_count'], 4),
+                'precent'       => $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_count']*100/($item['suc_count']+$item['fail_count'])), 4),
+            );
+        }
+        $max_time_line = $time_line;
+        $time_point = $start_time ? strtotime(date('Y-m-d', $start_time)) : strtotime(date('Y-m-d'))+300;
+        for($i=0;$i<288,$time_point<=$max_time_line;$i++)
+        {
+            $data[$time_point] = isset($data[$time_point]) ? $data[$time_point] : 
+            array(
+                    'time'          => date('Y-m-d H:i:s', $time_point),
+                    'total_count'   => 0,
+                    'total_avg_time'=> 0,
+                    'suc_count'     => 0,
+                    'suc_avg_time'  => 0,
+                    'fail_count'    => 0,
+                    'fail_avg_time' => 0,
+                    'precent'       => 100,
+                    );
+            $time_point +=300;
+        }
+        ksort($data);
+        return $data;
+    }
+    
+    /**
+     * 获取能展示统计数据的日期
+     * @param string $module
+     * @param string $interface
+     * @return array
+     */
+    protected function getAvailableStDate($module, $interface)
+    {
+        $date_array = array();
+        $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/'.$module.'/';
+        foreach(glob($st_dir."$interface|*") as $stFile)
+        {
+            $base_name = basename($stFile);
+            $tmp = explode('|', $base_name);
+            $date_array[strtotime($tmp[1])] = $tmp[1];
+        }
+        ksort($date_array);
+        return $date_array;
+    }
+    
+    /**
+     * 缓冲页面输出(non-PHPdoc)
+     * @see SocketWorker::onStart()
+     */
+    public function onStart()
+    {
+        ob_start();
+    }
+    
+    /**
+     * 获取缓冲(non-PHPdoc)
+     * @see SocketWorker::onAlarm()
+     */
+    public function onAlarm()
+    {
+        $ob_content = ob_get_contents();
+        if($ob_content)
+        {
+            \WORKERMAN\Core\Lib\Log::add('StatisticService:ob_content:'.$ob_content);
+            ob_clean();
+        }
+    }
+    
+}
+
+
+/**
+ * 
+ * 画图的一个类
+ *
+ */
+class buildGraph {
+    protected $graphwidth=800;
+    protected $graphheight=300;
+    protected $width_num=0;          // 宽分多少等分
+    protected $height_num=10;          // 高分多少等分,默认为10
+    protected $height_var=0;          // 高度增量(用户数据平均数)
+    protected $width_var=0;          // 宽度增量(用户数据平均数)
+    protected $height_max=0;          // 最大数据值
+    protected $array_data=array();      // 用户待分析的数据的二维数组
+    protected $array_error=array();      // 收集错误信息
+
+    protected $colorBg=array(255,255,255);  // 图形背景-白色
+    protected $colorGrey=array(192,192,192);  // 灰色画框
+    protected $colorBlue=array(0,0,255);     // 蓝色
+    protected $colorRed=array(255,0,0);    // 红色(点)
+    protected $colorDarkBlue=array(0, 0, 255);  // 深色
+    protected $colorBlack=array(0,0,0);
+    protected $colorLightBlue=array(200,200,255);     // 浅色
+
+    protected $array_color;          // 曲线着色(存储十六进制数)
+    protected $image;              // 我们的图像
+
+
+    /**
+     * 方法:接受用户数据
+     */
+    function addData($array_user_data){
+        if(!is_array($array_user_data) or empty($array_user_data)){
+            $this->array_error['addData']="没有可供分析的数据";
+            return false;
+        }
+        $i=count($this->array_data);
+        $this->array_data[$i]=$array_user_data;
+    }
+
+    /**
+     * 方法:定义画布宽和长
+     */
+    function setImg($img_width,$img_height){
+        $this->graphwidth=$img_width;
+        $this->graphheight=$img_height;
+    }
+
+    /**
+     * 设定Y轴的增量等分,默认为10份
+     */
+    function setHeightNum($var_y){
+        $this->height_num=$var_y;
+    }
+
+    /**
+     * 定义各图形各部分色彩
+     */
+    function getRgb($color){        // 得到十进制色彩
+        $R=($color>>16) &0xff;
+        $G=($color>>8) &0xff;
+        $B=($color) & 0xff;
+        return(array($R,$G,$B));
+    }
+    
+    /**
+     * 定义背景色
+     * @param unknown_type $c1
+     * @param unknown_type $c2
+     * @param unknown_type $c3
+     */
+    function setColorBg($c1,$c2,$c3){
+        $this->colorBg=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 定义画框色
+     */
+    function setColorGrey($c1,$c2,$c3){
+        $this->colorGrey=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 定义蓝色
+     * @param unknown_type $c1
+     * @param unknown_type $c2
+     * @param unknown_type $c3
+     */
+    function setColorBlue($c1,$c2,$c3){
+        $this->colorBlue=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 定义色Red
+     */
+    function setColorRed($c1,$c2,$c3){
+        $this->colorRed=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 定义深色
+     * @param unknown_type $c1
+     * @param unknown_type $c2
+     * @param unknown_type $c3
+     */
+    function setColorDarkBlue($c1,$c2,$c3){
+        $this->colorDarkBlue=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 定义浅色
+     * @param unknown_type $c1
+     * @param unknown_type $c2
+     * @param unknown_type $c3
+     */
+    function setColorLightBlue($c1,$c2,$c3){
+        $this->colorLightBlue=array($c1,$c2,$c3);
+    }
+    
+    /**
+     * 方法:由用户数据将画布分成若干等份宽
+     * 并计算出每份多少像素
+     */
+    function getWidthNum(){
+        $this->width_num=count($this->array_data[0]);
+    }
+    
+    /**
+     * 
+     * @return mixed
+     */
+    function getMaxHeight(){
+        // 获得用户数据的最大值
+        $tmpvar=array();
+        foreach($this->array_data as$tmp_value){
+            $tmpvar[]=max($tmp_value);
+        }
+        $this->height_max=max($tmpvar);
+        return max($tmpvar);
+    }
+    
+    /**
+     * 
+     * @return number
+     */
+    function getHeightLength(){
+        // 计算出每格的增量长度(用户数据,而不是图形的像素值)
+        $max_var=$this->getMaxHeight();
+        $max_var=ceil($max_var/$this->height_num);
+            $first_num=substr($max_var,0,1);
+            if(substr($max_var,1,1)){
+            if(substr($max_var,1,1)>=5)
+            $first_num+=1;
+        }
+        for($i=1;$i<strlen($max_var);$i++){
+        $first_num.="0";
+        }
+        return (int)$first_num;
+    }
+    
+    /**
+     * 
+     */
+    function getVarWh(){      // 得到高和宽的增量
+        $this->getWidthNum();
+        // 得到高度增量和宽度增量
+        $this->height_var=$this->getHeightLength();
+        $this->width_var=$this->graphwidth/$this->width_num;
+    }
+    
+    /**
+     * 
+     * @param unknown_type $str_colors
+     */
+    function setColors($str_colors){
+        // 用于多条曲线的不同着色,如$str_colors="ee00ff,dd0000,cccccc"
+        $this->array_color=explode(",",$str_colors);
+    }
+    
+    /**
+     * 
+     * @param unknown_type $var_num
+     */
+    function buildLine($var_num){
+        if(!empty($var_num)){            // 如果用户只选择显示一条曲线
+            $array_tmp[0]=$this->array_data[$var_num-1];
+            $this->array_data=$array_tmp;
+        }
+        
+        for($j=0;$j<count($this->array_data);$j++){
+            list($R,$G,$B)=$this->getRgb(hexdec($this->array_color[$j]));
+            $colorBlue=imagecolorallocate($this->image,$R,$G,$B);
+        
+            for($i=0;$i<$this->width_num-1;$i++){
+                $height_pix=$this->height_max == 0 ? 0 : round(($this->array_data[$j][$i]/$this->height_max)*$this->graphheight);
+                $height_next_pix= $this->height_max*$this->graphheight == 0 ? 0 : round($this->array_data[$j][$i+1]/$this->height_max*$this->graphheight);
+                imageline($this->image,$this->width_var*$i,$this->graphheight-$height_pix,$this->width_var*($i+1),$this->graphheight-$height_next_pix,$colorBlue);
+            }
+        }
+    }
+    
+    /**
+     * 
+     * @param unknown_type $select_gra
+     */
+    function buildRectangle($select_gra){
+        if(!empty($select_gra)){            // 用户选择显示一个矩形
+            $select_gra-=1;
+        }
+        // 画矩形
+        // 配色
+        $colorDarkBlue=imagecolorallocate($this->image,$this->colorDarkBlue[0], $this->colorDarkBlue[1],$this->colorDarkBlue[2]);
+        $colorLightBlue=imagecolorallocate($this->image,$this->colorLightBlue[0], $this->colorLightBlue[1],$this->colorLightBlue[2]);
+    
+        if(empty($select_gra))
+            $select_gra=0;
+        
+        for($i=0; $i<$this->width_num; $i++){
+            $height_pix = $this->height_max == 0 ? 0 : round(($this->array_data[$select_gra][$i]/$this->height_max)*$this->graphheight);
+            imagefilledrectangle($this->image,$this->width_var*$i,$this->graphheight-$height_pix,$this->width_var*($i+1),$this->graphheight,$colorDarkBlue);
+            imagefilledrectangle($this->image,($i*$this->width_var)+1,($this->graphheight-$height_pix)+1,$this->width_var*($i+1)-5,$this->graphheight-2,$colorLightBlue);
+        }
+    }
+    
+    /**
+     * 
+     */
+    function createCloths(){
+        // 创建画布
+        $this->image=imagecreate($this->graphwidth+20,$this->graphheight+20);
+    }
+    
+    /**
+     * 
+     */
+    function  createFrame(){
+        // 创建画框
+        $this->getVarWh();
+        // 配色
+        $colorBg=imagecolorallocate($this->image,$this->colorBg[0], $this->colorBg[1],$this->colorBg[2]);
+        $colorGrey=imagecolorallocate($this->image,$this->colorGrey[0], $this->colorGrey[1],$this->colorGrey[2]);
+        // 创建图像周围的框
+        imageline($this->image, 0, 0, 0,$this->graphheight,$colorGrey);
+        imageline($this->image, 0, 0,$this->graphwidth, 0,$colorGrey);
+        imageline($this->image,($this->graphwidth-1),0,($this->graphwidth-1),($this->graphheight-1),$colorGrey);
+        imageline($this->image,0,($this->graphheight-1),($this->graphwidth-1),($this->graphheight-1),$colorGrey);
+    }
+    
+    /**
+     * 
+     */
+    function  createLine(){
+        // 创建网格。
+        $this->getVarWh();
+        $colorBg=imagecolorallocate($this->image,$this->colorBg[0], $this->colorBg[1],$this->colorBg[2]);
+        $colorGrey=imagecolorallocate($this->image,$this->colorGrey[0], $this->colorGrey[1],$this->colorGrey[2]);
+        $colorRed=imagecolorallocate($this->image,$this->colorRed[0], $this->colorRed[1],$this->colorRed[2]);
+        $colorBlack=imagecolorallocate($this->image,$this->colorBlack[0], $this->colorBlack[1],$this->colorBlack[2]);
+        for($j=0;$j<$this->width_num;$j++){
+             if($j%12 == 0){
+                 // 画竖线
+                 imageline($this->image,$j*$this->width_var,0,$j*$this->width_var,$this->graphheight,$colorGrey);
+                 // 标出数字
+                 imagestring($this->image,2,$this->width_var*$j,$this->graphheight,$j/12,$colorBlack);
+           }
+         }
+        
+        for($i=1;$i<=$this->height_num;$i++){
+            // 画横线
+            imageline($this->image,0,$this->graphheight-($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$this->graphwidth,$this->graphheight - ($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$colorGrey);
+            // 标出数字
+            imagestring($this->image,2,0,$this->graphheight-($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$this->height_var*$i,$colorBlack);
+        }
+    }
+    
+    /**
+     * 
+     * @param unknown_type $graph
+     * @param unknown_type $str_var
+     */
+    function build($graph,$str_var){
+        // $graph是用户指定的图形种类,$str_var是生成哪个数据的图
+        $this->createCloths();      // 先要有画布啊~~
+        switch ($graph){
+            case"line":
+                $this->createFrame();      // 画个框先:)
+                $this->createLine();      // 打上底格线
+                $this->buildLine($str_var);      // 画曲线
+                break;
+            case"rectangle":
+                $this->createFrame();            // 画个框先:)
+                $this->buildRectangle($str_var);      // 画矩形
+                $this->createLine();            // 打上底格线
+            break;
+        }
+    
+    
+        // 输出图形并清除内存
+        imagepng($this->image);
+        imagedestroy($this->image);
+    }
+    
+}
+    

+ 328 - 0
Workers/StatisticWorker.php

@@ -0,0 +1,328 @@
+<?php 
+require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
+/**
+ * 
+ * 接口成功率统计worker
+ * 定时写入磁盘,用来统计请求量、延迟、波动等信息
+* @author walkor <worker-man@qq.com>
+ */
+class StatisticWorker extends WORKERMAN\Core\SocketWorker
+{
+    /**
+     * 最大buffer长度
+     * @var ineger
+     */
+    const MAX_BUFFER_SIZE = 524288;
+    
+    /**
+     * 上次写日志数据到磁盘的时间
+     * @var integer
+     */
+    protected $logLastWriteTime = 0;
+    
+    /**
+     * 上次写统计数据到磁盘的时间
+     * @var integer
+     */
+    protected $stLastWriteTime = 0;
+    
+    /**
+     * 上次清理磁盘的时间
+     * @var integer
+     */
+    protected $lastClearTime = 0;
+    
+    /**
+     * 缓冲的日志数据
+     * @var string
+     */
+    protected $logBuffer = '';
+    
+    /**
+     * 缓冲的统计数据
+     * modid=>interface=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx, 'time'=>xxx]
+     * @var array
+     */
+    protected $statisticData = array();
+    
+    /**
+     * 多长时间写一次log数据
+     * @var integer
+     */
+    protected $logSendTimeLong = 20;
+    
+    /**
+     * 多长时间写一次统计数据
+     * @var integer
+     */
+    protected $stSendTimeLong = 300;
+    
+    /**
+     * 多长时间清除一次统计数据
+     * @var integer
+     */
+    protected $clearTimeLong = 86400;
+    
+    /**
+     * 日志过期时间 14days
+     * @var integer
+     */
+    protected $logExpTimeLong = 1296000;
+    
+    /**
+     * 统计结果过期时间 14days
+     * @var integer
+     */
+    protected $stExpTimeLong = 1296000;
+    
+    /**
+     * 固定包长
+     * @var integer
+     */
+    const PACKEGE_FIXED_LENGTH = 25;
+    
+    
+    
+    /**
+     * 默认只收1个包
+     * 上报包的格式如下
+     * struct{
+     *     int                                    code,                 // 返回码
+     *     unsigned int                           time,                 // 时间
+     *     float                                  cost_time,            // 消耗时间 单位秒 例如1.xxx
+     *     unsigned int                           source_ip,            // 来源ip
+     *     unsigned int                           target_ip,            // 目标ip
+     *     unsigned char                          success,              // 是否成功
+     *     unsigned char                          module_name_length,   // 模块名字长度
+     *     unsigned char                          interface_name_length,//接口名字长度
+     *     unsigned short                         msg_length,           // 日志信息长度
+     *     unsigned char[module_name_length]      module,               // 模块名字
+     *     unsigned char[interface_name_length]   interface,            // 接口名字
+     *     char[msg_length]                       msg                   // 日志内容
+     *  }
+     * @see Worker::dealInput()
+     */
+    public function dealInput($recv_str)
+    {
+        return 0;
+    }
+    
+    /**
+     * 处理上报的数据 log buffer满的时候写入磁盘
+     * @see Worker::dealProcess()
+     */
+    public function dealProcess($recv_str)
+    {
+        // 解包
+        $time_now = time();
+        $unpack_data = unpack("icode/Itime/fcost_time/Isource_ip/Itarget_ip/Csuccess/Cmodule_name_length/Cinterface_name_length/Smsg_length", $recv_str);
+        $module = substr($recv_str, self::PACKEGE_FIXED_LENGTH, $unpack_data['module_name_length']);
+        $interface = substr($recv_str, self::PACKEGE_FIXED_LENGTH + $unpack_data['module_name_length'], $unpack_data['interface_name_length']);
+        $msg = substr($recv_str, self::PACKEGE_FIXED_LENGTH + $unpack_data['module_name_length'] + $unpack_data['interface_name_length'], $unpack_data['msg_length']);
+        $msg = str_replace("\n", '<br>', $msg);
+        $code = $unpack_data['code'];
+        
+        // 统计调用量、延迟、成功率等信息
+        if(!isset($this->statisticData[$module]))
+        {
+            $this->statisticData[$module] = array();
+        }
+        if(!isset($this->statisticData[$module][$interface]))
+        {
+            $this->statisticData[$module][$interface] = array('code'=>array(), 'suc_cost_time'=>0, 'fail_cost_time'=>0, 'suc_count'=>0, 'fail_count'=>0, 'time'=>$this->stLastWriteTime);
+        }
+        if(!isset($this->statisticData[$module][$interface]['code'][$code]))
+        {
+            $this->statisticData[$module][$interface]['code'][$code] = 0;
+        }
+        $this->statisticData[$module][$interface]['code'][$code]++;
+        if($unpack_data['success'])
+        {
+            $this->statisticData[$module][$interface]['suc_cost_time'] += $unpack_data['cost_time'];
+            $this->statisticData[$module][$interface]['suc_count'] ++;
+        }
+        else
+        {
+            $this->statisticData[$module][$interface]['fail_cost_time'] += $unpack_data['cost_time'];
+            $this->statisticData[$module][$interface]['fail_count'] ++;
+        }
+        
+        // 如果不成功写入日志
+        if(!$unpack_data['success'])
+        {
+            $log_str = date('Y-m-d H:i:s',$unpack_data['time'])."\t{$module}::{$interface}\tcode:{$unpack_data['code']}\tmsg:{$msg}\tsource_ip:".long2ip($unpack_data['source_ip'])."\ttarget_ip:".long2ip($unpack_data['target_ip'])."\n";
+            // 如果buffer溢出,则写磁盘,并清空buffer
+            if(strlen($this->logBuffer) + strlen($recv_str) > self::MAX_BUFFER_SIZE)
+            {
+                // 写入log数据到磁盘
+                $this->wirteLogToDisk();
+                $this->logBuffer = $log_str;
+            }
+            else 
+            {
+                $this->logBuffer .= $log_str;
+            }
+        }
+        
+    }
+    
+    /**
+     * 将日志数据写入磁盘
+     * @return void
+     */
+    protected function wirteLogToDisk()
+    {
+        // 初始化下一波统计数据
+        $this->logLastWriteTime = time();
+        
+        // 有数据才写
+        if(empty($this->logBuffer))
+        {
+            return true;
+        }
+        
+        file_put_contents(WORKERMAN_LOG_DIR . 'statistic/log/'.date('Y-m-d', $this->logLastWriteTime), $this->logBuffer, FILE_APPEND | LOCK_EX);
+        
+        $this->logBuffer = '';
+    }
+    
+    /**
+     * 将统计数据写入磁盘
+     * @return void
+     */
+    protected function wirteStToDisk()
+    {
+        // 记录
+        $this->stLastWriteTime = $this->stLastWriteTime + $this->stSendTimeLong;
+        
+        // 有数据才写磁盘
+        if(empty($this->statisticData))
+        {
+            return true;
+        }
+        
+        $ip = $this->getRemoteIp();
+        
+        foreach($this->statisticData as $module=>$items)
+        {
+            if(!is_dir(WORKERMAN_LOG_DIR . 'statistic/st/'.$module))
+            {
+                umask(0);
+                mkdir(WORKERMAN_LOG_DIR . 'statistic/st/'.$module, 0777, true);
+            }
+            foreach($items as $interface=>$data)
+            {
+                // modid=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx, 'time'=>xxx]
+                file_put_contents(WORKERMAN_LOG_DIR . "statistic/st/{$module}/{$interface}|".date('Y-m-d',$data['time']-1), "$ip\t{$data['time']}\t{$data['suc_count']}\t{$data['suc_cost_time']}\t{$data['fail_count']}\t{$data['fail_cost_time']}\t".json_encode($data['code'])."\n", FILE_APPEND | LOCK_EX);
+            }
+        }
+        
+        $this->statisticData = array();
+    }
+    
+    /**
+     * 该worker进程开始服务的时候会触发一次,初始化$logLastWriteTime
+     * @return bool
+     */
+    protected function onStart()
+    {
+        // 创建LOG目录
+        if(!is_dir(WORKERMAN_LOG_DIR . 'statistic/log'))
+        {
+            umask(0);
+            @mkdir(WORKERMAN_LOG_DIR . 'statistic/log', 0777, true);
+        }
+        
+        $time_now = time();
+        $this->logLastWriteTime = $time_now;
+        $this->stLastWriteTime = $time_now - $time_now%$this->stSendTimeLong;
+        \WORKERMAN\Core\Lib\Task::init($this->event);
+        \WORKERMAN\Core\Lib\Task::add(1, array($this, 'onAlarm'));
+    }
+    
+    /**
+     * 该worker进程停止服务的时候会触发一次,保存数据到磁盘
+     * @return bool
+     */
+    protected function onStop()
+    {
+        // 发送数据到统计中心
+        $this->wirteLogToDisk();
+        $this->wirteStToDisk();
+        return false;
+    }
+    
+    /**
+     * 每隔一定时间触发一次 
+     * @see Worker::onAlarm()
+     */
+    public function onAlarm()
+    {
+        $time_now = time();
+        // 检查距离最后一次发送数据到统计中心的时间是否超过设定时间
+        if($time_now - $this->logLastWriteTime >= $this->logSendTimeLong)
+        {
+            // 发送数据到统计中心
+            $this->wirteLogToDisk();
+        }
+        // 检查是否到了该发送统计数据的时间
+        if($time_now - $this->stLastWriteTime >= $this->stSendTimeLong)
+        {
+            $this->wirteStToDisk();
+        }
+        
+        // 检查是否到了清理数据的时间
+        if($time_now - $this->lastClearTime >= $this->clearTimeLong)
+        {
+            $this->lastClearTime = $time_now;
+            $this->clearDisk(WORKERMAN_LOG_DIR . 'statistic/log/', $this->logExpTimeLong);
+            $this->clearDisk(WORKERMAN_LOG_DIR . 'statistic/st/', $this->stExpTimeLong);
+        }
+    }
+    
+    
+    /**
+     * 清除磁盘数据
+     * @param string $file
+     * @param int $exp_time
+     */
+    protected function clearDisk($file = null, $exp_time = 86400)
+    {
+        $time_now = time();
+        if(is_file($file)) 
+        {
+            $stat = stat($file);
+            if(!$stat)
+            {
+                $this->notice("stat $file fail");
+                return;
+            }
+            $mtime = $stat['mtime'];
+            if($time_now - $mtime > $exp_time)
+            {
+                unlink($file);
+            }
+            return;
+        }
+        foreach (glob($file."/*") as $file_name) {
+            if(is_dir($file_name))
+            {
+                $this->clearDisk($file_name, $exp_time);
+                continue;
+            }
+            $stat = stat($file_name);
+            if(!$stat)
+            {
+                $this->notice("stat $file fail");
+                return;
+            }
+            $mtime = $stat['mtime'];
+            if($time_now - $mtime > $exp_time)
+            {
+                unlink($file_name);
+            }
+        }
+        
+    }
+    
+} 

+ 188 - 0
bin/workermand

@@ -0,0 +1,188 @@
+#!/usr/bin/env php
+<?php
+if(empty($argv[1]))
+{
+    echo "Usage: serverd {start|stop|restart|reload|kill|status}\n";
+    exit;
+}
+
+$cmd = $argv[1];
+
+define('WORKERMAN_ROOT_DIR', realpath(__DIR__."/../")."/");
+
+// ==pid-file==
+require_once WORKERMAN_ROOT_DIR . 'Core/Lib/Config.php';
+if(!($pid_file = WORKERMAN\Core\Lib\Config::get('pid_file')))
+{
+    $pid_file = '/var/run/php-server.pid';
+}
+define('WORKERMAN_PID_FILE', $pid_file);
+
+// ==log-dir==
+if(!($log_dir = WORKERMAN\Core\Lib\Config::get('log_dir')))
+{
+    $log_dir = WORKERMAN_ROOT_DIR . 'logs/';
+}
+define('WORKERMAN_LOG_DIR', $log_dir . '/');
+
+// ==ipc-key==
+if(!($ipc_key = WORKERMAN\Core\Lib\Config::get('ipc_key')))
+{
+    $ipc_key = 0x70010a2e;
+}
+define('IPC_KEY', $ipc_key);
+
+// ==shm-size==
+if(!($shm_size = WORKERMAN\Core\Lib\Config::get('shm_size')))
+{
+    $shm_size = 393216;
+}
+define('DEFAULT_SHM_SIZE', $shm_size);
+
+require_once WORKERMAN_ROOT_DIR . "Core/Master.php";
+
+chdir(WORKERMAN_ROOT_DIR."Core");
+
+//检查pid对应的进程是否存在,不存在删除PID文件
+if($cmd != 'status' && is_file(WORKERMAN_PID_FILE))
+{
+    //检查权限
+    if(!posix_access(WORKERMAN_PID_FILE, POSIX_W_OK))
+    {
+        if($stat = stat(WORKERMAN_PID_FILE))
+        {
+            if(($start_pwuid = posix_getpwuid($stat['uid'])) && ($current_pwuid = posix_getpwuid(posix_getuid())))
+            {
+                exit("\n\033[31;40mServer is started by user {$start_pwuid['name']}, {$current_pwuid['name']} can not $cmd Server, Permission denied\033[0m\n\n\033[31;40mServer $cmd failed\033[0m\n\n");
+            }
+        }
+        exit("Can not $cmd Server, Permission denied\n");
+    }
+    //检查pid进程是否存在
+    if($pid = @file_get_contents(WORKERMAN_PID_FILE))
+    {
+        if(false === posix_kill($pid, 0))
+        {
+            if(!unlink(WORKERMAN_PID_FILE))
+            {
+                exit("Can not $cmd Server\n\n");
+            }
+        }
+    }
+}
+
+switch($cmd)
+{
+    case 'start':
+        $worker_user = isset($argv[2]) ? $argv[2] : '';
+        WORKERMAN\Core\Master::run($worker_user);
+        break;
+    case 'stop':
+        $pid = @file_get_contents(WORKERMAN_PID_FILE);
+        if(empty($pid))
+        {
+            exit("Server not running?\n");
+        }
+        stop_and_wait();
+        break;
+    case 'restart':
+        stop_and_wait();
+        $worker_user = isset($argv[2]) ? $argv[2] : '';
+        WORKERMAN\Core\Master::run();
+        break;
+    case 'reload':
+        $pid = @file_get_contents(WORKERMAN_PID_FILE);
+        if(empty($pid))
+        {
+            exit("server not running?\n");
+        }
+        posix_kill($pid, SIGHUP);
+        echo "reload PHP-Server\n";
+        break;
+    case 'kill':
+        force_kill();
+        force_kill();
+        break;
+    case 'status':
+        $ip = '127.0.0.1';
+        $port = WORKERMAN\Core\Lib\Config::get('workers.Monitor.socket.port');
+        $port = $port ? $port : 10101;
+        $address = "$ip:$port";
+        $sock = @stream_socket_client($address);
+        if(!$sock)
+        {
+            exit("\n\033[31;40mcan not connect to $address \033[0m\n\n\033[31;40mServer not running\033[0m\n\n");
+        }
+        fwrite($sock, 'status');
+        $read_fds = array($sock);
+        $write_fds = $except_fds = array();
+        while($ret = stream_select($read_fds, $write_fds, $except_fds, 1))
+        {
+            if(!$ret)break;
+            foreach($read_fds as $fd)
+            {
+                if($ret_str = fread($fd, 8192))
+                {
+                    echo $ret_str;
+                }
+                else
+                {
+                    exit;
+                }
+            }
+        }
+        break;
+    default:
+        echo "Usage: serverd {start|stop|restart|reload|kill|status}\n";
+        exit;
+        
+}
+
+function force_kill()
+{
+    $ret = $match = array();
+    exec("ps aux | grep -E '".WORKERMAN\Core\Master::NAME.":|serverd' | grep -v grep", $ret);
+    $this_pid = posix_getpid();
+    $this_ppid = posix_getppid();
+    foreach($ret as $line)
+    {
+        if(preg_match("/^[\S]+\s+(\d+)\s+/", $line, $match))
+        {
+            $tmp_pid = $match[1];
+            if($this_pid != $tmp_pid && $this_ppid != $tmp_pid)
+            {
+                posix_kill($tmp_pid, SIGKILL);
+            }
+        }
+    }
+}
+
+function stop_and_wait($wait_time = 6)
+{
+    $pid = @file_get_contents(WORKERMAN_PID_FILE);
+    if(empty($pid))
+    {
+        //exit("server not running?\n");
+    }
+    else
+    {
+        $start_time = time();
+        posix_kill($pid, SIGINT);
+        while(is_file(WORKERMAN_PID_FILE))
+        {
+            clearstatcache();
+            usleep(1000);
+            if(time()-$start_time >= $wait_time)
+            {
+                force_kill();
+                force_kill();
+                unlink(WORKERMAN_PID_FILE);
+                usleep(500000);
+                break;
+            }
+        }
+        echo "PHP-Server stoped\n";
+    }
+}
+
+