workerman源码解读

workerman源码解读

workerman集成了进程的管理、网络连接处理。算是比较底层的实现多进程与异步通信的框架,并不是常见的MVC框架。看懂,需要有unix编程知识及相关进程管理的基础。

一种场景,作为httpServer,自定义协议。worker.php中,集中实现了进程管理。Workerman\Connection\TcpConnection.php,实现了通信的细节。先看udp连接,因为比较简单。

学习方式:

1、先了解功能,了解用法。

2、对关键的过程的代码,进行粗略阅读,再精细阅读。(焦点在重点代码)

3、上手拆解。对其中疑惑的代码,插入断点或修改,查看运行效果。

4、记录学习笔记。

准备条件

Linux网络编程

进程复制

进程复制的细节是什么?复制后的子进程,跟父进程一模一样?包含了所有已加载的类?

函数

SplPriorityQueue

php5.3起,内部已经实现了优先级队列的类 , 可以看到,php的优先级队列是用大顶堆实现的算法,其初始化时间复杂度为O(n),重排时间复杂度为O(logn)。

PHP优先级队列

stream_socket_sendto

stream_socket_get_name

参数 ( resource $handle , bool $want_peer )

返回给定的本地或者远程套接字连接的名称。

want_peer

TRUE ,那么将返回 remote 套接字连接名称;如果设置为 FALSE 则返回 local 套接字连接名称

stream_context_set_option

对资源流、数据包或者上下文设置参数

stream_set_blocking

为资源流设置阻塞或者阻塞模式,此函数适用于支持非阻塞模式的任何资源流(常规文件,套接字资源流等)。

stream_context_create

创建并返回一个资源流上下文,该资源流中包含了 options 中提前设定的所有参数的值。

stream_socket_server

创建一个Internet或Unix套解字。

stream_socket_accept

接受由 stream_socket_server() 创建的套接字连接。

workerman中tcp连接,由 stream_socket_server创建一个socket,然后使用stream_socket_accept创建连接,这样多个tcpConnect复用一个socket?

stream_socket_recvfrom

Receives data from a socket, connected or not

restore_error_handler

还原之前的错误处理函数
在使用 set_error_handler() 改变错误处理函数之后,此函数可以用于还原之前的错误处理程序(可以是内置的或者也可以是用户所定义的函数)。

workerman中的使用细节:

\set_error_handler(function(){});
\fclose($this->_mainSocket);
\restore_error_handler();

debug_backtrace

产生一条 PHP 的回溯跟踪,主要用来获取,启动文件名。

posix_kill

发送一个kill信号给进程。

kill -0 pid 不发送任何信号,但是系统会进行错误检查。所以经常用来检查一个进程是否存在,存在则echo $?返回0;不存在返回1 。

$info = \posix_kill($master_pid, 0);
//如果进程存在,$info为true ,否则为false。跟shell略有区别。

posix_getpid

返回当前进程 id号,(整型)。

posix_setsid

让当前的进程变为session leader。

posix_getuid

返回当前进程所属的用户id。

posix_getpwuid

返回用户信息。

posix_getpwuid(\posix_getuid())['name'];

pcntl_signal

安装一个信号处理器

pcntl_signal_dispatch

调用每个等待信号通过pcntl_signal() 安装的处理器

pcntl_wait

等待或返回fork的子进程状态。
wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。如果一个子进程在调用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。关于wait在您系统上工作的详细规范请查看您系统的wait(2)手册。

pcntl_fork

在当前进程当前位置产生分支(子进程)。译注:fork是创建了一个子进程,父进程和子进程都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0。
失败时,在父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。

变为守护进程的例子

<?php
  $pid = pcntl_fork(); // fork
  if ($pid < 0)
    exit;
  else if ($pid) // parent
    exit;
  else { // child
    $sid = posix_setsid();
    if ($sid < 0)
      exit;
    for($i = 0; $i <= 60; $i++) {//do something for 5 minutes
      sleep(5);
    }
  }
?>

workerman中的实现:

protected static function daemonize()
    {
        if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        \umask(0);
        $pid = \pcntl_fork();
        if (-1 === $pid) {
            throw new Exception('Fork fail');
        } elseif ($pid > 0) {
            exit(0);
        }
        if (-1 === \posix_setsid()) {
            throw new Exception("Setsid fail");
        }
        // Fork again avoid SVR4 system regain the control of terminal.
        $pid = \pcntl_fork();
        if (-1 === $pid) {
            throw new Exception("Fork fail");
        } elseif (0 !== $pid) {
            exit(0);
        }
    }

spl_object_hash

返回指定对象的hash_id (注意:是对象)。hash_id 可用于作为保存对象或区分不同对象的hash key,对于每个对象它都是唯一的,并且对同一个对象它总是相同。

如下的一个例子:

<?php
class Stu{
	public $age=1;
	public $init = true;
}
$obj  = new Stu();
$big = $obj;
var_dump(spl_object_hash($obj));
var_dump(spl_object_hash($big));

信号

SIGHUP       1          /* Hangup (POSIX).  */                          终止进程     终端线路挂断
SIGINT       2          /* Interrupt (ANSI).  */                        终止进程     中断进程 Ctrl+C
SIGQUIT      3          /* Quit (POSIX).  */                            建立CORE文件终止进程,并且生成core文件 Ctrl+\
SIGILL       4          /* Illegal instruction (ANSI).  */              建立CORE文件,非法指令
SIGTRAP      5          /* Trace trap (POSIX).  */                      建立CORE文件,跟踪自陷
SIGABRT      6          /* Abort (ANSI).  */
SIGIOT       6          /* IOT trap (4.2 BSD).  */                      建立CORE文件,执行I/O自陷
SIGBUS       7          /* BUS error (4.2 BSD).  */                     建立CORE文件,总线错误
SIGFPE       8          /* Floating-point exception (ANSI).  */         建立CORE文件,浮点异常
SIGKILL      9          /* Kill, unblockable (POSIX).  */               终止进程     杀死进程
SIGUSR1      10         /* User-defined signal 1 (POSIX).  */           终止进程     用户定义信号1
SIGSEGV      11         /* Segmentation violation (ANSI).  */           建立CORE文件,段非法错误
SIGUSR2      12         /* User-defined signal 2 (POSIX).  */           终止进程     用户定义信号2
SIGPIPE      13         /* Broken pipe (POSIX).  */                     终止进程     向一个没有读进程的管道写数据
SIGALARM     14         /* Alarm clock (POSIX).  */                     终止进程     计时器到时
SIGTERM      15         /* Termination (ANSI).  */                      终止进程     软件终止信号
SIGSTKFLT    16         /* Stack fault.  */
SIGCLD       SIGCHLD    /* Same as SIGCHLD (System V).  */
SIGCHLD      17         /* Child status has changed (POSIX).  */        忽略信号     当子进程停止或退出时通知父进程
SIGCONT      18         /* Continue (POSIX).  */                        忽略信号     继续执行一个停止的进程
SIGSTOP      19         /* Stop, unblockable (POSIX).  */               停止进程     非终端来的停止信号
SIGTSTP      20         /* Keyboard stop (POSIX).  */                   停止进程     终端来的停止信号 Ctrl+Z
SIGTTIN      21         /* Background read from tty (POSIX).  */        停止进程     后台进程读终端
SIGTTOU      22         /* Background write to tty (POSIX).  */         停止进程     后台进程写终端
SIGURG       23         /* Urgent condition on socket (4.2 BSD).  */    忽略信号     I/O紧急信号
SIGXCPU      24         /* CPU limit exceeded (4.2 BSD).  */            终止进程     CPU时限超时
SIGXFSZ      25         /* File size limit exceeded (4.2 BSD).  */      终止进程     文件长度过长
SIGVTALRM    26         /* Virtual alarm clock (4.2 BSD).  */           终止进程     虚拟计时器到时
SIGPROF      27         /* Profiling alarm clock (4.2 BSD).  */         终止进程     统计分布图用计时器到时
SIGWINCH     28         /* Window size change (4.3 BSD, Sun).  */       忽略信号     窗口大小发生变化
SIGPOLL      SIGIO      /* Pollable event occurred (System V).  */
SIGIO        29         /* I/O now possible (4.2 BSD).  */              忽略信号     描述符上可以进行I/O
SIGPWR       30         /* Power failure restart (System V).  */
SIGSYS       31         /* Bad system call.  */
SIGUNUSED    31
  1. SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.

  2. SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出

  3. SIGQUIT 和 SIGINT类似, 但由QUIT字符(通常是Ctrl+)来控制. 进程在因收到 SIGQUIT 退出时会产生core文件, 在这个意义上类似于一个程序错误信号.

  4. SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号.

  5. SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用.

  6. SIGABRT 程序自己发现错误并调用abort时产生.

  7. SIGIOT 在PDP-11上由iot指令产生, 在其它机器上和SIGABRT一样.

  8. SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.

  9. SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误.

  10. SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略.

  11. SIGUSR1 留给用户使用

  12. SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

  13. SIGUSR2 留给用户使用

  14. SIGPIPE Broken pipe

  15. SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

  16. SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号.

  17. SIGCHLD 子进程结束时, 父进程会收到这个信号.

  18. SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

  19. SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

  20. SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl+Z)发出这个信号

  21. SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

  22. SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

  23. SIGURG 有”紧急”数据或out-of-band数据到达socket时产生.

  24. SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变

  25. SIGXFSZ 超过文件大小资源限制.

  26. SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

  27. SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

  28. SIGWINCH 窗口大小改变时发出.

  29. SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.

  30. SIGPWR Power failure

有两个信号可以停止进程:SIGTERM和SIGKILL。 SIGTERM 比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。

在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

对于 SIGKILL 信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。

高并发

高并发,需要什么?硬件基础。大内存、多cpu等。

之前看到一个视频讲解:高并发的机制有并发编程、循环编程(进入死循环,不停的遍例可用的节点)。

不同语言的实现:

js

单进程 + 异步事件 + io复用? 底层依赖的是什么? 在pm2的加持下,然后变成多进程。

php

workerman采用的是,复制进程 +epoll + 信号机制。

go

csp通信机制。

java

实现有很多。可以用到多线程。

c

c是基础,是王道。

高并发中的惊群效应(该博主的其他博客也不错)

静态方法、属性

曾经的项目中,使用到Utils类,作为模型方法,其中全部都是static属性、方法。现在,通过workerman的例子可以看出,worker类中,可以用来实例化,代表一个具体的协议,另外,通过其内封装的staic方法,可以直接调用,又相当于master (重新组织语言?)

使用静态方法、静态属性,其实还是相当于全局的函数、变量,只不过,将其的作用域限制在类中,整体感觉有点像单例模式,只不过static要在程序上来就要初始化,而单例还有两种模式区别。

workerman中代码的难度在于,就wokerman,实例化n多workers对象,而又将一些类似全局的static方法、staic属性混入其中。增加了理解难度。另外,进程复制,是多了一套副本,父子进程运行的先后顺序,不固定。还有,关于socket复用,是如何情况?

<?php

class StaticDemo{
	static $instance = 0;
	static $_workers =[];
	public static function getCount(){
		echo self::$instance ."\n";
		var_dump(self::$_workers);
	}
	
	public function __construct(){
		++self::$instance;
		//添加对象添加到数组中
		self::$_workers[] = spl_object_hash($this);
	}
}

$demo1 = new StaticDemo();
$demo1 = new StaticDemo();

StaticDemo::getCount();

源码解读

<?php
//使用命名空间,因为Worker对象需要用到,否则,要使用 new Workerman\Worker
use Workerman\Worker;
//自动加载,需要了解psr4标准,
include '../Autoloader.php';
//实例化一个worker,实际上可以实例化多个,只会触发__construct,做一些初始化工作
//会将该实例放到变量中,static::$_workers[$this->workerId] = $this;
$http_worker = new Worker("http://0.0.0.0:2345");
//告诉,该worker会有8个实例。
$http_worker->count = 8;
//具体的业务处理,以回调的方式使用。
//可能用到闭包,使用外部变量时,需要用到use 变量来声明。
$http_worker->onMessage = function($connection, $data)
{
    $connection->send('hello world'.mt_rand(1000,9999));
};
//下面这个,是整体的核心。包含了一大堆的逻辑。所以,下面将其作为主要的流程来处理。
Worker::runAll();

主要流程

主要流程函数:Worker::runAll(); 其启动整个服务。

public static function runAll()
    {
        static::checkSapiEnv();
        static::init();
        static::lock();
        static::parseCommand();
        static::daemonize();
        static::initWorkers();
        static::installSignal();
        static::saveMasterPid();
        static::unlock();
        static::displayUI();
        static::forkWorkers();
        static::resetStd();
        static::monitorWorkers();
    }

checkSapiEnv

检查环境,cli环境,直接退出。判断操作系统的类型。

init

1、set_error_handler

设置好错误处理,将相关的错误,输出。safeEcho会决定,将错误输出到终端还是文件中。

2、debug_backtrace

利用该函数获取到,启动的文件名称。

$backtrace        = \debug_backtrace();
var_dump($backtrace);
static::$_startFile = $backtrace[\count($backtrace) - 1]['file'];

输出内容如下:

array(2) {
  [0]=>
  array(6) {
    ["file"]=>
    string(39) "/home/chaofml/demo/Workerman/Worker.php"
    ["line"]=>
    int(530)
    ["function"]=>
    string(4) "init"
    ["class"]=>
    string(16) "Workerman\Worker"
    ["type"]=>
    string(2) "::"
    ["args"]=>
    array(0) {
    }
  }
  [1]=>
  array(6) {
    ["file"]=>
    string(42) "/home/chaofml/demo/Workerman/demo/http.php"
    ["line"]=>
    int(14)
    ["function"]=>
    string(6) "runAll"
    ["class"]=>
    string(16) "Workerman\Worker"
    ["type"]=>
    string(2) "::"
    ["args"]=>
    array(0) {
    }
  }
}

所以:static::$_startFile = “/home/chaofml/demo/Workerman/demo/http.php”

pid文件名如:’_home_chaofml_demo_Workerman_demo_http.php.pid’

日志文件

1、文件名,定下来。2、检查是否有该文件名。

标志运行状态

将当前的 运行状态标记为:STATUS_STARTING

static::$_status = static::STATUS_STARTING;

设置当前进程的名称:WorkerMan:***

初始化idMap变量(initId),如果是已经在后台运行的,那么,重新更新变量?

static::$_idMap[$worker_id][index]
检查是否有这个变量,如果有的话,那么,就保持原有的值,否则,给个0值。

lock

上锁。如果上锁失败,则直接退出。

目的是什么呢?难道在关键的过程中,不允许并发的启动?防止出错。(目的,需要再探究。)

parseCommand

解析命令。对start,stop,restart,reload,status,connections命令进行不同的影响。注意,这个时候,当前进程还没有注册任何信号,posix_kill($master_pid, $sig) 是在给老的master发送命令。

如果当前系统非linux系统,直接返回,继续下一个函数。

启动文件命令,从$argv[0]中获取。

解析用户输入的第一个参数command1,$argv[1],如果不在上述6个命令中,程序终止,并提示用法。

解析用户输入的第二个参数command2,$argv[2],如果没有,默认设为空字符串。

针对Start命令,如果command2,是-d,则设置$mode= 提示符。

尝试从文件读取master文件的pid,否则置为0。

判断 ,上面获取到的pid是否还存在,pid不为0,pid制定的进程还存活,另外跟当前的进程id不一样。则,认为,还存在旧的master。

主进程存活,如果启动命令,则提示程序已运行,然后退出。

进程未存活,如果不是启动命令、重启命令,也会提示,进程还没有启动。

上面两条命令的含义,主进程存在,就不该再启动了,如果不存在,应该输入启动、重启的命令。

然后,进入switch阶段,

1、start 设置$daemonize变量。

2、status 进入死循环。 -d 代表是否要持续。

直接echo “\33[H\33[2J\33(B\33[m” 效果等效于,clear命令。

formatStatusData函数,大概相当于,从指定的文件中读取统计数据,进行分析,然后输出。统计文件,也是周期的删除,重建、删除、重建。

while (1) {
	if (\is_file(static::$_statisticsFile)) {
		@\unlink(static::$_statisticsFile);
	}
	// Master process will send SIGUSR2 signal to all child processes.
	\posix_kill($master_pid, SIGUSR2);
	// Sleep 1 second.
	\sleep(1);
	// Clear terminal.
	if ($command2 === '-d') {
		static::safeEcho("\33[H\33[2J\33(B\33[m", true);
	}
	// Echo status data.
	static::safeEcho(static::formatStatusData());
	if ($command2 !== '-d') {
		exit(0);
	}
	static::safeEcho("\nPress Ctrl+C to quit.\n\n");
}
exit(0);

3、connections

执行短暂的操作,然后退出进程。即:

删除统计文件,发送SIGIO信号,等待0.5秒,如果统计文件可读,直接读出来。退出 exit(0)。

4、restart (同stop)

5、stop

-g 代表平滑重启 ,对应信号SIGTERM ,否则 SIGINT 。

发送信号:

$master_pid && \posix_kill($master_pid, $sig)

………………

6、reload

$sig 平滑重启:SIGQUIT,直接:SIGUSR1

\posix_kill($master_pid, $sig);

7、其他

如果非上述的情况,默认直接退出,由于前面有验证,估计这一步也不会执行。

daemonize

使用pcntl_fork函数,对当前的master进程,完成两次复制,最后只有一个进程,存活下来。pcntl_fork函数执行后,进程会变成两个,其中返回为0的,为子进程。大于0,是父进程,但是pid为子进程的进程号,便于父进程获得子进程的id。

\umask(0);
//下面的代码,重复执行两次。完成守护进程。
$pid = \pcntl_fork();
if (-1 === $pid) {
	throw new Exception('Fork fail');
} elseif ($pid > 0) {
	exit(0);
}
//重复上面的代码。

initWorkers

非linux系统,直接返回。

对于static::$_workers中的每个worker,初实化name、user、socket、status变量。输出该阶段的worker信息。如果reusePort,那么调用listen函数。

installSignal

Linux系统下,安装七种信号。停止、优雅停止、重启、优雅重启、状态、连接状态、忽略信号。

信号的回调函数signalHandler

另外,在复制子进程的时候,会调用run函数。而run,会调用reinstallSignal。即复制子进程的时候,先卸载先前master进程上的信号,而安装static::$globalEvent中的事件。

saveMasterPid

unlock

displayUI

forkWorkers

resetStd

monitorWorkers

部分代码

  • 设置进程的名称,一是:主进程的名字,二是,复制的子进程的名字。核心函数 cli_set_process_title
protected static function setProcessTitle($title)
    {
        \set_error_handler(function(){});
        // >=php 5.5
        if (\function_exists('cli_set_process_title')) {
            \cli_set_process_title($title);
        } // Need proctitle when php<=5.5 .
        elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) {
            \setproctitle($title);
        }
        \restore_error_handler();
    }
  • parseCommand函数中有如下过程,关键搞懂posix_kill($master_pid, 0)函数的意思。

估计并不是杀掉一个进程,一开始,一直以为是杀掉一个进程。实际上并不是。kill -0 pid 不发送任何信号,但是系统会进行错误检查。所以经常用来检查一个进程是否存在,存在则echo $?返回0;不存在返回1 。

// Get master process PID.
$master_pid      = \is_file(static::$pidFile) ? \file_get_contents(static::$pidFile) : 0;
$master_is_alive = $master_pid && \posix_kill($master_pid, 0) && \posix_getpid() !== $master_pid;

kill用法示例(shell):


chaofml@scc:~/demo/Workerman$ kill -0 3394
-bash: kill: (3394) - No such process
chaofml@scc:~/demo/Workerman$ echo $?
1

chaofml@scc:~/demo/Workerman$ kill -0 3414
chaofml@scc:~/demo/Workerman$ echo $?
0

chaofml@scc:~/demo/Workerman$ ps aux |grep 3414
chaofml   3414  0.0  0.6 239616 12928 ?        S    09:38   0:00 WorkerMan: worker process  none http://0.0.0.0:2345
chaofml   3429  0.0  0.0  21532  1108 pts/3    S+   09:43   0:00 grep --color=auto 3414

有关kill的小例子(延伸):

#!/bin/bash

PID=$(pgrep sleep)
if ! kill -0 $PID 2>/dev/null; then 
  echo "you don't have permissions to kill PID:$PID"
  exit 1
fi

# 或者
kill -0 $pid
if [ $? -gt 0 ]
	echo "进程$pid存在"
else
	echo "进程$pid不存在"
fi
  • 终端格式化输出

终端输出样式示例:

<?php
   echo "\033[47;30m哈哈\033[0m".PHP_EOL;

shell版本(使用sh,bash好像不行):

#!/bin/sh
echo "\033[47;30m哈哈\033[0m"

workerman中的代码(替换相关的标记):

if (static::$_outputDecorated) {
                $line = "\033[1A\n\033[K";
                $white = "\033[47;30m";
                $green = "\033[32;40m";
                $end = "\033[0m";
            }
            $msg = \str_replace(array('<n>', '<w>', '<g>'), array($line, $white, $green), $msg);
            $msg = \str_replace(array('</n>', '</w>', '</g>'), $end, $msg);

疑惑

  • 打开一个文件,释放文件句柄,就能关闭这个文件吗?

resetStd函数中,代码如下:

$handle = \fopen(static::$stdoutFile, "a");
if ($handle) {
    unset($handle);
  • set_error_handler是栈的形式,记住所有的函数吗?

首先,下面的代码的函数,意图是啥?是想放弃错误捕捉吗?

另外,set_error_handler 肯定可以设置多次,那么每次,将错误补捉的堆栈,pop一层的时候,使用如下restore_error_handler函数实现吗?

set_error_handler(function(){});
//一大堆代码
\restore_error_handler();
  • 函数定义 ,self关键字

self是什么作用?从来没有见过这种语法。

按类型约束来讲(php5有类型约束,针对的是非标量),那么self 表示的是这个类的类型。

protected static function forkOneWorkerForLinux(self $worker)